diff options
Diffstat (limited to 'g10')
-rw-r--r-- | g10/ChangeLog | 12964 | ||||
-rw-r--r-- | g10/Makefile.am | 156 | ||||
-rw-r--r-- | g10/Makefile.in | 801 | ||||
-rw-r--r-- | g10/apdu.c | 3013 | ||||
-rw-r--r-- | g10/apdu.h | 123 | ||||
-rw-r--r-- | g10/app-common.h | 197 | ||||
-rw-r--r-- | g10/app-openpgp.c | 2528 | ||||
-rw-r--r-- | g10/armor.c | 1474 | ||||
-rw-r--r-- | g10/build-packet.c | 1308 | ||||
-rw-r--r-- | g10/card-util.c | 1610 | ||||
-rw-r--r-- | g10/cardglue.c | 1432 | ||||
-rw-r--r-- | g10/cardglue.h | 211 | ||||
-rw-r--r-- | g10/ccid-driver.c | 2745 | ||||
-rw-r--r-- | g10/ccid-driver.h | 108 | ||||
-rw-r--r-- | g10/cipher.c | 153 | ||||
-rw-r--r-- | g10/compress-bz2.c | 242 | ||||
-rw-r--r-- | g10/compress.c | 365 | ||||
-rw-r--r-- | g10/dearmor.c | 137 | ||||
-rw-r--r-- | g10/decrypt.c | 191 | ||||
-rw-r--r-- | g10/delkey.c | 221 | ||||
-rw-r--r-- | g10/encode.c | 877 | ||||
-rw-r--r-- | g10/encr-data.c | 320 | ||||
-rw-r--r-- | g10/exec.c | 626 | ||||
-rw-r--r-- | g10/exec.h | 52 | ||||
-rw-r--r-- | g10/export.c | 600 | ||||
-rw-r--r-- | g10/filter.h | 167 | ||||
-rw-r--r-- | g10/free-packet.c | 569 | ||||
-rw-r--r-- | g10/getkey.c | 3003 | ||||
-rw-r--r-- | g10/global.h | 30 | ||||
-rw-r--r-- | g10/gpg.c | 4207 | ||||
-rw-r--r-- | g10/gpgv.c | 434 | ||||
-rw-r--r-- | g10/helptext.c | 300 | ||||
-rw-r--r-- | g10/import.c | 2396 | ||||
-rw-r--r-- | g10/iso7816.c | 685 | ||||
-rw-r--r-- | g10/iso7816.h | 81 | ||||
-rw-r--r-- | g10/kbnode.c | 399 | ||||
-rw-r--r-- | g10/keydb.c | 807 | ||||
-rw-r--r-- | g10/keydb.h | 317 | ||||
-rw-r--r-- | g10/keyedit.c | 5094 | ||||
-rw-r--r-- | g10/keygen.c | 3707 | ||||
-rw-r--r-- | g10/keyid.c | 753 | ||||
-rw-r--r-- | g10/keylist.c | 1638 | ||||
-rw-r--r-- | g10/keyring.c | 1624 | ||||
-rw-r--r-- | g10/keyring.h | 46 | ||||
-rw-r--r-- | g10/keyserver-internal.h | 54 | ||||
-rw-r--r-- | g10/keyserver.c | 2132 | ||||
-rw-r--r-- | g10/main.h | 300 | ||||
-rw-r--r-- | g10/mainproc.c | 2083 | ||||
-rw-r--r-- | g10/mdfilter.c | 77 | ||||
-rw-r--r-- | g10/misc.c | 1284 | ||||
-rw-r--r-- | g10/openfile.c | 430 | ||||
-rw-r--r-- | g10/options.h | 335 | ||||
-rw-r--r-- | g10/options.skel | 241 | ||||
-rw-r--r-- | g10/packet.h | 569 | ||||
-rw-r--r-- | g10/parse-packet.c | 2411 | ||||
-rw-r--r-- | g10/passphrase.c | 1084 | ||||
-rw-r--r-- | g10/photoid.c | 385 | ||||
-rw-r--r-- | g10/photoid.h | 35 | ||||
-rw-r--r-- | g10/pipemode.c | 318 | ||||
-rw-r--r-- | g10/pkclist.c | 1448 | ||||
-rw-r--r-- | g10/plaintext.c | 589 | ||||
-rw-r--r-- | g10/progress.c | 118 | ||||
-rw-r--r-- | g10/pubkey-enc.c | 355 | ||||
-rw-r--r-- | g10/revoke.c | 738 | ||||
-rw-r--r-- | g10/seckey-cert.c | 427 | ||||
-rw-r--r-- | g10/seskey.c | 271 | ||||
-rw-r--r-- | g10/sig-check.c | 616 | ||||
-rw-r--r-- | g10/sign.c | 1578 | ||||
-rw-r--r-- | g10/signal.c | 238 | ||||
-rw-r--r-- | g10/skclist.c | 223 | ||||
-rw-r--r-- | g10/status.c | 790 | ||||
-rw-r--r-- | g10/status.h | 150 | ||||
-rw-r--r-- | g10/tdbdump.c | 233 | ||||
-rw-r--r-- | g10/tdbio.c | 1633 | ||||
-rw-r--r-- | g10/tdbio.h | 118 | ||||
-rw-r--r-- | g10/textfilter.c | 251 | ||||
-rw-r--r-- | g10/tlv.c | 306 | ||||
-rw-r--r-- | g10/tlv.h | 109 | ||||
-rw-r--r-- | g10/trustdb.c | 2368 | ||||
-rw-r--r-- | g10/trustdb.h | 98 | ||||
-rw-r--r-- | g10/verify.c | 211 |
81 files changed, 83317 insertions, 0 deletions
diff --git a/g10/ChangeLog b/g10/ChangeLog new file mode 100644 index 0000000..20d3ec1 --- /dev/null +++ b/g10/ChangeLog @@ -0,0 +1,12964 @@ +2006-12-04 Werner Koch <wk@g10code.com> + + * filter.h (armor_filter_context_t): New field REFCOUNT. + * armor.c (new_armor_context, release_armor_context) + (push_armor_filter): New. + (armor_filter): Call releae_armor_context for IOBUFCTRL_FREE. + * import.c (import): Use the new function here instead of the + old hack using the iobuf_push_filter2. + * keyserver.c (keyserver_spawn): Ditto. + +2006-12-03 Werner Koch <wk@g10code.com> + + * keyedit.c (menu_clean): Made strings translatable. + +2006-12-03 David Shaw <dshaw@jabberwocky.com> + + * keyedit.c (menu_clean): Show "already minimized" rather than + "already clean" when a minimized key is minimized again. From + Dirk Traulsen. + +2006-12-02 David Shaw <dshaw@jabberwocky.com> + + * options.h, gpg.c (main), passphrase.c (passphrase_to_dek): Add + --passphrase-repeat option to control how many times gpg will + re-prompt for a passphrase to ensure the user has typed it + correctly. Defaults to 1. + +2006-11-27 Werner Koch <wk@g10code.com> + + * openfile.c (ask_outfile_name): Fixed buffer overflow occurring + if make_printable_string returns a longer string. Fixes bug 728. + +2006-10-23 Werner Koch <wk@g10code.com> + + * gpg.c (main): New command --gpgconf-list. + +2006-10-12 David Shaw <dshaw@jabberwocky.com> + + * parse-packet.c (parse_symkeyenc): Show the unpacked as well as + the packed s2k iteration count. + + * main.h, options.h, gpg.c (encode_s2k_iterations, main), + passphrase.c (hash_passphrase): Add --s2k-count option to specify + the number of s2k hash iterations. + +2006-10-06 David Shaw <dshaw@jabberwocky.com> + + * keyserver.c (keyserver_spawn): Write the 16-digit keyid rather + than whatever key selector the user used on the command line. + +2006-10-02 Werner Koch <wk@g10code.com> + + * encr-data.c (decrypt_data, mdc_decode_filter): Check the MDC + right here and don't let parse-packet handle the MDC. + +2006-08-21 Werner Koch <wk@g10code.com> + + * skclist.c (is_insecure): Also test for uppercase version of the + insecure string. + +2006-07-31 Werner Koch <wk@g10code.com> + + * openfile.c (open_outfile) [USE_ONLY_8DOT3]: Search backwards for + the dot. Fixes bug 654. + + * passphrase.c (agent_open): Use log_info instead of log_error to + allow a fallback without having gpg return an error code. Fixes + bug #655. + + * encode.c (encode_crypt_files): Invalidate the whole fd cache. + This is a workaround for problems in iobuf's stupid fd cache. + * decrypt.c (decrypt_messages): Ditto. + * verify.c (verify_files): Ditto. + +2006-07-26 Werner Koch <wk@g10code.com> + + * keygen.c (gen_card_key_with_backup): Initialize sk_{un}protected. + + * import.c (import): Initialize KEYBLOCK. + + * pkclist.c (edit_ownertrust): Intialize trust to avoid gcc + warning. + + * parse-packet.c (parse_comment): Cap comments at 65k. + (parse_gpg_control): Skip too large control packets. + +2006-06-28 David Shaw <dshaw@jabberwocky.com> + + * keydb.h, pkclist.c (select_algo_from_prefs, algo_available): + Pass a union for preference hints rather than doing void * games. + + * sign.c (sign_file): Use it here. + + * sign.c (sign_file): When signing with multiple DSA keys, one + being DSA1 and one being DSA2 and encrypting at the same time, if + the recipient preferences give a hash that can work with the DSA2 + key, then allow the DSA1 key to be promoted rather than giving up + and using hash_for(). + + * pkclist.c (algo_available): Automatically enable DSA2 mode when + handling a key that clearly isn't DSA1 (i.e. q!=160). + +2006-06-28 Werner Koch <wk@g10code.com> + + * import.c (check_prefs_warning): Fix change for better + translatability. + + * 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-27 Werner Koch <wk@g10code.com> + + * gpg.c (reopen_std) [HAVE_W32_SYSTEM]: Do not use it. + +2006-06-22 David Shaw <dshaw@jabberwocky.com> + + * options.h, gpg.c (main), keygen.c (keygen_upd_std_prefs, + keygen_add_std_prefs, proc_parameter_file): Add + --default-keyserver-url to specify a keyserver URL at key + generation time, and "Keyserver:" keyword for doing the same + through a batch file. + + * sign.c (do_sign): Accept a truncated hash even for DSA1 keys (be + liberal in what you accept, etc). + +2006-06-12 David Shaw <dshaw@jabberwocky.com> + + * import.c (import_one): Add a flag (from_sk) so we don't check + prefs on an autoconverted public key. The check should only + happen on the sk side. Noted by Dirk Traulsen. + +2006-06-09 David Shaw <dshaw@jabberwocky.com> + + * keygen.c (gen_card_key): Add optional argument to return a + pointer (not a copy) of the stub secret key for the secret key we + just generated on the card. + (generate_card_subkeypair): Use it here so that the signing key on + the card can use the card to generate the 0x19 backsig on the + primary key. Noted by Janko Heilgeist and Jonas Oberg. + + * parse-packet.c (parse_user_id): Cap the user ID size at 2048 + bytes. This prevents a memory allocation attack with a very large + user ID. A very large packet length could even cause the + allocation (a u32) to wrap around to a small number. Noted by + Evgeny Legerov on full-disclosure. + +2006-05-25 David Shaw <dshaw@jabberwocky.com> + + * keygen.c (gen_dsa): Allow generating DSA2 keys + (allow specifying sizes > 1024 when --enable-dsa2 is set). The + size of q is set automatically based on the key size. + (ask_keysize, generate_keypair): Ask for DSA size when + --enable-dsa2 is set. + +2006-05-24 David Shaw <dshaw@jabberwocky.com> + + * exec.c (make_tempdir): Fix bug with a temporary directory on + Win32 that is over 256 bytes long. Noted by Israel G. Lugo. + +2006-05-23 David Shaw <dshaw@jabberwocky.com> + + * gpg.c (reopen_std): New function to reopen fd 0, 1, or 2 if we + are called with them closed. This is to protect our + keyring/trustdb files from corruption if they get attached to one + of the standard fds. Print a warning if possible that this has + happened, and fail completely if we cannot reopen (should never + happen). + (main): Call it here. + +2006-05-22 David Shaw <dshaw@jabberwocky.com> + + * parse-packet.c (dump_sig_subpkt, parse_signature), + build-packet.c (build_sig_subpkt_from_sig), getkey.c + (fixup_uidnode, merge_selfsigs_main, merge_selfsigs_subkey), + keygen.c (keygen_add_key_expire): Fix meaning of key expiration + and sig expiration subpackets - zero means "never expire" + according to 2440, not "expire instantly". + + * getkey.c (get_pubkey_byname), import.c (import_one): Fix key + selection problem when auto-key-locate returns a list of keys, not + all of which are usable (revoked, expired, etc). Noted by Simon + Josefsson. + +2006-04-26 David Shaw <dshaw@jabberwocky.com> + + * keyserver.c (direct_uri_map): New. + (keyserver_spawn): Used here to add "_uri" to certain gpgkeys_xxx + helpers when the meaning is different if a path is provided + (i.e. ldap). + (keyserver_import_cert): Show warning if there is a CERT + fingerprint, but no --keyserver set. + +2006-04-22 David Shaw <dshaw@jabberwocky.com> + + * keyserver.c: Fix build problem with platforms that stick libcurl + in a place not in the regular include search path. + +2006-04-20 David Shaw <dshaw@jabberwocky.com> + + * options.h, gpg.c (main): Add --enable-dsa2 and --disable-dsa2. + Defaults to disable. + + * pkclist.c (algo_available): If --enable-dsa2 is set, we're + allowed to truncate hashes to fit DSA keys. + + * sign.c (match_dsa_hash): New. Return the best match hash for a + given q size. + (do_sign, hash_for, sign_file): When signing with a DSA key, if it + has q==160, assume it is an old DSA key and don't allow truncation + unless --enable-dsa2 is also set. q!=160 always allows truncation + since they must be DSA2 keys. + (make_keysig_packet): If the user doesn't specify a + --cert-digest-algo, use match_dsa_hash to pick the best hash for + key signatures. + +2006-04-19 David Shaw <dshaw@jabberwocky.com> + + * gpg.c (print_mds), armor.c (armor_filter, parse_hash_header): + Add SHA-224. + + * sign.c (write_plaintext_packet), encode.c (encode_simple): + Factor common literal packet setup code from here, to... + + * main.h, plaintext.c (setup_plaintext_name): Here. New. Make sure + the literal packet filename field is UTF-8 encoded. + + * options.h, gpg.c (main): Make sure --set-filename is UTF-8 + encoded and note when filenames are already UTF-8. + +2006-04-18 David Shaw <dshaw@jabberwocky.com> + + * keyedit.c (menu_backsign): Give some more verbose errors when we + have no need to backsign. + +2006-04-11 David Shaw <dshaw@jabberwocky.com> + + * options.skel, photoid.c (get_default_photo_command): Find an + image viewer at runtime. Seems FC5 doesn't have xloadimage. + +2006-04-08 David Shaw <dshaw@jabberwocky.com> + + * getkey.c (parse_auto_key_locate): Fix dupe-removal code. + + * keyedit.c (menu_backsign): Allow backsigning even if the secret + subkey doesn't have a binding signature. + + * armor.c (radix64_read): Don't report EOF when reading only a pad + (=) character. The EOF actually starts after the pad. + + * gpg.c (main): Make --export, --send-keys, --recv-keys, + --refresh-keys, and --fetch-keys follow their arguments from left + to right. Suggested by Peter Palfrader. + +2006-04-08 Werner Koch <wk@g10code.com> + + * mainproc.c (list_node): Print ring trust value only if not empty + and --with-colons has been given. + +2006-04-05 Werner Koch <wk@g10code.com> + + * getkey.c (user_id_not_found_utf8): New. + (get_primary_uid, get_user_id): Use it. Fixes Debian bug #205028 + in the right way. + +2006-04-03 Werner Koch <wk@g10code.com> + + * import.c (check_prefs_warning): Merged strings for better + translation. + + * gpg.c (main) [__GLIBC__]: Default to libpcsclite.so.1. + + * status.h, status.c (STATUS_BEGIN_SIGNING): New. Suggested by + Daiki Ueno. + * textfilter.c (copy_clearsig_text): Issue new status code. + * sign.c (sign_file, sign_symencrypt_file): Ditto. + +2006-03-31 David Shaw <dshaw@jabberwocky.com> + + * getkey.c (get_pubkey_byname): Fix missing auto_key_retrieve + unlock. Fix strings to not start with a capital letter as per + convention. + +2006-03-30 David Shaw <dshaw@jabberwocky.com> + + * main.h, seskey.c (encode_md_value): Modify to allow a q size + greater than 160 bits as per DSA2. This will allow us to verify + and issue DSA2 signatures for some backwards compatibility once we + start generating DSA2 keys. + * sign.c (do_sign), sig-check.c (do_check): Change all callers. + + * sign.c (do_sign): Enforce the 160-bit check for new signatures + here since encode_md_value can handle non-160-bit digests now. + This will need to come out once the standard for DSA2 is firmed + up. + +2006-03-28 Werner Koch <wk@g10code.com> + + * openfile.c (overwrite_filep): Fix small cpr issue. Noted by + Daiki Ueno. + +2006-03-22 David Shaw <dshaw@jabberwocky.com> + + * getkey.c (parse_auto_key_locate): Silently strip out duplicates + rather than causing an error. + +2006-03-22 Werner Koch <wk@g10code.com> + + * sig-check.c (signature_check2): Changed warning URL to include faq. + * misc.c (idea_cipher_warn): Ditto. + +2006-03-22 David Shaw <dshaw@jabberwocky.com> + + * mainproc.c (get_pka_address): Fix bug introduced as part of + sig_to_notation conversion. Noted by Peter Palfradrer. + +2006-03-21 Werner Koch <wk@g10code.com> + + * cardglue.c (agent_scd_pksign): Allow the use of ripemd-160 along + with scdaemon. + +2006-03-16 David Shaw <dshaw@jabberwocky.com> + + * keyserver.c (keyserver_import_cert): Handle the IPGP CERT type + for both the fingerprint alone, and fingerprint+URL cases. + + * getkey.c (get_pubkey_byname): Minor cleanup. + +2006-03-13 David Shaw <dshaw@jabberwocky.com> + + * keyserver-internal.h, keyserver.c (keyserver_import_pka): Use + the same API as the other auto-key-locate fetchers. + + * getkey.c (get_pubkey_byname): Use the fingerprint of the key + that we actually fetched. This helps prevent problems where the + key that we fetched doesn't have the same name that we used to + fetch it. In the case of CERT and PKA, this is an actual security + requirement as the URL might point to a key put in by an attacker. + By forcing the use of the fingerprint, we won't use the attacker's + key here. + + * keyserver-internal.h, keyserver.c (keyserver_spawn, + keyserver_work, keyserver_import_cert, keyserver_import_name, + keyserver_import_ldap): Pass fingerprint info through. + + * main.h, import.c (import_one): Optionally return the fingerprint + of the key being imported. + (import_keys_internal, import_keys_stream, import): Change all + callers. + +2006-03-12 David Shaw <dshaw@jabberwocky.com> + + * sig-check.c (signature_check2): Print the backsig warning when + there is no backsig present. Give a URL for more information. + + * keyedit.c (menu_backsign): Small tweak to work properly with + keys originally generated with older GnuPGs that included comments + in the secret keys. + +2006-03-10 Werner Koch <wk@g10code.com> + + * card-util.c (get_manufacturer): Added Vendor 3 + +2006-03-09 David Shaw <dshaw@jabberwocky.com> + + * build-packet.c (string_to_notation): Add ability to indicate a + notation to be deleted with a '-' prefix. + + * keyedit.c (menu_set_notation): Use it here to allow deleting a + notation marked with '-'. This works with either "-notation" or + "-notation=value". + +2006-03-08 David Shaw <dshaw@jabberwocky.com> + + * keyedit.c (menu_set_notation): New function to set notations on + self-signatures. + (keyedit_menu): Call it here. + (tty_print_notations): Helper. + (show_prefs): Show notations in "showpref". + + * mainproc.c (get_pka_address) + * keylist.c (show_notation): Remove + duplicate code by using notation functions. + + * packet.h, build-packet.c (sig_to_notation) + * keygen.c (keygen_add_notations): Provide printable text for + non-human-readable notation values. + + * packet.h, build-packet.c (sig_to_notation) + * keygen.c (keygen_add_notations): Tweak to handle non-human-readable + notation values. + + * options.h, sign.c (mk_notation_policy_etc) + * gpg.c (add_notation_data): Use it here for the various notation + commands. + + * packet.h, main.h, keygen.c (keygen_add_notations) + * build-packet.c (string_to_notation, sig_to_notation) + (free_notation): New "one stop shopping" functions to handle + notations and start removing some code duplication. + +2006-03-08 Werner Koch <wk@g10code.com> + + * mainproc.c (do_check_sig): Use log_error for standalone revocations. + +2006-03-07 David Shaw <dshaw@jabberwocky.com> + + * options.h, mainproc.c (check_sig_and_print), gpg.c (main): + pka-lookups, not pka-lookup. + + * options.h, gpg.c (main), keyedit.c [cmds], sig-check.c + (signature_check2): Rename "backsign" to "cross-certify" as a more + accurate name. + + * options.h, gpg.c (main, parse_trust_model), pkclist.c + (check_signatures_trust), mainproc.c (check_sig_and_print, + pka_uri_from_sig), trustdb.c (init_trustdb): Some tweaks to PKA so + that it is a verify-option now. + +2006-03-07 Werner Koch <wk@g10code.com> + + * mainproc.c (proc_signature_packets): Return any_sig_seen to caller. + (check_sig_and_print): Option to partly allow the old behaviour. + * gpg.c: New option --allow-multisig-verification. + +2006-03-06 David Shaw <dshaw@jabberwocky.com> + + * sign.c (make_keysig_packet): Don't use MD5 for a RSA_S key as + that is not a PGP 2.x algorithm. + + * mainproc.c (proc_compressed): "Uncompressed" is not a valid + compression algorithm. + +2006-03-06 Werner Koch <wk@g10code.com> + + * mainproc.c (check_sig_and_print): Made the composition test more + tight. This is due to another bug report by Tavis Ormandy. + (add_onepass_sig): Simplified. + +2006-03-05 Werner Koch <wk@g10code.com> + + * plaintext.c (handle_plaintext): Replace assert by explict error + conflict message. Reported by Tavis Ormandy. + +2006-03-02 Werner Koch <wk@g10code.com> + + * cardglue.c (check_card_serialno): Don't ask in batch mode. + +2006-03-01 David Shaw <dshaw@jabberwocky.com> + + * getkey.c (parse_auto_key_locate): Error if the user selects + "cert" or "pka" when those features are disabled. + + * misc.c (has_invalid_email_chars): Fix some C syntax that broke + the compilers on SGI IRIX MIPS and Compaq/DEC OSF/1 Alpha. Noted + by Nelson H. F. Beebe. + +2006-02-27 David Shaw <dshaw@jabberwocky.com> + + * options.skel: Document auto-key-locate and give a pointer to + Simon Josefsson's page for CERT. + +2006-02-24 David Shaw <dshaw@jabberwocky.com> + + * keydb.h, getkey.c (release_akl), gpg.c (main): Add + --no-auto-key-locate. + + * options.h, gpg.c (main): Keep track of each keyserver registered + so we can match on them later. + + * keyserver-internal.h, keyserver.c (cmp_keyserver_spec, + keyserver_match), gpgv.c: New. Find a keyserver that matches ours + and return its spec. + + * getkey.c (get_pubkey_byname): Use it here to get the + per-keyserver options from an earlier keyserver. + +2006-02-23 David Shaw <dshaw@jabberwocky.com> + + * keyserver.c (parse_keyserver_options): Only change max_cert if + it is used. + + * options.c, gpg.c (main), keyserver.c (keyserver_spawn): No + special treatment of include-revoked, include-subkeys, and + try-dns-srv. These are keyserver features, and GPG shouldn't get + involved here. + + * keyserver.c (parse_keyserver_uri, add_canonical_option): Always + append options to the list, as ordering may be significant to the + user. + + * gpg.c (add_notation_data): Fix reversed logic for isascii check + when adding notations. Noted by Christian Biere. + + * options.h, keyserver.c (add_canonical_option): New. + (parse_keyserver_options): Moved from here. + (parse_keyserver_uri): Use it here so each keyserver can have some + private options in addition to the main keyserver-options + (e.g. per-keyserver auth). + +2006-02-22 David Shaw <dshaw@jabberwocky.com> + + * options.h, keyserver-internal.h, keyserver.c + (keyserver_import_name), getkey.c (free_akl, + parse_auto_key_locate, get_pubkey_byname): The obvious next step: + allow arbitrary keyservers in the auto-key-locate list. + + * options.h, keyserver.c (parse_keyserver_options): Remove + auto-cert-retrieve as it is no longer meaningful. Add + max-cert-size to allow users to pick a max key size retrieved via + CERT. + + * options.h, gpg.c (main), mainproc.c (check_sig_and_print), + keyserver.c (keyserver_opts): Rename auto-pka-retrieve to + honor-pka-record to be consistent with honor-keyserver-url. + + * options.h, keydb.h, g10.c (main), getkey.c + (parse_auto_key_locate): Parse a list of key access methods. + (get_pubkey_byname): Walk the list here to try and retrieve keys + we don't have locally. + +2006-02-21 David Shaw <dshaw@jabberwocky.com> + + * getkey.c (get_pubkey_byname): Fix minor security problem with + PKA when importing at -r time. The URL in the PKA record may + point to a key put in by an attacker. Fix is to use the + fingerprint from the PKA record as the recipient. This ensures + that the PKA record is followed. + + * keyserver-internal.h, keyserver.c (keyserver_import_pka): Return + the fingerprint we requested. + + * gpgv.c: Stub keyserver_import_ldap. + + * keyserver-internal.h, keyserver.c (keyserver_import_ldap): + Import using the PGP Universal trick of asking + ldap://keys.(maildomain) for the key. + +2006-02-20 David Shaw <dshaw@jabberwocky.com> + + * keyserver.c (parse_keyserver_uri): Include the scheme in the uri + even when we've assumed "hkp" when there was no scheme. + +2006-02-20 Werner Koch <wk@g10code.com> + + * apdu.c (open_pcsc_reader): As a precaution set LIST to NULL + after free. + +2006-02-14 Werner Koch <wk@gnupg.org> + + * verify.c (verify_signatures): Print warning also for NO_DATA. + + * mainproc.c (struct mainproc_context): New field any_sig_seen. + (add_signature): Set it. + (proc_signature_packets): Test and return NO_DATA. + +2006-02-09 Werner Koch <wk@g10code.com> + + * gpg.c (main) <oLockNever>: Disable random locking. + +2006-02-06 Werner Koch <wk@g10code.com> + + * ccid-driver.c, ccid-driver.h: Updated from GnuPG 1.9. Changes: + * ccid-driver.h (CCID_DRIVER_ERR_NO_KEYPAD): New. + * ccid-driver.c (send_escape_cmd): New args RESULT, RESULTLEN and + RESULTMAX. Changed all callers. + (ccid_transceive_escape): New. + * 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-24 David Shaw <dshaw@jabberwocky.com> + + * keyserver.c (parse_keyserver_uri): If there is a path present, + set the direct_uri flag so the right keyserver helper is run. + +2006-01-22 David Shaw <dshaw@jabberwocky.com> + + * keyserver.c (keyserver_spawn): Include the EXEEXT so we can find + keyserver helpers on systems that use extensions. + + * misc.c (path_access) [HAVE_DRIVE_LETTERS]: Do the right thing + with drive letter systems. + +2006-01-17 David Shaw <dshaw@jabberwocky.com> + + * keydb.h, passphrase.c (next_to_last_passphrase): New. "Touch" a + passphrase as if it was used (move from next_pw to last_pw). + + * pubkey-enc.c (get_session_key): Use it here to handle the case + where a passphrase happens to be correct for a secret key, but yet + that key isn't the anonymous recipient (i.e. the secret key could + be decrypted, but not the session key). This also handles the + case where a secret key is located on a card and a secret key with + no passphrase. Note this does not fix bug 594 (anonymous + recipients on smartcard do not work) - it just prevents the + anonymous search from stopping when the card is encountered. + +2006-01-07 David Shaw <dshaw@jabberwocky.com> + + * keyserver.c (keyserver_refresh): Fix problem when more than one + key in a refresh batch has a preferred keyserver set. Noted by + Nicolas Rachinsky. + +2006-01-01 David Shaw <dshaw@jabberwocky.com> + + * mainproc.c (check_sig_and_print), keyserver.c + (keyserver_import_pka), card-util.c (fetch_url): Always require a + scheme:// for keyserver URLs except when used as part of the + --keyserver command for backwards compatibility. + + * sign.c (write_signature_packets): Lost a digest_algo line. + + * sign.c (hash_for): Add code to detect if the sk lives on a smart + card. If it does, only allow 160-bit hashes, a la DSA. This + involves passing the *sk in, so change all callers. This is + correct for today, given the current 160-bit q in DSA, and the + current SHA-1/RIPEMD160 support in the openpgp card. It will + almost certainly need changing down the road. + + * app-openpgp.c (do_sign): Give user error if hash algorithm is + not supported by the card. + +2005-12-23 David Shaw <dshaw@jabberwocky.com> + + * keyserver.c (keyserver_import_pka): New. Moved from + getkey.c:get_pubkey_byname which was getting crowded. + + * keyserver.c (keyserver_import_cert): Import a key found in DNS + via CERT records. Can handle both the PGP (actual key) and IPGP + (URL) CERT types. + + * getkey.c (get_pubkey_byname): Call them both here. + + * options.h, keyserver.c (parse_keyserver_options): Add + "auto-cert-retrieve" option with optional max size argument. + + * gpgv.c: Stubs. + + * keyserver-internal.h, keyserver.c (keyserver_spawn, + keyserver_work, keygerver_getname): New keyserver_getname function + to fetch keys by name. + + * getkey.c (get_pubkey_byname): Call it here to enable locating + keys by full mailbox from a keyserver a la PKA. Try PKA first, + though, as it is likely to be faster. + +2005-12-20 Werner Koch <wk@g10code.com> + + * gpg.c: New option --allow-pka-lookup. + (parse_trust_model): Add "+pka" variants. + (main): Make KEYSERVER_AUTO_PKA_RETRIEVE teh default. + * options.h (opt): New fields PKA_TRUST_INCREASE and + ALLOW_PKA_LOOKUP. + * status.h (STATUS_PKA_TRUST_BAD, STATUS_PKA_TRUST_GOOD): New. + * pkclist.c (check_signatures_trust): Increase trust due to valid + PKA only if that new option has been set. Issue new status lines. + * trustdb.c (init_trustdb): Print info if this option is active. + * getkey.c (get_pubkey_byname): Honor allow-pka-lookup. + * mainproc.c (pka_uri_from_sig): Ditto. + + * trustdb.c (validate_keys): Print no "ultimately trusted keys + found" only in non-quiet mode. + +2005-12-19 David Shaw <dshaw@jabberwocky.com> + + * getkey.c (merge_selfsigs_main): All primary keys can certify. + +2005-12-18 David Shaw <dshaw@jabberwocky.com> + + * gpg.c (main): Restore convert-sk-to-pk as programs rely on it. + + * keyid.c (usagestr_from_pk): Remove special PUBKEY_USAGE_CERT + flag. It's no longer needed. + +2005-12-14 David Shaw <dshaw@jabberwocky.com> + + * gpg.c (main): Don't default to import-options convert-sk-to-pk. + It causes confusing warning messages when importing a PGP-exported + key that contains a secret key without selfsigs followed by the + public key. + +2005-12-08 David Shaw <dshaw@jabberwocky.com> + + * keyserver.c (keyserver_fetch): Switch on fast-import before we + --fetch-keys so we don't rebuild the trustdb after each fetch. + +2005-12-08 Werner Koch <wk@g10code.com> + + * gpg.c (main): Check for DBCS lead byte when converting the + homedir. By Kazuyoshi Kakihara. Fixes PR561. + + * keyserver.c (keyserver_fetch): Made strings translatable. + +2005-12-08 David Shaw <dshaw@jabberwocky.com> + + * options.h, keyserver.c (curl_cant_handle, keyserver_spawn) + (keyserver_fetch): Set a flag to indicate that we're doing a direct + URI fetch so we can differentiate between a keyserver operation + and a URI fetch for protocols like LDAP that can do either. + +2005-12-07 David Shaw <dshaw@jabberwocky.com> + + * keyserver.c (keyserver_spawn): Don't print "searching for key + 00000000" when fetching a URI. + + * keyserver-internal.h, keyserver.c (keyserver_fetch): New. Fetch + an arbitrary URI using the keyserver helpers. + + * gpg.c (main): Call it from here for --fetch-keys. + +2005-12-07 Werner Koch <wk@g10code.com> + + * pkclist.c (do_we_trust): Add NOTREACHED comment. + +2005-11-20 David Shaw <dshaw@jabberwocky.com> + + * main.h, keylist.c (print_revokers): New. Print the "rvk" + designated revoker record. Moved from + keyedit.c:show_key_with_all_names_colon. + + * keylist.c (list_keyblock_colon): Use it here ... + + * keyedit.c (show_key_with_all_names_colon): ... and here. + +2005-11-19 David Shaw <dshaw@jabberwocky.com> + + * free-packet.c (copy_secret_key): Copy secret key into secure + memory since we may unprotect it. + + * main.h, g10.c (main), revoke.c (gen_desig_revoke): Add local + user support so users can use -u with --desig-revoke. This + bypasses the interactive walk over the revocation keys. + +2005-11-17 David Shaw <dshaw@jabberwocky.com> + + * keyedit.c (keyedit_menu, menu_clean): Simplify clean options to + just "clean", and add "minimize". + + * import.c (parse_import_options): Make help text match the export + versions of the options. + + * options.h, export.c (parse_export_options, do_export_stream): + Reduce clean options to two: clean and minimize. + + * trustdb.h, trustdb.c (clean_one_uid): New function that joins + uid and sig cleaning into one for a simple API outside trustdb. + +2005-11-13 David Shaw <dshaw@jabberwocky.com> + + * armor.c (parse_header_line): A fussy bit of 2440: header lines + are delimited with a colon-space pair. Therefore a line such as + "Comment: " (with a trailing space) is actually legal, albeit not + particularly useful. + +2005-11-11 David Shaw <dshaw@jabberwocky.com> + + * trustdb.h, trustdb.c (clean_key): New function to handle key + cleaning from one convenient place. + + * options.h, import.c (parse_import_options, + clean_sigs_from_all_uids, import_one): Reduce clean options to + two: clean and minimize. + + * parse-packet.c (setup_user_id): Remove. + (parse_user_id, parse_attribute): Just use xmalloc_clear instead. + + * trustdb.c (clean_uid_from_key, clean_uids_from_key): + Significantly simpler implementation. + +2005-11-10 David Shaw <dshaw@jabberwocky.com> + + * keyedit.c (keyedit_menu, menu_clean_sigs_from_uids): Add + "minimize" command. + + * packet.h, keyedit.c (menu_clean_uids_from_key), trustdb.c + (clean_uids_from_key): Fix display bug where sigs cleaned for + other reasons caused a uid to appear as if it had been compacted. + + * packet.h: Move some flags to a bitfield. Change all callers. + + * options.h, import.c (parse_import_options, + clean_sigs_from_all_uids, import_one): Add import-minimal option. + Similar to export-minimal, except it works on the way in. + + * trustdb.h, trustdb.c (clean_sigs_from_uid): Add flag to remove + all non-selfsigs from key during cleaning. Change all callers. + + * export.c (do_export_stream): Use it here for export-minimal so + we don't need additional minimize code in the export path. + +2005-11-06 David Shaw <dshaw@jabberwocky.com> + + * options.skel: Add a section for --encrypt-to. This is Debian + bug 336211 by Javier Fernández-Sanguino Peña. + +2005-11-05 David Shaw <dshaw@jabberwocky.com> + + * Makefile.am: Include @LIBUSB_CPPFLAGS@ in our CPPFLAGS. + Strictly speaking this should be only in gpg_CPPFLAGS, but then we + have to compile everything twice for gpg and gpgv. + + * apdu.c (open_pcsc_reader): Fix double free. + + * gpg.c (main) [__APPLE__]: Default the PCSC driver to the OS X + location. Suggested by Patty A. Hardy. + +2005-11-02 David Shaw <dshaw@jabberwocky.com> + + * trustdb.c (clean_sigs_from_uid): Include sigs from unavailable + keys in the sigs that are cleaned. Suggested by Dirk Traulsen and + many others. + +2005-11-01 David Shaw <dshaw@jabberwocky.com> + + * import.c (import_one): Do collapse_uids() before we do any + cleaning so keyserver mangled keys with doubled user IDs can be + properly cleaned - possibly sigs on the different user IDs cancel + each other out. + + * import.c (parse_import_options), export.c + (parse_export_options): List "xxx-clean" before the longer options + so we don't end up with a partial match on the longer options. + + * trustdb.c (clean_uids_from_key): Return proper number of cleaned + user IDs. Don't count user IDs as cleaned unless we actually + delete something. + +2005-10-27 David Shaw <dshaw@jabberwocky.com> + + * keyedit.c (menu_addrevoker), getkey.c (finish_lookup): Fix + problem with adding a cert-only designated revoker. Code was + looking for a key with sign ability, and not cert ability. Noted + by Timo Schulz. + +2005-10-27 Werner Koch <wk@g10code.com> + + * gpg.c [__CYGWIN__]: Set default driver to winscard.dll. + + * apdu.c, apdu.h: Updated from gnupg 1.9. Changes are: + * apdu.c [__CYGWIN__]: Make cygwin environment similar to _WIN32. + Suggested by John P. Clizbe. + * apdu.h (SW_HOST_NO_KEYPAD): New. + * 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. + +2005-10-26 David Shaw <dshaw@jabberwocky.com> + + * keygen.c (proc_parameter_file): Default key and subkey usage + flags to algo capabilities if parameter file doesn't specify them. + Noted by Timo Schulz. + +2005-10-18 Werner Koch <wk@g10code.com> + + * cardglue.c (pin_cb): Fixed prompt for repeated PIN. Return + G10ERR_CANCELED and not just -1. + (status_sc_op_failure): New. Use it where we issue that status. + (pin_cb): Append serial number to the need-pin status message. + (agent_scd_change_pin): Add arg SERIALNO. Changed all callers. + (agent_scd_writekey): Ditto. + (agent_scd_setattr): Ditto. + (agent_scd_genkey): Ditto. + (agent_scd_checkpin): Pass serialno to the pin_cb. + + * keygen.c (parse_expire_string): Allow setting the expire + interval using a "seconds=<n>" syntax. This is useful for + debugging. + +2005-10-17 Werner Koch <wk@g10code.com> + + * export.c (do_export_stream): Factored some code out to ... + (skip_subkey_p): .. new. + (subkey_in_list_p, release_subkey_list): New. + (new_subkey_list_item): New. + (do_export_stream): Export exactly specified subkeys into one + keyblock. + +2005-10-13 David Shaw <dshaw@jabberwocky.com> + + * keyedit.c (keyedit_menu, menu_backsign): New "backsign" command + to add 0x19 backsigs to old keys that don't have them. + + * misc.c (parse_options): Fix build warning. + + * main.h, keygen.c (make_backsig): Make public. + +2005-10-12 David Shaw <dshaw@jabberwocky.com> + + * options.h, getkey.c (merge_selfsigs_subkey), gpg.c (main), + sig-check.c (signature_check2): Add --require-backsigs and + --no-require-backsigs. Currently defaults to + --no-require-backsigs. + +2005-10-11 David Shaw <dshaw@jabberwocky.com> + + * getkey.c (merge_selfsigs_subkey), sig-check.c + (signature_check2), keygen.c (make_backsig): Did some backsig + interop testing with the PGP folks. All is well, so I'm turning + generation of backsigs on for new keys. Checking for backsigs on + verification is still off. + +2005-10-05 Werner Koch <wk@g10code.com> + + * g10.c: Renamed to .. + * gpg.c: ..this. + * Makefile.am: Adjusted accordingly. + +2005-09-22 Werner Koch <wk@g10code.com> + + * sign.c (write_plaintext_packet): Don't print an empty file + warning if the file is actually too large. + * encode.c (encode_simple,encode_crypt): Ditto. + * progress.c (handle_progress): Adjusted for iobuf_get_filelength + change. + * photoid.c (generate_photo_id): Ditto. + +2005-09-20 Werner Koch <wk@g10code.com> + + * mainproc.c (proc_symkey_enc): Take care of a canceled passphrase + prompt. + +2005-09-19 David Shaw <dshaw@jabberwocky.com> + + * keylist.c (reorder_keyblock, do_reorder_keyblock): Reorder + attribute IDs as well as regular text IDs. + + * plaintext.c (ask_for_detached_datafile): Use make_filename() on + filename so tilde expansion works. + +2005-09-14 David Shaw <dshaw@jabberwocky.com> + + * main.h, misc.c (parse_options): Add the ability to have help + strings in xxx-options commands. + + * keyserver.c (keyserver_opts), import.c (parse_import_options), + export.c (parse_export_options), g10.c (parse_list_options, main): + Add help strings to xxx-options. + +2005-09-10 David Shaw <dshaw@jabberwocky.com> + + * keyedit.c (show_names): Moved name display code out from + show_key_with_all_names. + (keyedit_menu): Call it here for pref and showpref so they can + show only the selected user ID. Suggested by Timo Schulz. + +2005-09-07 Werner Koch <wk@g10code.com> + + * cardglue.h (GPG_ERR_TOO_LARGE): New. + + * apdu.c, apdu.h, iso7816.c, iso7816.h + * ccid-driver.c, ccid-driver.h: Updated from GnuPG 1.9 source. + Changes are: + * iso7816.c (iso7816_select_path): New. + * 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. + + * misc.c (parse_options): Allow meta option "help" to list all + options and to exit the program. + +2005-09-02 David Shaw <dshaw@jabberwocky.com> + + * parse-packet.c (enum_sig_subpkt, parse_signature, + parse_attribute_subpkts): Make a number of warnings verbose items. + These fire on many slightly mangled keys in the field, so the + warning is becoming burdensome. + +2005-09-01 David Shaw <dshaw@jabberwocky.com> + + * photoid.h, photoid.c (generate_photo_id): Allow passing in a + suggested filename. + + * keyedit.c (keyedit_menu, menu_adduid): Call it here so "addphoto + filename" works. + +2005-08-31 David Shaw <dshaw@jabberwocky.com> + + * photoid.c (generate_photo_id): Enable readline completion and + tilde expansion for the JPEG prompt. + +2005-08-30 Werner Koch <wk@g10code.com> + + * passphrase.c (agent_open): Print a warning and not an error in + case of a missing agent. Should fix Debian bug #325578. + +2005-08-26 David Shaw <dshaw@jabberwocky.com> + + * misc.c (openpgp_pk_algo_usage): Default to allowing CERT for + signing algorithms. + + * keyedit.c (sign_uids): Don't request a signing key to make a + certification. + + * keygen.c (do_add_key_flags): Force the certify flag on for all + primary keys, as the spec requires primary keys must be able to + certify (if nothing else, which key is going to issue the user ID + signature?) + (print_key_flags): Show certify flag. + (ask_key_flags, ask_algo): Don't allow setting the C flag for + subkeys. + + * keyid.c (usagestr_from_pk), getkey.c (parse_key_usage): + Distinguish between a sign/certify key and a certify-only key. + + * keyedit.c (ask_revoke_sig): Add a revsig --with-colons mode. + Suggested by Michael Schierl. + +2005-08-21 David Shaw <dshaw@jabberwocky.com> + + * Makefile.am: No need to link with curl any longer. + + * main.h, misc.c (path_access): New. Same as access() but does a + PATH search like execlp. + + * keyserver.c (curl_can_handle): Removed. Replaced by... + (curl_cant_handle): We are now relying on curl as the handler of + last resort. This is necessary because PGP LDAP and curl LDAP are + apples and oranges. + (keyserver_typemap): Only test for ldap and ldaps. + (keyserver_spawn): If a given handler is unusable (as determined + by path_access()) then try gpgkeys_curl. + + * exec.h, exec.c (make_tempdir, expand_args, exec_write, + exec_read): Minor cleanup to use bitfield flags instead of a bunch + of integers. + +2005-08-20 David Shaw <dshaw@jabberwocky.com> + + * g10.c (main): Add aliases sign-with->local-user and + user->recipient to make switching from PGP command line to GPG + easier. + +2005-08-19 David Shaw <dshaw@jabberwocky.com> + + * options.skel: Remove the surfnet LDAP keyserver from the list of + samples since it is being shut down. + + * getkey.c (classify_user_id): Disable the '.' and '+' search + modes since they aren't supported yet. + +2005-08-05 David Shaw <dshaw@jabberwocky.com> + + * g10.c (main), passphrase.c (set_passphrase_from_string): New + --passphrase command line option. Only useful in very special + circumstances. + +2005-08-05 Werner Koch <wk@g10code.com> + + * gpgv.c (keyserver_import_fprint): New stub. + + * keygen.c (ask_user_id): Moved email checking code out to .. + * misc.c (is_valid_mailbox): .. new. + * mainproc.c (get_pka_address): Use it here. + * getkey.c (get_pubkey_byname): Add falback to auto-retrieve a key + via the PKA mechanism. + + * options.h (KEYSERVER_AUTO_PKA_RETRIEVE): New. + * keyserver.c (keyserver_opts): Ditto. + * mainproc.c (check_sig_and_print): Use it here to retrieve keys + from a PKA DNS record. + + * pkclist.c (build_pk_list): Add comments to this function; + re-indented it. + +2005-08-04 David Shaw <dshaw@jabberwocky.com> + + * keygen.c (proc_parameter_file): Sanity check items in keygen + batch file. Noted by Michael Schierl. + + * pkclist.c (do_edit_ownertrust): Don't allow ownertrust level 0. + Noted by Michael Schierl. + + * keygen.c (write_keyblock): Don't try and build deleted kbnodes + since we start our tree with one. + +2005-08-04 Werner Koch <wk@g10code.com> + + * export.c (do_export_stream): Skip on-card keys when only subkeys + are to be exported. It does not make sense to replace the on-card + key stub by a no-key stub. + + * revoke.c (gen_revoke): Check for non-online keys. + + * seckey-cert.c (is_secret_key_protected): Return -3 for + non-online key stubs. The old code assumes that a protection + algorithm is still set but in some cases this one is 0 and thus it + won't be possible to decide whether it is unprotected or + protected. + +2005-07-28 Werner Koch <wk@g10code.com> + + * Makefile.am (other_libs): Add SRVLIBS. + + * parse-packet.c (can_handle_critical_notation): We know about + pka-address@gnupg.org. + * packet.h (PKT_signature): New fields PKA_INFO and PKA_TRIED. + (pka_info_t): New. + * free-packet.c (cp_pka_info): New. + (free_seckey_enc, copy_signature): Support new fields. + * mainproc.c (get_pka_address, pka_uri_from_sig): New. + (check_sig_and_print): Try to get the keyserver from the PKA + record. + * pkclist.c (check_signatures_trust): Adjust the trust based on + the PKA. + * gpgv.c (parse_keyserver_uri): New stub. + + * keygen.c (has_invalid_email_chars): Moved to .. + * misc.c (has_invalid_email_chars): .. here and made global. + +2005-07-27 Werner Koch <wk@g10code.com> + + * export.c (do_export_stream): Make two strings translatable. + +2005-07-26 David Shaw <dshaw@jabberwocky.com> + + * keyserver.c (keyserver_typemap): Special-case LDAP since curl + will report that it can handle it, and we don't want it to. + +2005-07-26 Werner Koch <wk@g10code.com> + + * passphrase.c (agent_get_passphrase): Make sure to release the + saved codeset. + (agent_open): Add arg ORIG_CODESET and switch back to it in case + of error. Changed all callers. + +2005-07-22 David Shaw <dshaw@jabberwocky.com> + + * keyedit.c (sign_uids): Don't prompt for setting signature expiry + to match key expiry unless --ask-cert-expire is set. Suggested by + Peter Palfrader. + +2005-07-22 Werner Koch <wk@g10code.com> + + * g10.c, options.h: New option --exit-on-status-write-error. + * status.c (write_status_text): Make use of this option. + +2005-07-22 David Shaw <dshaw@jabberwocky.com> + + * options.h, g10.c (main): Removed option --no-interactive-selection. + * keyedit.c (keyedit_menu): Use --interactive to enable the uid + walking when signing a key with no uids specified to sign. + + * keylist.c (list_keyblock_print): Fix silly typo. Noted by Greg + Sabino Mullane. + +2005-07-20 Werner Koch <wk@g10code.com> + + * openfile.c (open_outfile): Disable FD caching for created files. + * encode.c (encode_simple, encode_crypt): Disable FD caching for + input files. + * verify.c (verify_one_file): Ditto. + * decrypt.c (decrypt_messages): Ditto. This is bug #479. + + * misc.c (get_libexecdir) [W32]: Changed to return the value of + program used to create the process. + * keyserver.c (keyserver_spawn) [DISABLE_KEYSERVER_PATH]: Don't + change the exec-path at all. + +2005-07-20 David Shaw <dshaw@jabberwocky.com> + + * keyserver.c (curl_can_handle): New. Do a runtime check against + libcurl to see if it can handle a particular protocol. + (keyserver_typemap): Call it here. + + * Makefile.am: Pull in libcurl for curl_version_info() if used. + +2005-07-19 Werner Koch <wk@g10code.com> + + * g10.c, options.h: New option --limit-card-insert-tries. + * cardglue.c (open_card): Use it. + + * export.c (parse_export_options): New option + export-reset-subkey-passwd. + (do_export_stream): Implement it. + + * misc.c (get_libexecdir): New. + * keyserver.c (keyserver_spawn): Use it + +2005-07-18 Werner Koch <wk@g10code.com> + + * tdbio.c (open_db): Check for EROFS. Suggested by Bryce Nichols. + +2005-07-08 David Shaw <dshaw@jabberwocky.com> + + * trustdb.c (clean_uids_from_key): Don't keep a valid selfsig + around when compacting a uid. There is no reason to make an + attacker's job easier - this way they only have a revocation which + is useless in bringing the uid back. + + * keydb.h, kbnode.c (undelete_kbnode): Removed. No longer needed. + + * import.c (chk_self_sigs): Allow a uid revocation to be enough to + allow importing a particular uid (no self sig needed). This + allows importing compacted uids. + +2005-06-20 David Shaw <dshaw@jabberwocky.com> + + * keygen.c (save_unprotected_key_to_card): Better fix for gcc4 + warning. + +2005-06-20 Werner Koch <wk@g10code.com> + + * g10.c, options.h: New option --no-interactive-selection. + * keyedit.c (keyedit_menu): Use it. + +2005-06-18 Werner Koch <wk@g10code.com> + + * parse-packet.c (parse_signature): Use log_info for messages + about missing timestamp or keyid. In case we don't use that key + there won't be no further error and thus gpg does not need to + return with an error. + +2005-06-13 David Shaw <dshaw@jabberwocky.com> + + * keygen.c (save_unprotected_key_to_card): Fix gcc4 warning. + + * options.h, import.c (parse_import_options, import_one): Add + import-clean-uids option to automatically compact unusable uids + when importing. Like import-clean-sigs, this may nodify the local + keyring. + + * trustdb.c (clean_uids_from_key): Only allow selfsigs to be a + candidate for re-inclusion. + +2005-06-12 David Shaw <dshaw@jabberwocky.com> + + * options.h, import.c (parse_import_options, + clean_sigs_from_all_uids, import_one): Add import-clean-sigs + option to automatically clean a key when importing. Note that + when importing a key that is already on the local keyring, the + clean applies to the merged key - i.e. existing superceded or + invalid signatures are removed. + + * getkey.c (merge_selfsigs_main, merge_selfsigs_subkey): Make sure + that even after keys may be merged together, we only have one + chosen selfsig. + +2005-06-09 David Shaw <dshaw@jabberwocky.com> + + * options.h, import.c (parse_import_options, delete_inv_parts): + import-unusable-sigs is now a noop. + + * options.h, export.c (do_export_stream), keyedit.c (keyedit_menu, + menu_clean_subkeys_from_key), trustdb.h, trustdb.c + (clean_subkeys_from_key): Remove subkey cleaning function. It is + of very limited usefulness since it cannot be used on any subkey + that can sign, and can only affect multiple selfsigs on + encryption-only subkeys. + + * keydb.h, kbnode.c (undelete_kbnode): New function to undelete a + kbnode. + + * trustdb.c (clean_uids_from_key): Further tweak the algorithm so + that the last good selfsig is kept when the chosen selfsig is a + revocation. + +2005-06-08 David Shaw <dshaw@jabberwocky.com> + + * trustdb.c (clean_uids_from_key), keyedit.c + (menu_clean_uids_from_key): Tweak algorithm to preserve the last + selfsig which helps prevent uid resurrections. + + * getkey.c (fixup_uidnode, merge_selfsigs_main): Handle both + expired and revoked uids in fixup_uidnode(). No need to special + case in merge_selfsigs_main(). This also means that an expired + uid will have its selfsig tagged with chosen_selfsig. + +2005-06-07 David Shaw <dshaw@jabberwocky.com> + + * options.h, g10.c (main), export.c (parse_export_options, + do_export_stream): Add export-options export-clean-sigs, + export-clean-uids, export-clean-subkeys, and export-clean which is + all of the above. Export-minimal is the same except it also + removes all non-selfsigs. export-unusable-sigs is now a noop. + +2005-06-06 Werner Koch <wk@g10code.com> + + * cardglue.c (open_card): Emit new CARDCTRL status 5 for no reader + available. + +2005-06-02 Werner Koch <wk@g10code.com> + + * app-openpgp.c (do_writekey): Typo fix. + + * status.c, status.h: Removed STATUS_BAD_PASSPHRASE_PIN. + +2005-06-01 David Shaw <dshaw@jabberwocky.com> + + * signal.c [HAVE_DOSISH_SYSTEM]: Fix unused function warnings on + mingw32. Noted by Joe Vender. + + * passphrase.c [_WIN32]: Remove unused variables. + +2005-05-31 David Shaw <dshaw@jabberwocky.com> + + * keyedit.c (menu_clean_uids_from_key, + menu_clean_subkeys_from_key), trustdb.c (clean_uids_from_key, + clean_subkeys_from_key): Fix mingw32 build warnings. Noted by Joe + Vender. + +2005-05-31 Werner Koch <wk@g10code.com> + + * keydb.h [!ENABLE_AGENT_SUPPORT]: Define dummy types. + + * cardglue.c (assuan_strerror, assuan_transact): Dummy functions + if not build with agent support. + + * armor.c (check_input): Don't bail out on invalid header lines + unless in struict rfc2440 mode. Suggested by Richard Patterson. + +2005-05-30 Werner Koch <wk@g10code.com> + + * tlv.c: Add hack to compile without gpg-error.h. + +2005-05-30 David Shaw <dshaw@jabberwocky.com> + + * trustdb.h, trustdb.c (clean_subkeys_from_key): New. Walk + through the subkeys on a key, and mark any that aren't usable for + deletion. Note that a signing subkey is never marked for deletion + since these keys are still useful after expiration or revocation. + + * keyedit.c (menu_clean_subkeys_from_key): New function to call + clean_subkeys_from_key() on a key. Note that the strings here are + not marked for translation yet. The UI is still in flux, and + there is no point in annoying the translators twice. + (keyedit_menu): Call it here as part of the "clean" command. + +2005-05-29 David Shaw <dshaw@jabberwocky.com> + + * trustdb.h, trustdb.c (clean_uids_from_key): New. Walk through + the user IDs on a key, and mark any that aren't valid for + deletion. + + * keyedit.c (menu_clean_uids_from_key): New function to call + clean_uids_from_key() on a key. + (keyedit_menu): Call it from here as part of the "clean" command. + +2005-05-26 David Shaw <dshaw@jabberwocky.com> + + * g10.c (main): Default {export|import}-unusable-sigs to off until + the "clean" UI can be finished. + +2005-05-24 Werner Koch <wk@g10code.com> + + * passphrase.c (ask_passphrase): Unescape the description string. + * cardglue.c (unescape_status_string): Removed. Changed all + caller to use ... + * misc.c (unescape_percent_string): New. + + * g10.c (add_notation_data): Check number of at-signs. + +2005-05-23 Werner Koch <wk@g10code.com> + + * app-openpgp.c, app-common.h: Again updated from gnupg 1.9 CVS. + + * cardglue.c (open_card): Check USE_AGENT. + (agent_scd_checkpin): Implemented Assuan part. + (agent_scd_change_pin): Ditto. + + * g10.c (main): Option --debug-ccid-driver may now be given + several times increase the debug level. + + * ccid-driver.c (parse_ccid_descriptor): Mark SCR335 FW version + 5.14 as 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> + + * cardglue.c (send_status_info): Make CTRL optional. + (agent_scd_writekey, inq_writekey_parms): New. + (agent_openpgp_storekey): Removed. + * cardglue.h: Add a few more error code mappings. + * keygen.c (copy_mpi): Removed. + (save_unprotected_key_to_card): Changed to use agent_scd_writekey. + * app-common.h, app-openpgp.c, tlv.c, tlv.h: Updated from newer + version in gnupg 1.9 CVS. + +2005-05-20 Werner Koch <wk@g10code.com> + + * ccid-driver.c (ccid_transceive): Arghhh. The seqno is another + bit in the R-block than in the I block, this was wrong at one + place. Fixes bug #419 and hopefully several others. + +2005-05-19 Werner Koch <wk@g10code.com> + + * app-common.h, app-openpgp.c, tlv.c, tlv.h: Updated from newer + version in gnupg 1.9 CVS. + +2005-05-18 Werner Koch <wk@g10code.com> + + * passphrase.c (agent_open): Made global and add arg TRY. + (agent_close): Made global. + + * app-common.h (app_t): Add a field to store the Assuan context. + +2005-05-13 David Shaw <dshaw@jabberwocky.com> + + * build-packet.c (do_comment): Removed. + (build_packet): Ignore comment packets. + + * export.c (do_export_stream): Don't export comment packets any + longer. + + * options.h, g10.c (main): Remove --sk-comments and + --no-sk-comments options, and replace with no-op. + +2005-05-11 David Shaw <dshaw@jabberwocky.com> + + * keygen.c (write_selfsigs): Rename from write_selfsig. Write the + same selfsig into both the pk and sk, so that someone importing + their sk (which will get an autoconvert to the pk) won't end up + with two selfsigs. + (do_generate_keypair): Call it from here. + + * parse-packet.c (can_handle_critical_notation): New. Check for + particular notation tags that we will accept when critical. + Currently, that's only preferred-email-encoding@pgp.com, since we + know how to handle it (pass it through to a mail program). + (can_handle_critical): Call it from here. + (parse_one_sig_subpkt): Sanity check that notations are + well-formed in that the internal lengths add up to the size of the + subpacket. + +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-05-06 David Shaw <dshaw@jabberwocky.com> + + * main.h, keygen.c (parse_expire_string, ask_expire_interval), + sign.c (sign_file, clearsign_file, sign_symencrypt_file), g10.c + (main), keyedit.c (sign_uids): Use seconds rather than days + internally to calculate expiration. We no longer need the + day-based code as we don't generate v3 keys. + + * sign.c (sign_file, clearsign_file, sign_symencrypt_file): Use + the default sig expire value when signing in batchmode. + +2005-05-05 David Shaw <dshaw@jabberwocky.com> + + * Makefile.am, packet.h, main.h, comment.c: Remove comment.c. We + don't use any of these functions any longer. + + * keygen.c (start_tree): New function to "prime" a KBNODE list. + (do_generate_keypair): Use it here rather than creating and + deleting a comment packet. + + * keygen.c (gen_elg, gen_dsa): Do not put public factors in secret + key as a comment. + + * options.h, encode.c (encode_simple, encode_crypt), keygen.c + (do_create): Remove disabled comment packet code. + + * keygen.c (keygen_set_std_prefs): Add SHA256 and BZip2 to default + preferences. + + * options.h, g10.c (main): Add new --default-sig-expire and + --default-cert-expire options. Suggested by Florian Weimer. + + * main.h, keygen.c (parse_expire_string, ask_expire_interval): Use + defaults passed in, or "0" to control what default expiration is. + + * keyedit.c (sign_uids), sign.c (sign_file, clearsign_file, + sign_symencrypt_file): Call them here, so that default expiration + is used when --ask-xxxxx-expire is off. + +2005-05-03 Werner Koch <wk@g10code.com> + + * passphrase.c (agent_get_passphrase): Add new arg CACHEID. + Changed all callers. + (ask_passphrase): Add new arg CACHEID and use it in agent mode. + Changed all callers. + (passphrase_clear_cache): New arg CACHEID. Changed all callers. + * cardglue.c (format_cacheid): New. + (pin_cb): Compute a cache ID. + (agent_scd_pksign, agent_scd_pkdecrypt): Use it. + (agent_clear_pin_cache): New. + * card-util.c (change_pin): Clear the PIN cache. + (check_pin_for_key_operation): Ditto. + +2005-04-24 David Shaw <dshaw@jabberwocky.com> + + * trustdb.h, trustdb.c (mark_usable_uid_certs): Add flags for the + no-pubkey and chosen revocation cases. + (clean_uid): New function to clean a user ID of unusable (as + defined by mark_usable_uid_certs) certs. + + * keyedit.c (keyedit_menu, menu_clean_uids): Call it here for new + "clean" command that removes unusable sigs from a key. + + * trustdb.h, keyedit.c (keyedit_menu, menu_select_uid_namehash): + Allow specifying user ID via the namehash from --with-colons + --fixed-list-mode --list-keys. Suggested by Peter Palfrader. + +2005-04-21 David Shaw <dshaw@jabberwocky.com> + + * keyedit.c (sign_uids, keyedit_menu): When the user requests to + sign a key without specifying which user IDs to sign, and declines + to sign all user IDs, walk through the set of user IDs and prompt + for which to sign. + + * mainproc.c (symkey_decrypt_seskey): There is no need to have an + extra check for a bad passphrase and/or unknown cipher algorithm + here. We'll fail quite happily later, and usually with a better + error message to boot. + +2005-04-20 Werner Koch <wk@g10code.com> + + * sign.c (sign_file, sign_symencrypt_file): Allow for hash + debugging. + +2005-04-16 David Shaw <dshaw@jabberwocky.com> + + * keyserver.c (keyserver_spawn): Free some memory. + + * sign.c (hash_for): Comments. + +2005-04-11 Werner Koch <wk@g10code.com> + + * g10.c (main, add_notation_data, add_policy_url) + (add_keyserver_url): Use isascii() to protect the isfoo macros and + to replace direct tests. Possible problems noted by Christian + Biere. + * keyserver.c (parse_keyserver_uri): Ditto. + +2005-04-07 Werner Koch <wk@g10code.com> + + * g10.c (main): Declare --pipemode deprecated. + * misc.c (deprecated_command): New. + + * ccid-driver.c (ccid_slot_status): Fixed debug messages. + + * card-util.c (card_edit): Add command "verify". Enhanced admin + command to allow optional arguments "on", "off" and "verify". + (card_status): Print private DOs in colon mode. + * app-openpgp.c (do_check_pin): Add hack to allow verification of + CHV3. + +2005-04-01 Werner Koch <wk@g10code.com> + + * keygen.c (keygen_set_std_prefs): Explain the chosen order of + AES key sizes. + +2005-04-01 David Shaw <dshaw@jabberwocky.com> + + * mainproc.c (proc_plaintext): Properly handle SIG+LITERAL + (old-style PGP) signatures that use hashes other than SHA-1, + RIPEMD160, or MD5. + +2005-03-31 David Shaw <dshaw@jabberwocky.com> + + * exec.h, exec.c (set_exec_path): Remove some dead code and change + all callers. We no longer need to append to $PATH. + +2005-03-31 Werner Koch <wk@g10code.com> + + * passphrase.c (agent_open): Dropped support for W32 - is was + never actually used. Removed support for the old non-assuan + protocol; there has never been a matured implementation and + gpg-agent is now arround for quite some time. Rewritten to make + use of the Assuan code from ../util. + (gpga_protocol_codes): Removed. + (readn): Removed. + (agent_close): Simplified for use with Assuan. + (agent_get_passphrase, passphrase_clear_cache): Removed support + for old protocol. Use only with ENABLE_CARD_SUPPORT defined. + (agent_send_all_options): Take assuan context instead of a file + descriptor. + (agent_send_option): Likewise. Use assuan_transact. + * passphrase.c (writen, readaline): Removed. + + * g10.c (main): Print a warning if --use-agent has been used but + it has not been build with support for it. + + * keydb.c (keydb_add_resource): Clarify meaning of flags. Add new + flag 4. Use log_info for errors registering the default secret key. + * g10.c (main): Flag the default keyrings. + +2005-03-30 David Shaw <dshaw@jabberwocky.com> + + * keyserver.c (keyserver_spawn): Don't mess about with the $PATH. + Rather, call keyserver helpers with the full path. This fixes + some PATH-inspired DLL problems on W32. Noted by Carlo Luciano + Bianco. + +2005-03-30 Werner Koch <wk@g10code.com> + + * cardglue.c (pin_cb): Print a warning if the info string hack is + not there. This may happen due to typos in the translation. + +2005-03-22 Werner Koch <wk@g10code.com> + + * misc.c (w32_shgetfolderpath) [W32]: Changed declaration of + function ptr. Noted by Tim Costello. + * apdu.c [W32]: Changed declaration of dlopened function pointers. + +2005-03-21 David Shaw <dshaw@jabberwocky.com> + + * gpgv.c: Stubs for tty_enable_completion() & + tty_disable_completion(). + + * openfile.c (ask_outfile_name): Enable readline completion when + prompting for an output filename. + + * plaintext.c (ask_for_detached_datafile): Enable readline + completion when prompting for a detached sig datafile. + +2005-03-21 Werner Koch <wk@g10code.com> + + * keyedit.c (command_generator, keyedit_completion): Changed + indentation. + * card-util.c (command_generator, card_edit_completion): Ditto. + +2005-03-19 David Shaw <dshaw@jabberwocky.com> + + * card-util.c (command_generator, card_edit_completion) + [GNUPG_MAJOR_VERSION==1 && HAVE_LIBREADLINE]: New functions to + enable command completion in the --card-edit menu. + (card_edit): Call them here. + +2005-03-18 David Shaw <dshaw@jabberwocky.com> + + * keyedit.c (command_generator, keyedit_completion) + [HAVE_LIBREADLINE]: New functions to enable command completion in + the --edit-key menu. + (keyedit_menu): Call them here. + +2005-03-17 David Shaw <dshaw@jabberwocky.com> + + * getkey.c (get_seckey_byname2): If no explicit default key is + set, don't pick a disabled default. Noted by David Crick. + + * Makefile.am: Calculate GNUPG_LIBEXECDIR directly. Do not + redefine $libexecdir. + + * options.h, keyserver.c (parse_keyserver_options) + (keyserver_spawn): Don't treat 'verbose' and 'include-disabled' as + special. Just pass them through silently to the keyserver helper. + +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-03-14 Werner Koch <wk@g10code.com> + + * card-util.c (card_status): Use isotimestamp and not the + localized asctimestamp to match the timezone used in the key + information. + + * cardglue.c (pin_cb): Disable debug output. + +2005-03-11 Werner Koch <wk@g10code.com> + + * keygen.c (gen_card_key_with_backup): Write status line with the + backup filename. + + * status.h, status.h (STATUS_BACKUP_KEY_CREATED): New. + +2005-03-10 David Shaw <dshaw@jabberwocky.com> + + * keyserver.c (parse_keyserver_options): Accept honor-http-proxy + as an alias for http-proxy. + + * delkey.c (do_delete_key, delete_keys): Fix problem with --expert + preventing --delete-secret-and-public-keys from deleting secret + keys. + +2005-03-10 Werner Koch <wk@g10code.com> + + * keyedit.c (keyedit_menu) [W32]: Run the trustdb stale check + earlier. + +2005-03-07 Werner Koch <wk@g10code.com> + + * cardglue.c (agent_scd_pkdecrypt, agent_scd_pksign) + (agent_scd_genkey, agent_scd_setattr, agent_scd_change_pin) + (agent_scd_checkpin, agent_openpgp_storekey): Make sure to send a + SC_OP_FAILURE after card operations which might change data. + * card-util.c (change_pin): Send a SC_OP_SUCCESS after a PIN has + been changed. + (change_name): Removed a debug output. + * status.h, status.c: New codes BAD_PASSPHRASE_PIN, SC_OP_FAILURE + and SC_OP_SUCCESS. + +2005-02-24 David Shaw <dshaw@jabberwocky.com> + + * keyedit.c (keyedit_menu): Only print the key signing hint when + signing from a place where it is useful (i.e. --edit-key and not + --sign-key). + +2005-02-16 Werner Koch <wk@g10code.com> + + * card-util.c (fetch_url): Fetch the key from the default + keyserver if no URL is available. + +2005-02-15 Werner Koch <wk@g10code.com> + + * passphrase.c (agent_get_passphrase): Don't call free_public_key + if PK is NULL. + (passphrase_clear_cache): Ditto. Removed debug output. + (passphrase_to_dek): Ditto. + +2005-02-13 Werner Koch <wk@g10code.com> + + * keyedit.c (cmds): Limit code to 80 columns. Add command + BKUPTOCARD. + +2005-02-09 David Shaw <dshaw@jabberwocky.com> + + * encr-data.c (decrypt_data): Use it here to turn off the "quick + check" bytes for PK decryptions. This is in regards to the Mister + and Zuccherato attack on OpenPGP CFB mode. + + * mainproc.c (proc_symkey_enc): Set a flag to indicate that a + particular session key came from a passphrase and not a PK. + +2005-02-08 Werner Koch <wk@g10code.com> + + * misc.c (w32_shgetfolderpath): New. + (default_homedir): Use it to avoid problems under Windows95. + +2005-02-06 David Shaw <dshaw@jabberwocky.com> + + * trustdb.h, trustdb.c (trustdb_check_or_update): New. If the + trustdb is dirty and --interactive is set, do an --update-trustdb. + If not interactive, do a --check_trustdb unless + --no-auto-check-trustdb is set. + + * import.c (import_keys_internal): Moved from here. + + * keyserver.c (keyserver_refresh): Call it here after all + refreshing has happened so that we don't rebuild after each + preferred keyserver set of imports, but do one big rebuild at the + end. This is Debian bug #293816, noted by Kurt Roeckx. + +2005-02-04 David Shaw <dshaw@jabberwocky.com> + + * getkey.c (merge_selfsigs_subkey): Merged away definition from + the backsigs code. + +2005-01-31 David Shaw <dshaw@jabberwocky.com> + + * keygen.c (do_generate_keypair): Write the auth key to the card + before the encryption key. This is a partial workaround for a PGP + bug (as of this writing, all versions including 8.1), that causes + it to try and encrypt to the most recent subkey regardless of + whether that subkey is actually an encryption type. In this case, + the auth key is an RSA key so it succeeds. + +2005-01-27 David Shaw <dshaw@jabberwocky.com> + + * keyid.c (keyid_from_sk, keyid_from_pk): Use 0xFFFFFFFFFFFFFFFF + instead of 0x0000000000000000 for the invalid key ID since + all-zeroes is reserved for the anonymous recipient. + + * keyedit.c (change_passphrase), keygen.c (generate_subkeypair): + Fix a string ;) + +2005-01-27 Werner Koch <wk@g10code.com> + + * parse-packet.c (listfp): New. + (set_packet_list_mode): Intialize it to stdout or stderr depending + on a global option. Made all printing in list mode use LISTFP. + + * keygen.c (generate_subkeypair): Detect primary key on-card and + ask for the passphrase. Return an error if the primary key is a + plain stub. + + * keyedit.c (change_passphrase): Don't ever change any stub key. + Print a note if a key consists of only stub keys. Reported by + Dany Nativel. These are bugs #401 and #402. + +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-26 David Shaw <dshaw@jabberwocky.com> + + * keyserver.c (parse_keyserver_uri): Allow RFC-2732 IPv6 [literal + address] syntax in keyserver URLs. + (keyserver_typemap): Map ftps if we are supporting it. + +2005-01-25 Werner Koch <wk@g10code.com> + + * keygen.c (do_generate_keypair): Don't continue after an error; + fixed at two places. Why at all didn't I used a goto to cleanup, + tsss? + + * 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 only 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 + chaced fingerprint will get updated to the old value and later + when signing the generated key the checking of the fingerprint + fails becuase 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. + +2005-01-21 David Shaw <dshaw@jabberwocky.com> + + * keyserver.c (free_keyserver_spec): Fix small leak. + (keyserver_typemap): Map https if we are supporting it. + +2005-01-20 Werner Koch <wk@g10code.com> + + * cardglue.c (open_card): Issue new CARDCTRL(4) status. + + * gpgv.c (tty_fprintf): New stub. + + * card-util.c (card_status): Create a secret key stub on the fly + and print more information about a card key. + * import.c (pub_to_sec_keyblock, auto_create_card_key_stub): New. + * getkey.c (get_seckeyblock_byfprint): New. + * keylist.c (print_card_key_info): New. + + * g10.c (i18n_init) [W32]: Pass registry key to gettext + initialization. + * gpgv.c (i18n_init) [W32]: Ditto. + +2005-01-18 Werner Koch <wk@g10code.com> + + * misc.c (default_homedir): New. Taken from gnupg 1.9.15. + * g10.c (main): Use it. + * gpgv.c (main): Ditto. + + * keylist.c (public_key_list): Do a trustdb staleness check before + opening the keyring. + (secret_key_list): Ditto. + +2005-01-10 David Shaw <dshaw@jabberwocky.com> + + * keyedit.c (keyedit_menu): Move command strings outside the + function to get ready for the readline completion code. + + * passphrase.c (readline, agent_send_option, agent_open, + agent_get_passphrase, passphrase_clear_cache): Rename readline() + to readaline() to keep readline library namespace clear. + +2005-01-06 David Shaw <dshaw@jabberwocky.com> + + * filter.h, armor.c (armor_filter): Use the eol string from the + armor filter context instead of hardcoding '\n' or '\r\n'. If no + eol string is provided, default to '\n' or '\r\n' as appropriate. + (is_armor_header): Trim tabs in armor header lines as well. + + * keyserver.c (keyserver_spawn): Use it here to force '\n' line + endings since the keyserver output file gets a LF->CRLF expansion + on win32. + +2005-01-05 David Shaw <dshaw@jabberwocky.com> + + * g10.c (main): Typo. + + * armor.c (is_armor_header): Allow CR and LF (not just actual + spaces) in an armor header line (-----BEGIN etc). This is needed + due to CRLF issues on win32. As before, --openpgp makes it + strict. + +2005-01-03 David Shaw <dshaw@jabberwocky.com> + + * Makefile.am: Use @LIBUSB@ instead of @LIBUSB_LIBS@ + + * import.c (delete_inv_parts): Comments on import-unusable-sigs. + +2005-01-01 David Shaw <dshaw@jabberwocky.com> + + * options.h, import.c (parse_import_options, delete_inv_parts): + Add import-unusable-sigs flag to enable importing unusable + (currently: expired) sigs. + + * options.h, export.c (parse_export_options, do_export_stream): + Add export-unusable-sigs flag to enable exporting unusable + (currently: expired) sigs. + +2004-12-29 David Shaw <dshaw@jabberwocky.com> + + * packet.h, getkey.c (merge_selfsigs_main, sig_to_revoke_info), + keyid.c (revokestr_from_pk), keyedit.c (show_key_with_all_names): + Show who revoked a key (either the same key or a designated + revoker) and when. + +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-24 David Shaw <dshaw@jabberwocky.com> + + * keyserver.c (keyserver_typemap): Only map HTTP and FTP if + libcurl has specifically been selected to handle them. + +2004-12-22 David Shaw <dshaw@jabberwocky.com> + + * options.h, keyserver.c (parse_keyserver_uri): Properly parse + auth data from URLs and pass to keyserver helpers. + + * keyserver.c (keyserver_typemap): New. Map certain keyserver + types to a common type (e.g. ldaps -> ldap). If we are building + with curl, map both http and ftp to curl. + + * build-packet.c (build_sig_subpkt): Only allow one preferred + keyserver subpacket at a time. + +2004-12-21 David Shaw <dshaw@jabberwocky.com> + + * keyedit.c (menu_set_keyserver_url): Make sure we only operate on + the chosen selfsig so we don't accidentally promote an older + selfsig to chosen. Discovered by Simon Josefsson and 'Todd'. + + * keygen.c (ask_expire_interval): Fix typo. + +2004-12-20 David Shaw <dshaw@jabberwocky.com> + + * keylist.c (list_keyblock_print): Secret key listings should + always show everything (expired UIDs, revoked subkeys, etc, etc). + + * keyedit.c (keyedit_menu): Add additional help for the "sign" + flags. + +2004-12-20 Werner Koch <wk@g10code.com> + + * keygen.c (ask_expire_interval): For better translations chnage 2 + strings. + + * seckey-cert.c (do_check): Handle case when checksum was okay but + passphrase still wrong. Roman Pavlik found such a case. + +2004-12-20 David Shaw <dshaw@jabberwocky.com> + + * keyedit.c (keyedit_menu): Invisible alias "passwd" as + "password". + + * passphrase.c: Don't check for __CYGWIN__, so it is treated as a + unix-like system. + + * options.h, g10.c (main), textfilter.c (standard): Use new option + --rfc2440-text to determine whether to filter "<space>\t\r\n" or + just "\r\n" before canonicalizing text line endings. Default to + "<space>\t\r\n". + +2004-12-19 David Shaw <dshaw@jabberwocky.com> + + * keygen.c (keygen_get_std_prefs): Set reference count when + creating the temporary user ID. + + * keyedit.c (keyedit_menu): Merge updpref and setpref. Keep + updpref as an invisible alias. Add invisible alias for revphoto. + Fix small memory leak when using "setpref" (not all of the uid was + freed). + (menu_revkey): Trigger a trust rebuild after revoking a key. + Don't allow revoking an already-revoked whole key. + (menu_revsubkey): Don't allow revoking an already-revoked subkey. + +2004-12-18 David Shaw <dshaw@jabberwocky.com> + + * keyedit.c (menu_revkey): Rename to menu_revsubkey. + (menu_revkey): New. Revoke a whole key. + (keyedit_menu): Call it here for when 'revkey' is used without any + subkeys selected. This is to be consistent with the other + functions which are "selected part if selected, whole key if not". + + * signal.c: Use only HAVE_LIBREADLINE to detect readline + availability. + + * Makefile.am: Link with readline where necessary. + +2004-12-17 Werner Koch <wk@g10code.com> + + * passphrase.c (agent_get_passphrase): Define NREAD locally as + size_t or int. + + * keylist.c (list_keyblock_print): Make field width an int. + * keyedit.c (show_key_with_all_names): Ditto. + +2004-12-16 David Shaw <dshaw@jabberwocky.com> + + * g10.c (main): Add --require-secmem/--no-require-secmem to cause + gpg to exit if it cannot lock memory. Also remove --nrsign-key + and --nrlsign-key since this can better be done via --edit-key. + +2004-12-15 David Shaw <dshaw@jabberwocky.com> + + * apdu.c (apdu_send_le, apdu_send_direct), keylist.c + (status_one_subpacket, print_one_subpacket): Fix some compiler + warnings. + + * g10.c (main): Fix --compression-algo to take a string argument + like --compress-algo. + + * trustdb.c (uid_trust_string_fixed): For safety, check for a pk. + +2004-12-14 David Shaw <dshaw@jabberwocky.com> + + * keyedit.c (keyedit_menu): Re-remove the N_() markers. + + * trustdb.c (uid_trust_string_fixed): Show uids as revoked if the + key is revoked. + + * keyedit.c (show_key_with_all_names): Don't show validity for + secret key UIDs. + + * keyedit.c (parse_sign_type): New. Figure out the flags (local, + nonrevoke, trust) for a signature. + (keyedit_menu): Call it here so we can mix and match flags, and + don't need "nrltsign", "ltsign", "tnrsign", etc, etc, etc. + +2004-12-14 Werner Koch <wk@g10code.com> + + * passphrase.c (agent_get_passphrase): Removed debug output + + * keyserver.c (keyserver_work, keyserver_spawn): Map ldaps to ldap. + + * keyedit.c (keyedit_menu): Removed the N_() markers from the + command names. + * card-util.c (card_edit): Ditto. + +2004-12-13 Werner Koch <wk@g10code.com> + + * passphrase.c (read_passphrase_from_fd): Fixed memory leak. + Noted by Andrei Darashenka. + +2004-12-11 David Shaw <dshaw@jabberwocky.com> + + * keyserver.c (parse_preferred_keyserver): Force preferred + keyserver subpackets to have a URI scheme specified. + +2004-12-10 David Shaw <dshaw@jabberwocky.com> + + * options.h, g10.c (main), textfilter.c (standard): Use --rfc2440 + or --openpgp directly to determine the end of line hashing rule. + + * trustdb.c (uid_trust_string_fixed): Show uids as expired if the + key is expired. + +2004-12-10 Werner Koch <wk@g10code.com> + + * app-openpgp.c (send_fprtime_if_not_null): New. + (do_getattr): Add KEY_TIME. + (do_learn_status): Print KEY_TIME. + * cardglue.c (learn_status_cb): Parse KEY-TIME. + * card-util.c (card_status): Print creation time if available. + +2004-12-09 David Shaw <dshaw@jabberwocky.com> + + * options.h, g10.c (main), textfilter.c (len_without_trailing_ws): + Removed (not used). + (standard): 2440 says that textmode hashes should canonicalize + line endings to CRLF and remove spaces and tabs. 2440bis-12 says + to just canonicalize to CRLF. So, we default to the 2440bis-12 + behavior, but revert to the strict 2440 behavior if the user + specifies --rfc2440. In practical terms this makes no difference + to any signatures in the real world except for a textmode detached + signature. + +2004-12-09 Werner Koch <wk@g10code.com> + + * passphrase.c (agent_get_passphrase): New args CUSTOM_PROMPT and + CUSTOM_DESCRIPTION. Changed all callers. + + * app-openpgp.c (do_getattr, do_learn_status, do_setattr): Support + the new private DOs. + (do_change_pin): Add a "N" prefix to the strings so that the + callback can act accordingly for a new PIN. Unfortunately this + breaks existing translations but I see no wother way to overvome + this. + + * cardglue.c (learn_status_cb): Ditto. + (agent_release_card_info): Ditto. + (struct pin_cb_info_s): Removed and changed all users. + (pin_cb): Reworked. + + * card-util.c (card_status): Print them + (card_edit): New command PRIVATEDO. + (change_private_do): New. + +2004-12-09 David Shaw <dshaw@jabberwocky.com> + + * keygen.c (ask_algo): Add a choose-your-own-capabilities option + for DSA. + +2004-12-07 David Shaw <dshaw@jabberwocky.com> + + * keygen.c (ask_keysize): Change strings to always use %u instead + of hardcoding key sizes. Bump default to 2048. Bump minimum down + to 512, where possible, but require --expert to get there. DSA is + always 1024 unless --expert is given. + +2004-11-29 David Shaw <dshaw@jabberwocky.com> + + * getkey.c (parse_key_usage): New function to parse out key usage + flags. Set PUBKEY_USAGE_UNKNOWN to handle flags that we don't + understand. + (fixup_uidnode, merge_selfsigs_main, merge_selfsigs_subkey): Call + it from here to remove duplicate code. + +2004-11-26 David Shaw <dshaw@jabberwocky.com> + + * export.c (do_export_stream): Allow export-minimal to work with + secret keys, even though a non-selfsig secret key signature is + rare. + + * options.h, export.c (parse_export_options, do_export_stream), + import.c (parse_import_options, import_keys_internal): Make the + import-options and export-options distinct since they can be mixed + together as part of keyserver-options. + +2004-11-24 David Shaw <dshaw@jabberwocky.com> + + * options.h, export.c (parse_export_options, do_export_stream): + Add "export-minimal" option to disregard any sigs except selfsigs. + + * trustdb.c (uid_trust_string_fixed): Use a string that can be + atoi-ed, but also has a comment for the translator. + + * trustdb.h, trustdb.c (uid_trust_string_fixed): New. Return a + fixed-size translatable string similar to trust_value_to_string. + This allows for easier lining up of displays. + + * keyedit.c (show_key_with_all_names), keylist.c + (list_keyblock_print): Use it here to print validity strings. + + * gpgv.c: Stub. + +2004-11-18 Werner Koch <wk@g10code.com> + + * g10.c (S_IRGRP) [HAVE_DOSISH_SYSTEM]: Define to 0. + +2004-11-17 Werner Koch <wk@g10code.com> + + * g10.c (open_info_file): New. + (main): Unconditionally implement --status-file, --logger-file, + --attribute-file, --passphrase-file, --command-file. This is not + generally useful but easy to support and might make scripting + under Windows easier. + +2004-11-11 Werner Koch <wk@g10code.com> + + * passphrase.c (readn): Fixed test against EINTR. + +2004-11-05 Werner Koch <wk@g10code.com> + + * app-openpgp.c: Made more strings translatable. + (verify_chv3, do_change_pin): Add a special prefix to the prompt + of the Admin PIN prompts. + * passphrase.c (ask_passphrase): Add arg TRYAGAIN_TEXT. Changed + call callers. + * cardglue.c (pin_cb): Make use of the OPAQUE arg to pass + arguments to the PIN callback. Use this to implement a way to + check for correct PIN repetition. Changed all callers to pass an + opaque argument. Improved detection of Admin PIN prompts. + +2004-11-04 David Shaw <dshaw@jabberwocky.com> + + * plaintext.c (handle_plaintext): Don't try and create a + zero-length filename when using --use-embedded-filename with input + that has no filename (clearsigned or message generated from a + pipe). + + * encode.c (encode_simple, encode_crypt), progress.c + (handle_progress), sign.c (write_plaintext_packet): Fix a few + inconsistent calls (NULL filename means a pipe here, so don't + bother to check it twice). + +2004-11-03 David Shaw <dshaw@jabberwocky.com> + + * misc.c (print_digest_algo_note): The latest 2440bis drafts + deprecates MD5, so give a warning. + (print_pubkey_algo_note, print_cipher_algo_note, + print_digest_algo_note): Give the algorithm name in the + experimental algo warning. + +2004-11-03 Timo Schulz <twoaday@g10code.com> + + * passphrase.c (readn, writen): Use w32_strerror instead + of just showing the error number. + * misc.c [_WIN32]: Fix warning about missing prototypes. + +2004-10-28 David Shaw <dshaw@jabberwocky.com> + + * skclist.c (build_sk_list): Don't need to warn about + PGP-generated Elgamal signing keys since we no longer support any + Elgamal signing keys. + + * sign.c (sign_file, clearsign_file): Use "writing to" instead of + "writing to file" to match other strings. + + * pkclist.c (check_signatures_trust): Fix typo. Noted by Moray + Allan. This is Debian bug #278708. + + * passphrase.c (ask_passphrase, passphrase_to_dek): "password" -> + "passphrase". + + * keyedit.c (show_key_with_all_names): Show designated revoker as + part of translatable string. + +2004-10-28 Werner Koch <wk@g10code.com> + + * Makefile.am (other_libs): New. Also include LIBICONV. Noted by + Tim Mooney. + +2004-10-28 Werner Koch <wk@g10code.com> + + * apdu.c (open_pcsc_reader): Removed bad free in error handler. + +2004-10-27 David Shaw <dshaw@jabberwocky.com> + + * card-util.c, delkey.c, keygen.c, plaintext.c, keyedit.c, + passphrase.c, revoke.c: Collapse the two different "can't do that + in batch mode" strings into one. + + * keylist.c (status_one_subpacket): New. Send the subpacket data + to the --status interface. + + * card-util.c (card_edit): Show when admin is enabled or not. + + * status.h, status.c: New STATUS_SIG_SUBPACKET type. + + * build-packet.c (build_sig_subpkt): Multiple keyserver URLs are + allowed. + + * keyring.c: Make some strings translatable. + + * exec.c, sign.c: Change "can't open file" to "can't open" and + "can't create file" to "can't create" to be consistent with other + strings so we don't have to translate both. + + * delkey.c, export.c, keyedit.c, pkclist.c, revoke.c, skclist.c: + Fix a few missed possible \"username\" quotes. + +2004-10-26 Werner Koch <wk@g10code.com> + + * app-openpgp.c (verify_chv3): The minimum length for CHV3 is + 8. Changed string to match the other ones. + + * passphrase.c (agent_send_all_options): Try to deduce the ttyname + from stdin. + +2004-10-22 Werner Koch <wk@g10code.com> + + * card-util.c (fetch_url): Disable for gnupg 1.9 + (card_generate_subkey): Ditto. + (card_store_subkey): Ditto. + +2004-10-21 David Shaw <dshaw@jabberwocky.com> + + * options.h, g10.c (main), mainproc.c (check_sig_and_print): + Rename verify-option show-validity to show-uid-validity to match + the similar list-option. + + * app-openpgp.c (verify_chv3): Fix typo. + +2004-10-21 Werner Koch <wk@g10code.com> + + * app-common.h (app_openpgp_storekey): Add prototype. + + * app-openpgp.c (do_sign): Replace asprintf by direct allocation. + This avoids problems with missing vasprintf implementations. + + * card-util.c (generate_card_keys): Add a #warning for gnupg 1.9 + and use the same string there. + +2004-10-20 David Shaw <dshaw@jabberwocky.com> + + * g10.c (parse_list_options): Fix non-constant initializer so we + can build with C89. + +2004-10-17 David Shaw <dshaw@jabberwocky.com> + + * keylist.c (print_one_subpacket): The flags field should be hex. + +2004-10-17 Werner Koch <wk@g10code.com> + + * passphrase.c (agent_get_passphrase): Cast UIDLEN to int. Noted + by Christian Cornelssen. + +2004-10-16 David Shaw <dshaw@jabberwocky.com> + + * parse-packet.c (parse_one_sig_subpkt, enum_sig_subpkt): Don't + BUG() on unknown subpackets. Rather, just return them silently. + +2004-10-15 Werner Koch <wk@g10code.com> + + * status.h (STATUS_NEED_PASSPHRASE_PIN): New. + * status.c (get_status_string): Added. + * passphrase.c (ask_passphrase): Moved status printing to .. + * cardglue.c (pin_cb): .. here and issue new status message. + + * keyedit.c (sign_uids): Don't include the leading LF in the + translatable string but print them separately. + + * apdu.c (apdu_open_remote_reader) [_WIN32]: We don't have ENOSYS. + + * app-openpgp.c (parse_login_data): New. + (app_select_openpgp): Call it. + (do_setattr): Reparse it after change. + + * pkclist.c (do_edit_ownertrust): Add a note to translators. + * keygen.c (ask_user_id): Ditto. + + * helptext.c: Typo fix. + +2004-10-14 David Shaw <dshaw@jabberwocky.com> + + * keylist.c (list_keyblock_print): Show the fingerprint after the + key, not after the first user ID. + + * keyedit.c (show_key_with_all_names): Don't show validity if + we're just printing user IDs for signing. + + * armor.c (fake_packet): Properly handle the case where the line + is dash-space (i.e. a blank line that was quoted). Give a warning + for bad dash escaping. + +2004-10-14 Werner Koch <wk@g10code.com> + + * export.c (do_export_stream) [ENABLE_SELINUX_HACKS]: Don't allow + secret key export. + * import.c (import_secret_one) [ENABLE_SELINUX_HACKS]: Likewise + + * misc.c (is_secured_filename): New. + * keydb.c (maybe_create_keyring) + * tdbio.c (tdbio_set_dbname) + * plaintext.c (handle_plaintext) + * openfile.c (copy_options_file, open_outfile) + * exec.c (exec_write) + * keygen.c (do_generate_keypair, gen_card_key_with_backup) + + * sign.c (sign_file, clearsign_file) + * keyring.c (create_tmp_file, do_copy): Check for secured files + before creating them. + + * keygen.c (print_status_key_created, read_parameter_file): + s/unsigned char/byte/ due to a strange typedef for RISC OS. Noted + by Stefan. + +2004-10-13 David Shaw <dshaw@jabberwocky.com> + + * armor.c (fake_packet): Allow arbitrary dash-escaped lines as per + 2440bis-10. This is bug #158. + + * keyserver.c (keyserver_work): Handle keyserver timeouts. + + * pkclist.c (do_edit_ownertrust): Different prompt when we're + using direct trust since the meaning is different. + + * keyedit.c (trustsig_prompt): Change the strings to match the + ones in pkclist.c:do_edit_ownertrust to make translation easier. + + * trustdb.c (trust_model_string, get_validity): Add direct trust + model which applies to the key as a whole and not per-uid. + + * options.h, g10.c (parse_trust_model): New. + (main): Call it from here to do string-to-trust-model. + +2004-10-13 Werner Koch <wk@g10code.com> + + * tdbdump.c (import_ownertrust): Removed all log_error_f and + reworded the messages. + + * dermor.c: Include i18n.h. Made 2 strings translatable. + + * misc.c (register_secured_file, is_secured_file) + (unregister_secured_file): New. + * keyring.c (do_copy, rename_tmp_file): Implement the SELinux hacks. + (keyring_register_filename): Ditto. + * tdbio.c (open_db): Ditto. + * openfile.c (copy_options_file, open_sigfile): Ditto. + * verify.c (verify_signatures, verify_one_file): Ditto. + * photoid.c (generate_photo_id): Ditto. + * keygen.c (read_parameter_file): Ditto. + * import.c (import_keys_internal): Ditto. + * decrypt.c (decrypt_message, decrypt_messages): Ditto. + * dearmor.c (dearmor_file, enarmor_file): Ditto. + * g10.c (main, print_mds): Ditto. + * exec.c (exec_write, exec_read): Ditto. + * card-util.c (change_login): Ditto. + * encode.c (encode_simple, encode_crypt): Ditto. + + * openfile.c (overwrite_filep, make_outfile_name, open_outfile) + (open_sigfile): Use iobuf_is_pipe_filename to check for pipes so + that special filesnames are taken into account. This is bug 327. + + * tdbdump.c (import_ownertrust): Ditto. + + * sign.c (write_plaintext_packet): Ditto. + (sign_file, clearsign_file, sign_symencrypt_file): + + * progress.c (handle_progress): Ditto. + * plaintext.c (handle_plaintext): Ditto. + (ask_for_detached_datafile, hash_datafiles): + + * encode.c (encode_simple, encode_crypt): Ditto. + +2004-10-12 Werner Koch <wk@g10code.com> + + * keygen.c (read_parameter_file): Changed to use iobuf based file + reading to allow the special file name feature to work. + + * keygen.c (read_parameter_file): New keyword "Handle". This is + bug 287. + (print_status_key_not_created): New. + (print_status_key_created): Add new arg HANDLE. + (do_generate_keypair): Print not created status. + * status.c, tatus.h (STATUS_KEY_NOT_CREATED): New. + +2004-10-11 David Shaw <dshaw@jabberwocky.com> + + * pkclist.c (do_edit_ownertrust): Use the same translated string + for showing the user ID as mainproc.c:print_pkenc_list. + + * mainproc.c (print_pkenc_list): Allow translating the quotes + around the user ID. + + * card-util.c, g10.c, photoid.c, trustdb.c: The last of the \"%s\" + -> `%s' quoting for things that aren't user IDs. + + * keyserver.c (keyserver_spawn): If there is no keyserver host, + print the whole URI since it is self-contained. + +2004-10-11 Werner Koch <wk@g10code.com> + + * keyserver.c (keyserver_spawn): Print an empty string in log_info + if the host is not set (e.g. finger). + +2004-10-10 David Shaw <dshaw@jabberwocky.com> + + * card-util.c, keyedit.c, openfile.c, pkclist.c, delkey.c, + keygen.c, photoid.c, revoke.c: Some yes-or-no prompts end in + "(y/n)". Some don't. Consistently use y/n everywhere. + + * keygen.c (ask_key_flags): New. + (ask_algo): Call it here in --expert mode so we don't need to + specify each possible variation of RSA capabilities. + + * keygen.c (do_add_key_flags): The spec says that all primary keys + MUST be able to certify. Force the certify flag on for primaries + (and off for subkeys). + + * keygen.c (generate_keypair): Fix generating keys with the auth + flag. + +2004-10-08 David Shaw <dshaw@jabberwocky.com> + + * encr-data.c (decrypt_data): Give a warning with a weak key, but + still allow to decrypt the message. + +2004-10-07 David Shaw <dshaw@jabberwocky.com> + + * pkclist.c (build_pk_list): Keystrify. + + * mainproc.c (check_sig_and_print), pkclist.c + (do_edit_ownertrust): Improve translatability of user ID prompts. + +2004-10-06 David Shaw <dshaw@jabberwocky.com> + + * helptext.c, pkclist.c (do_we_trust): It is not possible to get + here with a revoked or expired key, so BUG() that case. Remove + question about overriding revoked/expired. Also + --keyid-format-ify. + (do_we_trust_pre): Use print_pubkey_info() instead of printing the + info ourselves. + + * passphrase.c (passphrase_to_dek): Improve translatability of + user ID prompts. + + * keylist.c (print_pubkey_info): Use the user ID the pk was + selected by, if any. + + * keyedit.c (sign_uids, ask_revoke_sig): Improve translatability + of user ID prompts. + (ask_revoke_sig, menu_revsig): Try and use common strings for + these two functions so they don't need to be translated twice. + + * keyedit.c, keylist.c, keyserver.c, mainproc.c: The + revoked/expired/expires string change of 2004-09-29 was too + simple. Use two styles for each tag. + +2004-10-06 Werner Koch <wk@g10code.com> + + * ccid-driver.c (ccid_open_reader): Store the vendor ID. + (ccid_transceive_secure): New. + (parse_ccid_descriptor): Workaround for an SCM reader problem. + (send_escape_cmd): New. + +2004-10-05 David Shaw <dshaw@jabberwocky.com> + + * passphrase.c (agent_get_passphrase): Use keystrs for agent + strings, and fix sprintf warnings. + + * keyserver.c (keyserver_spawn): Fix BUG() with certain sets of + mixed regular and preferred keyserver refreshes. Noted by + Sebastian Wiesinger. + + * keyedit.c (show_key_with_all_names): Show uid validity in menu. + +2004-10-03 Timo Schulz <twoaday@g10code.de> + + * apdu.c (apdu_open_remote_reader) [_WIN32]: Do not set ENOSYS. + +2004-10-03 David Shaw <dshaw@jabberwocky.com> + + * keyedit.c (print_and_check_one_sig_colon): Fix bad keyids in + colon delsig output. Noted by Peter Palfrader. + (show_prefs): Do not reference missing selfsig. Noted by Alex + Moroz. + +2004-10-01 Werner Koch <wk@g10code.com> + + * gpgv.c (i18n_init): Always use LC_ALL. + +2004-09-30 Werner Koch <wk@g10code.com> + + * app-openpgp.c (verify_chv3) [GNUPG_MAJOR_VERSION!=1]: Typo fix. + +2004-09-30 David Shaw <dshaw@jabberwocky.com> + + * gpgv.c, keydb.c (keydb_add_resource): Factored keyring creation + out to .. + (maybe_create_keyring): .. new. Make sure that we do the checks + in a locked state. Problem reported by Stefan Haller. Try to + create the home directory before acquiring a lock for the keyring. + From Werner on stable branch. + + * g10.c (main): Blow up if we didn't lose setuid. From Werner on + stable branch. + +2004-09-29 David Shaw <dshaw@jabberwocky.com> + + * keyedit.c, keylist.c, keyserver.c, mainproc.c: Reduce the many + variations of "revoked" ("revoked", "[revoked]", " [revoked]", + "[revoked] ") "and" expired down to two to simplify translation. + +2004-09-28 David Shaw <dshaw@jabberwocky.com> + + * keyedit.c (print_and_check_one_sig): Account for the extra space + that show-sig-expire takes up so we do not wrap lines. + (show_key_with_all_names): No need to show subkey revocations as a + seperate line since we now show revocation date in the main subkey + line. + + * signal.c (got_fatal_signal): HAVE_DECL_SYS_SIGLIST is defined, + but zero if not found. Noted by John Clizbe. + + * keyserver.c (parse_keyrec): Fix problem with non-expiring keys + appearing expired in --search-keys results. + +2004-09-27 Werner Koch <wk@g10code.com> + + * card-util.c (card_edit): Take admin only status from the table. + + * 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. + +2004-09-25 David Shaw <dshaw@jabberwocky.com> + + * main.h, g10.c (main), card-util.c (change_pin): If "admin" has + not been issued, skip right to the CHV1/CHV2 PIN change. No need + to show the unblock or admin PIN change option. + (card_edit): Add "admin" command to add admin commands to the + menu. Do not allow admin commands until "admin" is given. + + * app-openpgp.c (verify_chv3): Show a countdown of how many wrong + admin PINs can be entered before the card is locked. + + * options.h, g10.c (main), app-openpgp.c (verify_chv3): Remove + --allow-admin. + +2004-09-24 David Shaw <dshaw@jabberwocky.com> + + * main.h: Create S2K_DIGEST_ALGO macro so we do not need to always + set opt.s2k_digest_algo. This helps fix a problem with PGP 2.x + encrypted symmetric messages. Change all callers (encode.c, + g10.c, keyedit.c, keygen.c, passphrase.c, sign.c). + + * armor.c, cardglue.c, getkey.c, import.c, keygen.c: Be consistent + in some more quoted strings. Always use 'user ID', not 'user id', + "quotes" for user IDs, etc. + + * keyedit.c (keyedit_menu), gpgv.c (agent_scd_getattr (stub)), + keygen.c (copy_mpi, generate_raw_key): Fix a compile problem and a + few warnings when building without card support. + +2004-09-23 Werner Koch <wk@g10code.com> + + * card_util.c (generate_card_keys): ask whether backup should be + created. + (card_store_subkey): Factored some code out to .. + * keygen.c (save_unprotected_key_to_card): .. new function. + (gen_card_key_with_backup): New. + (generate_raw_key): New. + (generate_keypair): New arg BACKUP_ENCRYPTION_DIR. Changed all + callers. + (do_generate_keypair): Divert to gen_card_key_with_backup when + desired. + + * apdu.c (open_pcsc_reader): Do not print empty reader string. + + * keygen.c (ask_algo): Allow creation of AUTH keys. + + * keyid.c (usagestr_from_pk): New. + + * 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. + + * keyedit.c (keyedit_menu): New command "keytocard" + (keyedit_menu): Bad hack for the not_with_sk element. + (show_key_with_all_names): Print the usage. + (find_pk_from_sknode): New. + + * card-util.c (card_store_subkey): New. + (copy_mpi): New. + + * cardglue.c (agent_openpgp_storekey): New. + +2004-09-22 Werner Koch <wk@g10code.com> + + * card-util.c (card_generate_subkey, generate_card_keys): Factored + common code out to ... + (get_info_for_key_operation, check_pin_for_key_operation) + (restore_forced_chv1, replace_existing_key_p) + (show_card_key_info): ... new functions. + +2004-09-21 David Shaw <dshaw@jabberwocky.com> + + * mainproc.c (check_sig_and_print), keyedit.c (show_prefs, + menu_set_keyserver_url): Make sure that keyserver URLs with + control characters inside are printed properly. In fact, handle + them as UTF8. + + * keyedit.c (keyedit_menu): Don't show "addcardkey" in the menu if + we do not have card support. + + * keydb.h, keyserver.c (print_keyrec, keyserver_spawn): fpr is an + array of unsigned bytes. + +2004-09-20 Werner Koch <wk@g10code.com> + + * g10.c: Make -K an alias for --list-secret-keys. + + * keylist.c (print_card_serialno): New. Taken from gnupg 1.9.11. + (list_keyblock_print): Make use of it. + * keyedit.c (show_key_with_all_names): Print the card S/N. + + * keyedit.c (keyedit_menu): New command ADDCARDKEY. + * card-util.c (card_generate_subkey): New. + * keygen.c (generate_card_subkeypair): New. + (gen_card_key): New arg IS_PRIMARY; changed all callers. + + * cardglue.c (open_card): Use shutdown code if possible. + (check_card_serialno): Ditto. + + * 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. + +2004-09-17 Werner Koch <wk@g10code.com> + + * g10.c (list_config): New config option ccid-reader-id. + (gpgconf_list): Add "reader-port". + + * 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. + +2004-09-16 David Shaw <dshaw@jabberwocky.com> + + * keyedit.c (show_key_with_all_names, show_prefs): Show preferred + keyserver(s) in "showpref" output. + + * keygen.c (keygen_add_keyserver_url), keyedit.c + (menu_set_keyserver_url): Allow setting a keyserver URL of "none" + to remove an existing keyserver URL. + + * keyedit.c (menu_set_keyserver_url): Confirm replacement of a + keyserver URL before overwriting the old one. + +2004-09-15 David Shaw <dshaw@jabberwocky.com> + + * gpgv.c (agent_scd_getattr): Stub. + + * misc.c (get_signature_count): New. Get the signature count from + a smartcard. + (pct_expando): Call it here so the %c expando becomes the number + of signatures issued. This allows for notations or the like with + an automatic signature count. + + * ccid-driver.c (usb_get_string_simple): Replacement function to + work with older libusb. + +2004-09-15 Werner Koch <wk@g10code.com> + + * g10.c [HAVE_LIBUSB]: New option --debug-ccid-driver. + + * 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. + +2004-09-13 David Shaw <dshaw@jabberwocky.com> + + * getkey.c (premerge_public_with_secret): Fix subkey<->binding sig + mismatch when some secret subkeys are missing. Discovered by + Michael Roth. + + * main.h, keylist.c (print_subpackets_colon): Make a public + function. + + * keyedit.c (print_and_check_one_sig_colon): New. Print a + with-colons version of the sig record. + (menu_delsig): Call it here for a with-colons delsig. + +2004-09-12 David Shaw <dshaw@jabberwocky.com> + + * options.h, keylist.c (print_one_subpacket, + print_subpackets_colon): Print a spk record for each request + subpacket. + (list_keyblock_colon): Call them here. + + * g10.c (parse_subpacket_list, parse_list_options): New. Make the + list of subpackets we are going to print. + (main): Call them here. + +2004-09-11 David Shaw <dshaw@jabberwocky.com> + + * card-util.c (fetch_url, card_edit): Use the pubkey URL stored on + the card to fetch an updated copy. Works with either straight + URLs or HKP or LDAP keyservers. + + * keyserver-internal.h, keyserver.c (keyserver_import_fprint), + import.c (revocation_present): Use a keyserver_spec so the caller + can pass in whatever keyserver they like. + +2004-09-10 David Shaw <dshaw@jabberwocky.com> + + * app-openpgp.c (get_cached_data): Avoid mallocing zero since it + breaks us when using --enable-m-guard. + + * 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-09 Werner Koch <wk@g10code.com> + + * cardglue.h: Add members for CA fingerprints. + * cardglue.c (agent_release_card_info): Invalid them. + (learn_status_cb): Store them. + + * app-common.h, app-openpgp.c, iso7816.c, iso7816.h + * apdu.c, apdu.h, ccid-driver.c, ccid-driver.h + * card-util.c: Updated from current gnupg-1.9. + + Changes are: + + * 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. + * apdu.c: Include <signal.h>. + * 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. + * 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. + * 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. + * app-openpgp.c (do_getattr): Fix for sending CA-FPR. + * app-openpgp.c (app_openpgp_readkey): Fixed check for valid + exponent. + * app-openpgp.c (do_setattr): Sync FORCE_CHV1. + * card-util.c (change_login): Kludge to allow reading data from a + file. + (card_edit): Pass ARG_STRING to change_login. + (card_status): Print CA fingerprints. + (change_cafpr): New. + (card_edit): New command CAFPR. + +2004-04-30 Werner Koch <wk@gnupg.org> + + * g10.c (main) <gpgconf>: Use gpg.conf and not /dev/null as + default filename. + +2004-04-28 Werner Koch <wk@gnupg.org> + + * card-util.c (card_edit): Remove PIN verification. + (generate_card_keys): New arg SERIALNO. Do PIN verification here + after resetting forced_chv1. + + +2004-09-09 Werner Koch <wk@g10code.com> + + * signal.c (got_fatal_signal): Do readline cleanup. Print signal + number if we can't print the name. Use new autoconf macro + HAVE_DECL_SYS_SIGLIST. + (get_signal_name): Removed. + + * photoid.c: Include ttyio.h. + + * parse-packet.c (skip_rest): Removed. Changed all callers to use + the new iobuf_skip_reset. Orginal patch by Florian Weimer. + +2004-09-07 Werner Koch <wk@g10code.de> + + * photoid.c (generate_photo_id): Use tty_printf and not just + printf. Put _() around one string. + +2004-09-03 David Shaw <dshaw@jabberwocky.com> + + * keyserver.c (parse_keyrec): Force the 'e'xpired flag on as soon + as we know the key is definitely expired. Some translatable + string cleanup. + +2004-08-27 David Shaw <dshaw@jabberwocky.com> + + * encode.c, exec.c, g10.c, sign.c: Some translatable string + cleanup. Change some "this" to `this'. + +2004-08-23 David Shaw <dshaw@jabberwocky.com> + + * keyserver.c (keyserver_spawn): Show log line for what keyserver + action we are taking. + + * keyid.c (keystr): If printing a keyid that lacks the high 4 + bytes, print the low 4 alone. + (keystr_from_desc): Handle short keyids and warn on v3 + fingerprints. + + * keydb.h, getkey.c (get_user_id_printable, + get_user_id_string_printable): Rename to get_user_id_native and + get_user_id_string_native and remove the printable stuff since + we're print-ifying valid utf8 characters. Change all callers in + import.c, sign.c, keylist.c, and encode.c. + + * keyserver.c (keyserver_search_prompt): Make sure the search + string is converted from UTF-8 before display. + +2004-08-19 Werner Koch <wk@g10code.de> + + * seskey.c (encode_session_key): Changed the zero random byte + substituting code to actually do clever things. Thanks to + Matthias Urlichs for noting the implementation problem. + +2004-08-18 Marcus Brinkmann <marcus@g10code.de> + + * passphrase.c (agent_get_passphrase): Fix detection of gpg-agent + cancellation. + +2004-08-08 David Shaw <dshaw@jabberwocky.com> + + * plaintext.c (handle_plaintext): Bigger buffer for extra safety. + + * g10.c (main): New alias --throw-keyid for --throw-keyids, so + that it continues to work in old configuration files. Noted by + Jens Adam. + + * pkclist.c (algo_available): --pgp8 now allows blowfish, zlib, + and bzip2. + + * status.c (do_get_from_fd): Flush stdout if status isn't flushing + it for us. This guarantees that any menus that were displayed + before the prompt don't get stuck in a buffer. Noted by Peter + Palfrader. This is Debian bug #254072. + + * sign.c (update_keysig_packet): Revert change of 2004-05-18. It + is not appropriate to strip policy and notations when remaking a + sig. That should only happen when specifically requested by the + user. + +2004-08-05 David Shaw <dshaw@jabberwocky.com> + + * armor.c (radix64_read): No armor CRC is legal according to the + spec (the CRC is a MAY). + +2004-07-28 David Shaw <dshaw@jabberwocky.com> + + * misc.c (argsplit): Properly split quoted args from the keyword + and trim whitespace afterwards. + +2004-07-27 David Shaw <dshaw@jabberwocky.com> + + * misc.c (optsep): Add the ability to understand keyword="quoted + arg with spaces" type options. + +2004-07-16 David Shaw <dshaw@jabberwocky.com> + + * keylist.c (list_keyblock_print): Always use the new listing + format where uids are always on a line for themselves. Mark + expired secret keys as expired. + + * options.h, g10.c (main): Rename list show-validity to + show-uid-validity as it only shows for uids. + + * armor.c (armor_filter): Do not use padding to get us to 8 bytes + of header. Rather, use 2+4 as two different chunks. This avoids + a fake filename of "is". + +2004-07-15 David Shaw <dshaw@jabberwocky.com> + + * keyedit.c (sign_uids): Properly handle remaking a self-sig on + revoked or expired user IDs. Also, once we've established that a + given uid cannot or will not be signed, don't continue to ask + about each sig. + + * mainproc.c (proc_symkey_enc), seckey-cert.c (do_check): Check + the S2K hash algorithm before we try to generate a passphrase + using it. This prevents hitting BUG() when generating a + passphrase using a hash that we don't have. + + * sign.c (sign_symencrypt_file): Allow using --force-mdc in --sign + --symmetric messages. + + * g10.c (main): Alias --charset as --display-charset to help avoid + the continuing confusion and make room for possible changes in + devel. + + * parse-packet.c (parse_plaintext): Show the hex value for the + literal packet mode since it may not be printable. + + * keygen.c (make_backsig): Make sure that the backsig was built + successfully before we try and use it. + + * status.h, status.c (get_status_string), plaintext.c + (handle_plaintext): New status tags PLAINTEXT and + PLAINTEXT_LENGTH. + +2004-06-16 Werner Koch <wk@gnupg.org> + + * free-packet.c (copy_secret_key): Get last fix right. + +2004-06-16 Werner Koch <wk@gnupg.org> + + * free-packet.c (copy_secret_key): Fixed memory leak when D is not + NULL. + + * passphrase.c (passphrase_to_dek): Added a few comments to the + code. + +2004-05-26 David Shaw <dshaw@jabberwocky.com> + + * keyserver.c (keyserver_refresh): Keep track of keys already + fetched so we don't do a regular keyserver fetch if the preferred + keyserver fetch has exhausted the list. + +2004-05-23 David Shaw <dshaw@jabberwocky.com> + + * verify.c (verify_signatures): Verify multiple files in the same + order in which we hashed them when issuing the signature. Noted + by Nicholas Cole. + + * pkclist.c (do_edit_ownertrust): Fix a kbnode leak and do another + keyid-format conversion. + +2004-05-22 Werner Koch <wk@gnupg.org> + + * trustdb.c (check_regexp): s/EXP/EXPR/. + + * keyedit.c (trustsig_prompt): Remoev useless range check. + + * options.h: Renamed ctrl to glo_ctrl. Changed all users. + + * ccid-driver.c (ccid_open_reader): Print a warning when CCID + can't be used. + +2004-05-21 David Shaw <dshaw@jabberwocky.com> + + * mainproc.c (check_sig_and_print): If we're honoring preferred + keyservers, and auto-key-retrieve is set, try and get a missing + key from the preferred keyserver subpacket when we verify the sig. + + * gpgv.c (parse_preferred_keyserver, free_keyserver_spec): Stubs. + + * keyserver.c (keyidlist): Use new parse_preferred_keyserver + function. + (keyserver_work): Use the passed-in keyserver spec rather than the + options global one. + + * keyserver-internal.h, keyserver.c (parse_preferred_keyserver): + New function to take a sig and return a split out keyserver_spec. + (keyserver_import_keyid): Now takes a keyserver_spec. + + * keyserver.c (keyidlist): Go back to the old fast keyid lister. + Only merge selfsigs if we have to for honor-keyserver-url. + (keyserver_refresh): Keyserver URL handler moved here. + (calculate_keyid_fpr): Removed. + + * keydb.h, keyid.c (keystr_from_desc): Calculate a key string from + a KEYDB_SEARCH_DESC. + + * keyserver.c (keyserver_spawn): Fix keyserver options on tempfile + only platforms. Noted by Roger Sondermann. + +2004-05-20 David Shaw <dshaw@jabberwocky.com> + + * keyserver.c (keyserver_work): Allow --refresh-keys with a + preferred keyserver to happen even if there is no global keyserver + set. + + * sig-check.c (do_check_messages): No need to check for Elgamal + signatures any longer. + (do_check_messages, do_check, check_key_signature2): + --keyid-format conversion. + + * pkclist.c (show_paths, edit_ownertrust): Remove some unused + code. + + * options.h (ctrl): New for member IN_AUTO_KEY_RETRIEVE. + + * mainproc.c (check_sig_and_print): track whether we are + retrieving a key. + + * status.c (status_currently_allowed): New. + (write_status_text, write_status_text_and_buffer): Use it here. + + * g10.c: New command --gpgconf-list. + (gpgconf_list): New. From Werner on stable branch. + +2004-05-19 David Shaw <dshaw@jabberwocky.com> + + * pubkey-enc.c (get_session_key, get_it), keyedit.c + (show_key_with_all_names, show_basic_key_info): --keyid-format + conversion. + +2004-05-18 David Shaw <dshaw@jabberwocky.com> + + * sign.c (update_keysig_packet): Policies and notations should be + stripped out when remaking a self-signature. Noted by Atom + Smasher. + + * keyserver.c (parse_keyserver_uri): Fix compiler warnings. + +2004-05-11 David Shaw <dshaw@jabberwocky.com> + + * options.h, keyserver-internal.h, keyserver.c + (parse_keyserver_uri): Improved URI parser that keeps track of the + path information and doesn't modify the input string. + (keyserver_spawn): Tell keyserver plugins about the path. + +2004-05-11 Werner Koch <wk@gnupg.org> + + * keylist.c (show_policy_url, show_keyserver_url, show_notation) + (list_one): Use const char* for i18n string helpers. + + * keygen.c (do_generate_keypair, read_parameter_file): Really + close the files. + (do_generate_keypair): Create the secret key file using safe + permissions. Noted by Atom Smasher. + +2004-05-10 David Shaw <dshaw@jabberwocky.com> + + * options.h, mainproc.c (symkey_decrypt_seskey), keyserver.c + (struct keyrec, parse_keyrec, keyserver_search_prompt), keyedit.c + (keyedit_menu), g10.c (add_keyserver_url, add_policy_url): Fix + some compiler warnings. + +2004-05-08 David Shaw <dshaw@jabberwocky.com> + + * keyedit.c (keyedit_menu, menu_set_keyserver_url): Allow passing + preferred keyserver on "keyserver" command line. Sanity check + keyserver URL before accepting it. + + * keyserver-internal.h, g10.c (main), keyserver.c + (parse_keyserver_uri): Add an option to require the scheme:// and + change all callers. + (free_keyserver_spec): Make public. + +2004-05-07 Werner Koch <wk@gnupg.org> + + * sign.c (write_plaintext_packet): Fixed the detection of too + large files in the same way as in encode.c. + +2004-05-04 David Shaw <dshaw@jabberwocky.com> + + * keylist.c (show_notation): Use bits to select which sort of + notation to show. Don't allow a not-shown notation to prevent us + from issuing the proper --status-fd message. + + * options.h, g10.c (main): Add show-std/standard-notations and + show-user-notations. show-notations is both. Default is to show + standard notations only during verify. Change all callers. + +2004-04-28 David Shaw <dshaw@jabberwocky.com> + + * main.h, keylist.c (show_notation): Add argument to show only + user notations, only standard notations, or both. Change all + callers. + + * keyserver.c (keyserver_spawn): We still need EXEC_TEMPFILE_ONLY. + +2004-04-28 Werner Koch <wk@gnupg.org> + + * card-util.c (card_edit): Require PIN only for generate. + + * app-openpgp.c (do_setattr): Sync FORCE_CHV1. + +2004-04-27 Werner Koch <wk@gnupg.org> + + * keyserver.c (keyserver_spawn) [EXEC_TEMPFILE_ONLY]: Removed + setting use_temp_file because this option has been removed. + + * g10.c: New commands --allow-admin and --deny-admin. + * options.h (opt): Add member ALLOW_ADMIN. + + * tlv.h, tlv.c: New. Copied from gnupg-1.9. + * cardglue.c (open_card): The serialno is now set internally by + app_select_openpgp; changed invocation. + * cardglue.h (app_t, ctrl_t): New. + (GPG_ERR_EBUSY, GPG_ERR_ENOENT, GPG_ERR_NOT_FOUND, GPG_ERR_BUG) + (GPG_ERR_NOT_IMPLEMENTED, GPG_ERR_EACCESS): New. + (gpg_err_code_from_errno): New. + + * app-common.h, app-openpgp.c, iso7816.c, iso7816.h + * apdu.c, apdu.h, ccid-driver.c, ccid-driver.h + * card-util.c: Updated from current gnupg-1.9. + + Changes are: + + * app-common.h: New members FNC.DEINIT and APP_LOCAL. + * 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. + + * apdu.c (apdu_send_le): Reinitialize RESULTLEN. Handle + SW_EOF_REACHED like SW_SUCCESS. + + * 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. + + * 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. + +2004-04-25 David Shaw <dshaw@jabberwocky.com> + + * getkey.c (get_seckey_byname2): Significantly simplify this + function by using key_byname to do the heavy lifting. Note that + this also fixes an old problem when the first key on the secret + keyring has an unusable stub primary, but is still chosen. + + * getkey.c (key_byname): If namelist is NULL, return the first key + in the keyring. + +2004-04-22 David Shaw <dshaw@jabberwocky.com> + + * keygen.c (make_backsig): If DO_BACKSIGS is not defined, do not + create backsigs. + + * getkey.c (merge_selfsigs_subkey): Find 0x19 backsigs on subkey + selfsigs and verify they are valid. If DO_BACKSIGS is not + defined, fake this as always valid. + + * packet.h, parse-packet.c (parse_signature): Make parse_signature + non-static so we can parse 0x19s in self-sigs. + + * main.h, sig-check.c (check_backsig): Check a 0x19 signature. + (signature_check2): Give a backsig warning if there is no or a bad + 0x19 with signatures from a subkey. + +2004-04-21 David Shaw <dshaw@jabberwocky.com> + + * parse-packet.c (dump_sig_subpkt, parse_one_sig_subpkt, + can_handle_critical): Parse and display 0x19 signatures. + +2004-04-20 David Shaw <dshaw@jabberwocky.com> + + * keyserver.c (parse_keyserver_uri): Do not accept "http" as an + alias for "hkp". They are not the same thing. + +2004-04-19 David Shaw <dshaw@jabberwocky.com> + + * options.h, g10.c (main): Add keyserver-option + honor-keyserver-url. parse_keyserver_options now returns a + success code. + + * keyserver.c (parse_keyserver_options): Return error on failure + to parse. Currently there is no way to fail as any unrecognized + options get saved to be sent to the keyserver plugins later. + Check length of keyserver option tokens since with =arguments we + must only match the prefix. + (free_keyserver_spec): Moved code from parse_keyserver_url. + (keyserver_work, keyserver_spawn): Pass in a struct keyserver_spec + rather than using the global keyserver option. + (calculate_keyid_fpr): New. Fills in a KEYDB_SEARCH_DESC for a + key. + (keyidlist): New implementation using get_pubkey_bynames rather + than searching the keydb directly. If honor-keyserver-url is set, + make up a keyserver_spec and try and fetch that key directly. Do + not include it in the returned keyidlist in that case. + +2004-04-16 David Shaw <dshaw@jabberwocky.com> + + * plaintext.c (handle_plaintext): Accept 'u' as a plaintext mode + that requires end of line conversion. This is being considered + for a UTF8 text packet. If this doesn't take place, no major harm + done. If it does take place, we'll get a jump on starting the + changeover. + + * g10.c (main): --no-use-embedded-filename. + + * build-packet.c (calc_plaintext, do_plaintext): Do not create + illegal (packet header indicates a size larger than the actual + packet) encrypted data packets when not compressing and using a + filename longer than 255 characters. + + * keyedit.c (no_primary_warning): Cleanup. (menu_expire): Don't + give primary warning for subkey expiration changes. These cannot + reorder primaries. + + * keygen.c (gen_elg, gen_dsa, gen_rsa, do_create, + do_generate_keypair, generate_subkeypair): New is_subkey argument + to set whether a generated key is a subkey. Do not overload the + ret_sk. This is some early cleanup to do backsigs for signing + subkeys. + + * keygen.c (write_keybinding, do_generate_keypair, + generate_subkeypair): Keep track of the unprotected subkey secret + key so we can make a backsig with it. + + * keygen.c (make_backsig): New function to add a backsig to a + binding sig of signing subkeys. Currently disabled. + (write_keybinding): Call it here, for signing subkeys only. + + * sign.c (make_keysig_packet): Allow generating 0x19 signatures + (same as 0x18 or 0x28, but used for backsigs). + + * packet.h, build-packet.c (build_sig_subpkt): Add new + SIGSUBPKT_SIGNATURE type for embedded signatures. + + * main.h, misc.c (optsep, argsplit, optlen, parse_options): + Simplify code and properly handle a partial match against an + option with an argument. + + * keyserver-internal.h, keyserver.c (parse_keyserver_options): Use + new optsep and argsplit functions. + +2004-04-15 David Shaw <dshaw@jabberwocky.com> + + * main.h, misc.c (argsplit): Refactor argsep into argsplit and + argsep so they can be called separately. + + * options.h, keyserver.c (parse_keyserver_options): Remove + duplicate code from parse_keyserver_options by calling the generic + parse_options. + + * keyserver.c (keyserver_spawn, keyserver_refresh), g10.c (main), + gpgv.c (main), mainproc.c (check_sig_and_print), import.c + (revocation_present): Change all callers. + +2004-04-14 David Shaw <dshaw@jabberwocky.com> + + * packet.h, getkey.c (fixup_uidnode, merge_selfsigs_subkey): Keep + track of which self-sig we actually chose. + + * keyedit.c (menu_expire, menu_set_primary_uid, + menu_set_preferences): Use it here to avoid updating non-used + self-sigs and possibly promoting an old self-sig into + consideration again. + + * options.h, import.c, keyserver-internal.h, g10.c, mainproc.c, + keyserver.c (parse_keyserver_uri): Parse keyserver URI into a + structure. Cleanup for new "guess my keyserver" functionality, as + well as refreshing via a preferred keyserver subpacket. + + * options.h: Encapsulate keyserver details. Change all callers. + +2004-04-05 Werner Koch <wk@gnupg.org> + + * status.h (STATUS_NEWSIG): New. + * status.c (get_status_string): Add it. + +2004-03-27 David Shaw <dshaw@jabberwocky.com> + + * keyedit.c (keyedit_menu): Request a trustdb update when adding a + new user ID so the new ID gets validity set. Reported by Owen + Taylor. + +2004-03-25 David Shaw <dshaw@jabberwocky.com> + + * options.h, g10.c (main), compress-bz2.c (init_uncompress): + Rename --bzip2-compress-lowmem to --bzip2-decompress-lowmem since + it applies to decompression, not compression. + +2004-03-24 David Shaw <dshaw@jabberwocky.com> + + * keyedit.c (sign_uids, show_key_and_fingerprint, ask_revoke_sig, + menu_revsig, menu_showphoto): --keyid-format conversion. + (menu_addrevoker): Use print_pubkey_info() rather than duplicating + code. + +2004-03-19 David Shaw <dshaw@jabberwocky.com> + + * trustdb.c (update_min_ownertrust, validate_keys): Do not use + keystr functions in log_debug. + + * import.c (import_one): Try and collapse user IDs when importing + a key for the first time. + + * keyedit.c (menu_addrevoker): Allow appointing a subkey as a + designated revoker if the user forces it via keyid!, so long as + the subkey can certify. Also use the proper date string when + prompting for confirmation. + + * g10.c (main): Maintain ordering of multiple Comment lines. + Requested by Peter Hyman. + +2004-03-17 David Shaw <dshaw@jabberwocky.com> + + * mainproc.c (proc_pubkey_enc, print_pkenc_list, list_node): + --keyid-format conversion. + +2004-03-16 David Shaw <dshaw@jabberwocky.com> + + * getkey.c (skip_unusable, merge_selfsigs_main, + premerge_public_with_secret, lookup, get_user_id_string): + --keyid-format conversion. + +2004-03-15 David Shaw <dshaw@jabberwocky.com> + + * trustdb.c (add_utk, verify_own_keys, update_min_ownertrust, + get_validity, ask_ownertrust, validate_keys): --keyid-format + conversion. + + * import.c (check_prefs_warning, check_prefs): --keyid-format + conversion and a little better text. + (import_one, import_secret_one, import_revoke_cert, chk_self_sigs, + delete_inv_parts, merge_blocks): Still more --keyid-format + conversions. + +2004-03-06 David Shaw <dshaw@jabberwocky.com> + + * keylist.c (print_seckey_info, print_pubkey_info): --keyid-format + conversion. + (list_keyblock_print): 0xshort should not push us into the new + list format since it is not much longer than regular 8-character + short keyids. + + * keydb.h, keyid.c (keystr_from_pk, keystr_from_sk): New functions + to pull a key string from a key in one step. This isn't faster + than before, but makes for neater code. + + * keylist.c (list_keyblock_print): Use keystr_from_xx here. + (print_key_data): No need to pass a keyid in. + +2004-03-05 David Shaw <dshaw@jabberwocky.com> + + * keyid.c (keyid_from_sk): Minor performance boost by caching + secret key keyids so we don't have to calculate them each time. + + * getkey.c (merge_selfsigs_subkey): Do not mark subkeys valid if + we do not support their pk algorithm. This allows for early + (during get_*) rejection of a subkey, and selection of another. + + * passphrase.c (passphrase_to_dek): Give a little more information + when we have room to do so. + +2004-03-04 David Shaw <dshaw@jabberwocky.com> + + * revoke.c (export_minimal_pk), export.c (do_export_stream), + passphrase.c (passphrase_to_dek), keyserver.c (print_keyrec): A + few more places to use --keyid-format. + + * options.h, g10.c (main), export.c (parse_export_options, + do_export_stream): Remove --export-all and the "include-non-rfc" + export-option as they are no longer meaningful with the removal of + v3 Elgamal keys. + + * armor.c (fake_packet, armor_filter): Use the 2440 partial length + encoding for the faked plaintext packet. + +2004-03-03 David Shaw <dshaw@jabberwocky.com> + + * options.h, g10.c (main), mainproc.c (check_sig_and_print): + Remove verify-option show-long-keyids and replace with + the more general keyid-format. + + * build-packet.c (write_header2): Remove call to start old gpg + partial length mode and change all callers. + (do_plaintext): Turn off partial length encoding now that we're + done writing the packet. + (do_comment, do_user_id): Try for a headerlen of 2 since that's + the smallest and most likely encoding for these packets. + + * parse-packet.c (parse): Remove call to start old gpg partial + length mode. + +2004-03-02 David Shaw <dshaw@jabberwocky.com> + + * options.h, g10.c (main): Add a more flexible --keyid-format + option to replace the list-option (and eventually verify-option) + show-long-keyids. The format can be short, long, 0xshort, and + 0xlong. + + * keydb.h, keyid.c (keystr, keystrlen): New functions to generate + a printable keyid. + + * keyedit.c (print_and_check_one_sig, show_key_with_all_names), + keylist.c (list_keyblock_print): Use new keystr() function here to + print keyids. + + * packet.h, free-packet.c (free_encrypted, free_plaintext), + parse-packet.c (copy_packet, skip_packet, skip_rest, read_rest, + parse_plaintext, parse_encrypted, parse_gpg_control): Use a flag + to indicate partial or indeterminate encoding. This is the first + step in some minor surgery to remove the old gpg partial length + encoding. + +2004-03-01 David Shaw <dshaw@jabberwocky.com> + + * parse-packet.c (parse): Only data-type packets are allowed to + use OpenPGP partial length encoding. + +2004-02-25 David Shaw <dshaw@jabberwocky.com> + + * delkey.c (do_delete_key): Allow deleting a public key with a + secret present if --expert is set. + + * plaintext.c (handle_plaintext): Make bytecount static so it + works with multiple literal packets inside a message. + + * encode.c, helptext.c (keygen.algo, keygen.algo.elg_se), keygen.c + (ask_algo), sig-check.c (do_check_messages), skclist.c + (build_sk_list): Rename "ElGamal" to "Elgamal" as that is the + proper spelling nowadays. Suggested by Jon Callas. + +2004-02-24 David Shaw <dshaw@jabberwocky.com> + + * plaintext.c: Copyright. + + * encode.c (encode_simple): Show cipher with --verbose. + + * options.h, g10.c (main), keyedit.c (sign_keys): Add + --ask-cert-level option to enable cert level prompts during + sigs. Defaults to on. Simplify --default-cert-check-level to + --default-cert-level. If ask-cert-level is off, or batch is on, + use the default-cert-level as the cert level. + + * options.h, g10.c (main), trustdb.c (mark_usable_uid_certs): + Simplify --min-cert-check-level to --min-cert-level. + +2004-02-22 David Shaw <dshaw@jabberwocky.com> + + * options.h, g10.c (main), trustdb.c (mark_usable_uid_certs): Add + --min-cert-check-level option to specify minimum cert check level. + Defaults to 2 (so 0x11 sigs are ignored). 0x10 sigs cannot be + ignored. + +2004-02-21 David Shaw <dshaw@jabberwocky.com> + + * plaintext.c (handle_plaintext): Properly handle a --max-output + of zero (do not limit output at all). + + * keyserver.c (keyserver_spawn): Use the full 64-bit keyid in the + INFO header lines, and include "sig:" records for the benefit of + people who store their keys in LDAP servers. It makes it easy to + do queries for things like "all keys signed by Isabella". + + * main.h, misc.c (hextobyte): Removed. It's in libutil.a now. + +2004-02-20 David Shaw <dshaw@jabberwocky.com> + + * keyserver.c (keyserver_export): Disallow user strings that + aren't key IDs. + (keyserver_import): Clarify error message. + (keyserver_spawn): Properly handle 8 bit characters in user IDs in + the info lines during SEND. + + * mkdtemp.c: Removed. + + * Makefile.am: We get mkdtemp.c from libutil.a now, so don't link + with @LIBOBJS@. + + * keyserver.c (keyserver_spawn): Pass the scheme to the keyserver + helper. + +2004-02-18 David Shaw <dshaw@jabberwocky.com> + + * options.h, g10.c (main), plaintext.c (handle_plaintext): Add + --max-output option to help people deal with decompression bombs. + +2004-02-15 David Shaw <dshaw@jabberwocky.com> + + * build-packet.c (do_user_id): Do not force a header for attribute + packets as they require a new CTB, and we don't support forced + headers for new CTBs yet. + +2004-02-14 David Shaw <dshaw@jabberwocky.com> + + * build-packet.c (write_header2): If a suggested header length is + provided along with a zero length, interpret this as an actual + zero length packet and not as an indeterminate length packet. + (do_comment, do_user_id): Use it here as these packets might be + naturally zero length. + + * parse-packet.c (parse): Show packet type when failing due to an + indeterminate length packet. + + * misc.c (parse_options): Only provide args for the true (i.e. not + "no-xxx") form of options. + +2004-02-13 David Shaw <dshaw@jabberwocky.com> + + * keyserver.c (argsep): Move to misc.c. + + * main.h, misc.c (parse_options), export.c (parse_export_options), + import.c (parse_import_options), g10.c (main): Use it here to + allow for options with optional arguments. Change all callers. + + * import.c (check_prefs): Some language fixes. + (sec_to_pub_keyblock, import_secret_one): Without knowing the + number of MPIs there are, we cannot try and sk-to-pk-ize a key. + +2004-02-12 David Shaw <dshaw@jabberwocky.com> + + * import.c (check_prefs): New function to check preferences on a + public key to ensure that it does not advertise any that we cannot + fulfill. Use the keyedit command list function to optionally + rewrite the prefs. + (import_one, import_secret_one): Use it here when importing a + public key that we have the secret half of, or when importing a + secret key that we have the public half of. + + * main.h, keyedit.c (keyedit_menu): Remove sign_mode and enhance + the more general command list functionality to replace it. + + * g10.c (main): Use the general command functionality to implement + --sign-key, --lsign-key, --nrsign-key, and --nrlsign-key. + + * import.c (import_one): Do the revocation check even in the case + when a key, a revocation key set in a direct key signature, and a + revocation from that revocation key, all arrive piecemeal. + Needless to say, this is pretty obscure. + +2004-02-11 David Shaw <dshaw@jabberwocky.com> + + * options.h, g10.c (main), keylist.c (list_keyblock_print): Add + "show-unusable-subkeys" list-option to show revoked and/or expired + subkeys. + +2004-02-10 David Shaw <dshaw@jabberwocky.com> + + * keyedit.c (keyedit_menu): Prompt for subkey removal for both + secret and public subkeys. + + * keylist.c (list_keyblock_print), keyedit.c + (show_key_with_all_names): Show the revocation date of a + key/subkey, and general formatting work. + + * packet.h, getkey.c (merge_selfsigs_main, merge_selfsigs_subkey, + merge_selfsigs): Keep track of the revocation date of a key. + + * keydb.h, keyid.c (revokestr_from_pk): New function to print the + revocation date of a key. + + * keygen.c (keygen_set_std_prefs): Build the default preferences + list at runtime as it properly handles algorithms disabled at + build or run time. + + * getkey.c (merge_selfsigs_main): Properly handle expired user IDs + when the expired self-sig is not the only self-sig. + + * misc.c (compress_algo_to_string): Return NULL on failure like + all of the other xxxx_algo_to_string() functions. + + * mainproc.c (list_node): Minor spacing tweak to match --list-keys + output. + + * keylist.c (list_keyblock_print), mainproc.c (list_node): Mark + revoked subkeys as revoked. Requested by Matthew Wilcox. Revoked + overrides expiration when both apply. + + * keyedit.c (show_prefs): Use compress algo constants. + (show_basic_key_info): Make revoked and expired tags translatable. + + * g10.c (rm_group): Properly ungroup from a list of groups. + +2004-01-30 David Shaw <dshaw@jabberwocky.com> + + * g10.c (main, rm_group): Add --ungroup command to remove a + particular group. + (add_group): When adding a group with the same name as an already + existing group, merge the two groups. + (list_config): Show an error message when listing a config item + that doesn't exist. + (main): Replace -z0 trick for no compression. + + * packet.h, keyedit.c (show_key_with_all_names_colon), keylist.c + (list_keyblock_colon), mainproc.c (list_node, proc_tree): Minor + cleanup to remove local_id, which is no longer used. + +2004-01-27 David Shaw <dshaw@jabberwocky.com> + + * getkey.c: Set MAX_PK_CACHE_ENTRIES and MAX_UID_CACHE_ENTRIES to + PK_UID_CACHE_SIZE (set in ./configure). + + * getkey.c (get_pubkey): When reading key data into the cache, + properly handle keys that are partially (pk, no UIDs) cached + already. This is Debian bug #176425 and #229549. + + * compress.c (init_compress, push_compress_filter2): Do the right + thing (i.e. nothing) with compress algo 0. + + * main.h, decrypt.c (decrypt_messages): Accept filenames to + decrypt on stdin. This is bug #253. + +2004-01-23 David Shaw <dshaw@jabberwocky.com> + + * mainproc.c (list_node): Show sigs with --verbose. + + * options.h, g10.c (set_screen_dimensions): New function to look + at COLUMNS and LINES. + + * keyserver.c (parse_keyrec, keyserver_search_prompt), keyedit.c + (print_and_check_one_sig): Use new screen dimension variables. + +2004-01-21 David Shaw <dshaw@jabberwocky.com> + + * g10.c (list_config): New function to dump config options to + stdout. Currently requires --with-colons. + (collapse_args): New function to turn argc/argv into a single + string. + (main): Use it here to pass list_config() more than one argument + as a single string. + (print_algo_numbers): Helper to print algorithm number for + --list-config "pubkey", "cipher", "hash"/"digest", and "compress" + config options. + + * packet.h, getkey.c (merge_selfsigs, merge_selfsigs_main), + pkclist.c (check_signatures_trust): Indicate who has revoked a key + (the owner or a designated revoker). If a key was revoked by + both, prefer the owner. If a subkey is already revoked by the + owner, don't allow a designated revokation of the whole key to + override this. We're still revoked either way, of course. + + * keyedit.c (print_and_check_one_sig, keyedit_menu): Use the + COLUMNS environment variable (if any) to hint how wide the + terminal is. Disabled on _WIN32. Suggested by Janusz + A. Urbanowicz. + +2004-01-20 David Shaw <dshaw@jabberwocky.com> + + * keylist.c (set_attrib_fd): Open attribute fd in binary + mode. This isn't meaningful on POSIX systems, but the Mingw builds + aren't exactly POSIX. + + * trustdb.c (reset_trust_records): New, faster, implementation + that doesn't involve a keyring scan. + (clear_validity): Removed. + + * g10.c (main), keydb.h, keydb.c (keydb_rebuild_caches), + keyring.h, keyring.c (keyring_rebuild_cache): Add "noisy" flag so + cache rebuilds can remain noisy when called for itself, and quiet + when called as part of the trustdb rebuild. + + * trustdb.c (validate_keys): Rebuild the sig caches before + building the trustdb. Note that this is going to require some + architectual re-thinking, as it is agonizingly slow. + +2004-01-19 David Shaw <dshaw@jabberwocky.com> + + * sig-check.c (check_key_signature2): Comments. + + * keyring.c (keyring_rebuild_cache): Clear sig cache for any + signatures that we can no longer process (say, if the user removed + support for a necessary pubkey or digest algorithm). + +2004-01-16 David Shaw <dshaw@jabberwocky.com> + + * misc.c (print_cipher_algo_note): May as well call Rijndael AES + at this point. + + * keygen.c (do_create), misc.c (openpgp_pk_algo_usage): Remove the + last bits of Elgamal type 20 support. + +2004-01-03 Stefan Bellon <sbellon@sbellon.de> + + * compress.c [__riscos__]: Only use RISC OS' own ZLib module if + configured to use it. + +2003-12-30 David Shaw <dshaw@jabberwocky.com> + + * options.h, g10.c (main), import.c (parse_import_options, + import_one, import_secret_one), keyserver.c (keyserver_refresh): + Change --merge-only to --import-option merge-only. Deprecate + --merge-only. + +2003-12-29 David Shaw <dshaw@jabberwocky.com> + + * misc.c (pull_in_libs): Dead code. Removed. + + * sig-check.c (check_revocation_keys): Comments. + + * getkey.c (merge_selfsigs_main): Don't bother to check designated + revoker sigs if the key is already revoked. + + * packet.h, getkey.c (merge_selfsigs_main): New "maybe_revoked" + flag on PKs. It is set when there is a revocation signature from + a valid revocation key, but the revocation key is not present to + verify the signature. + + * pkclist.c (check_signatures_trust): Use it here to give a + warning when showing key trust. + + * compress-bz2.c: Include stdio.h. Solaris 9 has a very old bzip2 + library and we can at least guarantee that it won't fail because + of the lack of stdio.h. + + * tdbio.c: Fixed format string bugs related to the use of DB_NAME. + Reported by Florian Weimer. + +2003-12-28 David Shaw <dshaw@jabberwocky.com> + + * options.h, g10.c (main), keyserver.c (keyserver_opts, + parse_keyserver_uri): honor-http-proxy is no longer an option + since we can do the same thing with http-proxy with no arguments. + Also remove broken-http-proxy since it can be better handled in + the HTTP helper. + + * keyserver.c (argsep): New variation on strsep that knows about + optional arguments. + (parse_keyserver_options): Use it here for optional arguments. + +2003-12-28 Stefan Bellon <sbellon@sbellon.de> + + * plaintext.c (handle_plaintext) [__riscos__]: Don't mangle + filename if the user specified it. + + * g10.c, gpgv.c [__riscos__]: Removal of unnecessary #ifdef + __riscos__ sections. + +2003-12-27 David Shaw <dshaw@jabberwocky.com> + + * keyserver.c (strip_leading_space, get_arg): New. + (parse_keyserver_options): Use them here to allow arguments to + keyserver-options. Since none of our options need arguments yet, + just pass them through whole to the keyserver helper. + + * main.h, misc.c (parse_options): Add a "noisy" flag to enable and + disable the messages about which option didn't match or matched + ambiguously. Change all callers (g10.c, keyserver.c). + + * main.h, import.c (import_options), export.c (export_options): + Pass the noisy flag through. + +2003-12-17 David Shaw <dshaw@jabberwocky.com> + + * build-packet.c (write_fake_data, do_secret_key), seckey-cert.c + (do_check): Use an unsigned length for mpi_get_opaque. + + * options.h: It's impolite to assign -1 to an unsigned + opt.force_ownertrust. + + * sig-check.c (cmp_help, do_check), sign.c (do_sign): Remove old + unused code. + + * keyid.c (keyid_from_sk): Make sure lowbits is initialized. + +2003-12-12 David Shaw <dshaw@jabberwocky.com> + + * sig-check.c (do_check): Move the signing algo and hash checks + from here... + (signature_check2): ... to here. + (check_key_signature2): ... and here. This is a minor + optimization to avoid fetching a key (which can be expensive, + especially if it is not self-signed, and there are many key + signatures on it which need to be checked for ultimate trust) if + the signature would have failed anyway because of algorithm or + hash problems. + +2003-12-10 David Shaw <dshaw@jabberwocky.com> + + * packet.h, build-packet.c (hash_public_key): Remove function ... + + * keydb.h, keyid.c (hash_public_key, do_fingerprint_md): ... and + make a new one here that shares code with the fingerprint + calculations. This removes some duplicated functionality, and is + also around 14% faster. (Every bit helps). + + * import.c (import_one): No longer need the Elgamal import + warning. + + * getkey.c (get_pubkey_fast): This one is sort of obscure. + get_pubkey_fast returns the primary key when requesting a subkey, + so if a user has a key signed by a subkey (we don't do this, but + used to), AND that key is not self-signed, AND the algorithm of + the subkey in question is not present in GnuPG, AND the algorithm + of the primary key that owns the subkey in question is present in + GnuPG, then we will try and verify the subkey signature using the + primary key algorithm and hit a BUG(). The fix is to not return a + hit if the keyid is not the primary. All other users of + get_pubkey_fast already expect a primary only. + +2003-12-09 David Shaw <dshaw@jabberwocky.com> + + * keyid.c (do_fingerprint_md): Remove the rules to hash the old v3 + Elgamal keys. They are no longer needed. + + * keyid.c (keyid_from_sk, keyid_from_pk, fingerprint_from_pk, + fingerprint_from_sk): Enforce the v3-is-only-RSA rule. Anything + that isn't RSA gets a zero keyid and fingerprint. + + * keyid.c (do_fingerprint_md): Properly handle hashing of keys + that we don't know the structure of by using the opaque MPI. + (do_fingerprint_md_sk): We cannot calculate the fingerprint from a + secret key unless we know the structure (since we can't leave off + the secret key parts), so fail early..... + (keyid_from_sk, fingerprint_from_sk): .... and return all zeroes. + +2003-12-03 David Shaw <dshaw@jabberwocky.com> + + * g10.c (strusage, main): Show development version warning in + --version output. + (main): Set --bzip2-compress-level to the default value at + startup. Remove --emulate-checksum-bug noop. + + * options.h, g10.c (main), main.h, seskey.c (do_encode_md, + encode_md_value), sig-check.c (do_check), sign.c (do_sign): Remove + --emulate-md-encode-bug as it only applied to Elgamal signatures, + which are going away. + +2003-11-30 David Shaw <dshaw@jabberwocky.com> + + * mainproc.c (proc_symkey_enc, proc_encrypted): Add ability to use + --override-session-key on --symmetric messages (new-style or + old-style). + (proc_pubkey_enc): Move code to show session key from here to + proc_encrypted() so it can work with any type of message. + Suggested by Michael Young. + +2003-11-29 David Shaw <dshaw@jabberwocky.com> + + * trustdb.c (validate_keys): Reset the trustdb before checking if + we have any ultimately trusted keys. This ensures that if we lose + all our ultimately trusted keys, we don't leave behind the old + validity calculations. Noted by Peter Palfrader. + + * revoke.c (gen_desig_revoke): Specify in the comment when a + designated revocation is generated. + + * getkey.c (merge_selfsigs_main, merge_selfsigs_subkey, + get_seckey_byname2): Remove Elgamal check since we are removing + type 20 keys altogether. + +2003-11-27 David Shaw <dshaw@jabberwocky.com> + + * pkclist.c (build_pk_list): Do not allow an empty PK list in + interactive mode. + + * keygen.c (ask_algo): Remove ability to generate Elgamal + sign+encrypt keys. + + * getkey.c (merge_selfsigs_main, merge_selfsigs_subkey, + get_seckey_byname2): Disallow use of sign+encrypt Elgamal keys. + +2003-11-20 David Shaw <dshaw@jabberwocky.com> + + * seskey.c (do_encode_md): Comment about earlier (pre-PGP 2.3) + encodings. + + * misc.c (compress_algo_to_string): Translate "Uncompressed". + Requested by Tommi Vainikainen. + (string_to_compress_algo): Include multi-string for + "uncompressed|none". + +2003-11-17 David Shaw <dshaw@jabberwocky.com> + + * options.h, g10.c (main), compress-bz2.c (init_uncompress): Add + --bz2-compress-lowmem to set bzlib "small" flag for low memory + (but slow) decompression. + +2003-11-15 David Shaw <dshaw@jabberwocky.com> + + * compress.c (init_compress): Remove compress level 10 trick, + since it is no longer needed. + + * g10.c: Fix typoed option name. + + * compress-bz2.c (init_compress): Compression level 0 is not + meaningful for bzip2. + + * options.h, g10.c (main), compress.c (init_compress), + compress-bz2.c (init_compress): Add --compress-level and + --bzip2-compress-level. -z sets them both. Change various + callers. + + * encode.c (encode_simple), sign.c (sign_symencrypt_file): + Properly use default_compress_algo (--compress-algo, followed by + the highest --personal-compress-preference, followed by ZIP) to + get the algorithm. + +2003-11-14 David Shaw <dshaw@jabberwocky.com> + + * options.h, trustdb.c (trust_model_string, init_trustdb): Add + support for "external" trust model, where the user can provide a + pregenerated trustdb. + + * keyedit.c (keyedit_menu): Do not allow editing ownertrust with + an external trust model trustdb. + +2003-11-13 David Shaw <dshaw@jabberwocky.com> + + * options.h, g10.c, keyedit.c, keylist.c, mainproc.c: Clarify the + plurarility (or not) of various list and verify options. + +2003-11-12 David Shaw <dshaw@jabberwocky.com> + + * g10.c (main): Add --symmetric --sign --encrypt. + + * main.h, encode.c (setup_symkey): New. Prompt for a passphrase + and create a DEK for symmetric encryption. + (write_symkey_enc): New. Write out symmetrically encrypted + session keys. + (encode_crypt, encrypt_filter): Use them here here when creating a + message that can be decrypted with a passphrase or a pk. + + * sign.c (sign_file): Call setup_symkey if we are doing a + --symmetric --sign --encrypt. + +2003-11-09 David Shaw <dshaw@jabberwocky.com> + + * mainproc.c (proc_symkey_enc): Don't show algorithm information + when --quiet is set. Suggested by Duncan Harris. Also don't fail + with BUG() when processing a --symmetric message with a cipher we + don't have. + + * g10.c: Alias --personal-xxx-prefs to --personal-xxx-preferences. + + * pkclist.c (build_pk_list): When adding recipients interactively, + allow the user to stop at any point. + +2003-10-31 David Shaw <dshaw@jabberwocky.com> + + * trustdb.h, trustdb.c (register_trusted_keyid): New. Adds a + keyid to the list of ultimately trusted keys. + + * keygen.c (do_generate_keypair): Use it here so that the ultimate + ownertrust happens before the trustdb (might be) rebuilt. Also + fix an error where the newly generated pk is thought to be a + subkey by the trustdb. + + * g10.c (main): Fix --export-all do actually do something + different than --export. + + * pkclist.c (build_pk_list): Show all recipients rather than + showing each recipient as they are added. + + * mainproc.c (proc_symkey_enc, proc_encrypted): Keep a count of + the number of passphrases that can decrypt a symmetric or mixed + symmetric/pk message and include it in the list of keys shown to + the user. + +2003-10-30 David Shaw <dshaw@jabberwocky.com> + + * misc.c (compress_algo_to_string, string_to_compress_algo, + check_compress_algo): Add bzip2. + + * compress.c (compress_filter): Make static to help force the use + of push_compress_filter. Remove default algorithm setting since + that is done in push_compress_filter now. + + * main.h: Use named algorithm. + + * filter.h, compress.c (push_compress_filter, + push_compress_filter2): New. Figure out which is the appropriate + compression filter to use, and push it into place. + + * compress.c (handle_compressed), encode.c (encode_simple, + encode_crypt), sign.c (sign_file, sign_symencrypt_file), import.c + (read_block), export.c (do_export): Use push_compress_filter + instead of pushing the compression filter ourselves. + + * compress-bz2.c: New. Bzlib versions of the compression filter + routines. + + * Makefile.am: Include compress-bz2.c if bz2lib is available. + +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-29 Werner Koch <wk@gnupg.org> + + * cardglue.c (open_card): Ask for card insertion. + (check_card_serialno): New. + (agent_scd_pksign, agent_scd_pkdecrypt): Use it here. + * cardglue.c (open_card): Issue insertion status message. + * status.h, status.c (STATUS_CARDCTRL): New. + + * status.c (cpr_get_answer_okay_cancel): New. + +2003-10-28 Werner Koch <wk@gnupg.org> + + * keylist.c (list_keyblock_print): Denote secrets keys stored on a + card with an '>'. Print the '#' also for subkeys. + (list_keyblock_colon): Introduce new field 15 for sec/ssb to print + the serial number. + +2003-10-26 David Shaw <dshaw@jabberwocky.com> + + * g10.c (main): Enhance the version-specific config file code to + try for more specific matches before giving up (e.g. 1.3.3-cvs, + 1.3.3, 1.3, 1). + +2003-10-25 David Shaw <dshaw@jabberwocky.com> + + * g10.c (main): Add --symmetric --encrypt command. This generates + a message that can be decrypted via a passphrase or public key + system. + + * main.h, encode.c (encode_seskey): Allow passing in an + already-created session key dek. + (encode_simple): Use the actual symmetric cipher when encrypting a + session key for a symmetric message. + (encode_crypt): Add a flag to trigger a hybrid mode that can be + decrypted via a passphrase or a pk. Change all callers. + + * mainproc.c (symkey_decrypt_sesskey): There is no way to tell the + difference here between a bad passphrase and a cipher algorithm + that we don't have, so use a error message that makes that clear. + Use the actual list of ciphers when checking whether a cipher is + invalid. Return error if the decrypted cipher algorithm is + invalid. + (proc_symkey_enc): In a mixed passphrase/pk message, if a valid + dek already exists from decrypting via pk, do not try to process + the passphrase. + (proc_symkey_enc): Indicate when we're decrypting a session key as + opposed to decrypting data. If a passphrase is invalid, discard + the dek so we'll keep trying. + +2003-10-25 Werner Koch <wk@gnupg.org> + + * ccid-driver.c (ccid_open_reader): Return an error if no USB + devices are found. + + * Makefile.am: Replaced INTLLIBS by LIBINTL. + + * g10.c (main) [ENABLE_CARD_SUPPORT]: Add a default for + --pcsc-driver. + + * cardglue.c (learn_status_cb): Fixed faulty use of !space. + +2003-10-24 Werner Koch <wk@gnupg.org> + + * apdu.c (apdu_open_reader): Hacks for PC/SC under Windows. + +2003-10-21 Werner Koch <wk@gnupg.org> + + * passphrase.c (ask_passphrase): Add optional promptid arg. + Changed all callers. + * cardglue.c (pin_cb): Use it here, so the machine interface can + tell whether the Admin PIN is requested. + + * cardglue.c (agent_scd_checkpin): New. + + * misc.c (openpgp_pk_algo_usage): Added AUTH usage. + + * app-openpgp.c (check_against_given_fingerprint): New. Factored + out that code elsewhere. + (do_check_pin): New. + * card-util.c (card_edit): New command "passwd". Add logic to + check the PIN in advance. + (card_status): Add new args to return the serial number. Changed + all callers. + +2003-10-14 David Shaw <dshaw@jabberwocky.com> + + * import.c (import_one): Show the keyid when giving the Elgamal + slow import warning. + + * g10.c (main): Older versions used --comment "" to indicate no + comment. Don't add an empty comment. + +2003-10-13 David Shaw <dshaw@jabberwocky.com> + + * keyedit.c (show_key_with_all_names): Ownertrust is only + meaningful for the PGP or classic trust models. Both validity and + ownertrust are not meaningful for the always trust model. + +2003-10-11 Werner Koch <wk@gnupg.org> + + * keygen.c: Always enable the gen_card_key prototype. + +2003-10-10 Werner Koch <wk@gnupg.org> + + * cardglue.c (card_close): New. + (agent_scd_change_pin): Implemented. + + * 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. + * g10.c (g10_exit): Use close_card. + +2003-10-09 David Shaw <dshaw@jabberwocky.com> + + * g10.c (main): Give a deprecated option warning for + --show-keyring, --show-photos, --show-policy-url, --show-notation, + and their respective no- forms. + + * options.skel: Remove show-photos and replace with + list/verify-options show-photos. Remove no-mangle-dos-filenames. + + * misc.c (parse_options): Allow for incomplete (but unambiguous) + options. + +2003-10-09 Werner Koch <wk@gnupg.org> + + * ccid-driver.c (ccid_transceive): Add T=1 chaining for sending. + + * sign.c (do_sign) [!ENABLE_CARD_SUPPORT]: Return an error for + card keys. + + * cardglue.c (agent_scd_pkdecrypt): Implemented. + * pubkey-enc.c (get_it) [ENABLE_CARD_SUPPORT]: Divert decryption + to card + +2003-10-08 Werner Koch <wk@gnupg.org> + + * cardglue.c (pin_cb): Detect whether an admin or regular PIN is + requested. + (genkey_status_cb): New. + (agent_scd_genkey): Implemented. + + * keygen.c (generate_keypair): New arg CARD_SERIALNO and prepare + parameters for on card key generation. Changed all callers. + (do_generate_keypair): Add new arg card and merged casrd specific + changes from 1.9. + (proc_parameter_file): New arg card, apss it down to + do_generate_keypair and changed all callers. + (gen_card_key): New. + + * g10.c: Include cardclue.h. + (main): s/app_set_default_reader_port/card_set_reader_port/. + * cardglue.c (card_set_reader_port): New to address include file + issues. + +2003-10-02 Werner Koch <wk@gnupg.org> + + * cardglue.c (learn_status_cb): Release values before assignment + so that it can be used by getattr to update the structure. + (agent_scd_getattr): New. + + * keylist.c (print_pubkey_info): Add FP arg for optional printing + to a stream. Changed all callers. + +2003-10-01 Werner Koch <wk@gnupg.org> + + * app-common.h, app-openpgp.c, iso7816.c, iso7816.h, apdu.c + * apdu.h, ccid-driver.c, ccid-driver.h, card-util.c: Updated + from current GnuPG 1.9. Changes over there are: + * card-util.c: Tweaked to use this source also under 1.3. + (card_edit): New command "forcesig". + * card-util.c (print_name, print_isoname): Use 0 and not LF fro + the max_n arg of tty_print_utf8_string2. + * card-util.c (change_pin): Simplified. We now have only a PIN + and an Admin PIN. + * 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 + * 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. + + * Makefile.am (card_support_source): Depend on new AM conditional + to get the ifdef ENABLE_CARD_SUPPORT off the way from source + copied files. + (update-source-from-gnupg-2): Maintainer helper. + +2003-10-01 David Shaw <dshaw@jabberwocky.com> + + * g10.c (main): Add --no-groups to zero --group list. + + * encode.c (encode_simple): Allow for 32 bytes (256 bits) of + symmetrically encrypted session key. Use --s2k-cipher-algo to + choose cipher, rather than the default cipher. + + * parse-packet.c (parse_subkeyenc): Give a warning if an + symmetrically encrypted session key is seen without salt. Show in + --list-packets if a symetrically encrypted session key is present. + + * pubkey-enc.c (get_it): Always show cipher-not-in-prefs warning + unless --quiet is set. Use text name of cipher in warning. + +2003-09-30 David Shaw <dshaw@jabberwocky.com> + + * options.h, g10.c (main), mainproc.c (check_sig_and_print): Add + --verify-option show-unusable-uids. + + * gpgv.c (check_trustdb_stale): Stub. + + * trustdb.c (get_validity): Move the up-to-date check to + check_trustdb_stale (new), so that it can be called before + validity is checked. + + * keylist.c (list_keyblock_print): Disable the overall key + validity display until it can be thought about more. Use + check_trustdb_stale here to avoid putting the check warning in the + middle of a listed key. + + * trustdb.c (init_trustdb): Only verify_own_keys() for those trust + models that it applies to (i.e. classic and OpenPGP). + +2003-09-29 Werner Koch <wk@gnupg.org> + + * keygen.c (do_add_key_flags, parse_parameter_usage): Add support + the proposed AUTH key flag. + * getkey.c (fixup_uidnode, merge_selfsigs_main) + (merge_selfsigs_subkey, premerge_public_with_secret): Ditto. + * keylist.c (print_capabilities): Ditto. + + * parse-packet.c (parse_key): Allow to parse the divert-to-card + S2K mode. + * build-packet.c (do_secret_key): Handle divert-to-card S2K + * seckey-cert.c (is_secret_key_protected): Ditto. + (check_secret_key): Ditto. + + * keygen.c (do_ask_passphrase): Renamed from ask_passphrase. + * passphrase.c (ask_passphrase): New. + +2003-09-28 Werner Koch <wk@gnupg.org> + + * g10.c (main): New commands --card-edit, --card-status and + --change-pin. New options --ctapi-driver, --pcsc-driver and + --disable-ccid + * options.h (DBG_CARD_IO): New. + * cardglue.c, cardclue.h: Enhanced. + * card-util.c: New. Taken from current the gnupg 1.9 branch. + * app-common.h, app-openpgp.c, iso7816.c, iso7816.h, apdu.c + * apdu.h, ccid-driver.c, ccid-driver.h: New. Takem from the current + gnupg 1.9 branch withy minor changes to include directives. + * Makefile.am: Added these files. + +2003-09-27 Werner Koch <wk@gnupg.org> + + * sign.c (do_sign) [ENABLE_CARD_SUPPORT]: Divert to card. + * cardglue.c, cardglue.h: New. + * Makefile.am (gpg_LDADD): Added. + (card_support_sources): New. + +2003-09-25 David Shaw <dshaw@jabberwocky.com> + + * options.h, g10.c (main), keylist.c (list_keyblock_print): Add + "show-unusable-uids" list-option to show revoked and/or expired + user IDs. + +2003-09-24 David Shaw <dshaw@jabberwocky.com> + + * keyedit.c (show_key_with_all_names): Show names a little neater + by putting the [revoked] or [expired] in the space used for the + [validity]. There is also no point in showing "[unknown] + [revoked]". + +2003-09-23 David Shaw <dshaw@jabberwocky.com> + + * sign.c (mk_notation_policy_etc): Capitalize "URL". + + * trustdb.c (validate_keys): Give a little more information while + rebuilding trustdb. + + * pkclist.c (do_edit_ownertrust): Clarify "don't know". + + * g10.c (main): Default to --no-mangle-dos-filenames. + + * keydb.h, keyring.c (keyring_search), trustdb.c (search_skipfnc): + Expand the skipfnc to include a pointer to the user ID that + matched. + + * getkey.c (skip_disabled): Rename to skip_unusable, and add + checks for expired or revoked user IDs. + +2003-09-22 David Shaw <dshaw@jabberwocky.com> + + * g10.c (main): Deprecate --default-comment in favor of + --no-comments. + + * options.h, g10.c (main), armor.c (armor_filter): Allow using + --comment multiple times to get multiple Comment: header lines. + --no-comments resets list. + +2003-09-11 David Shaw <dshaw@jabberwocky.com> + + * g10.c (main): Trim --help to commonly used options. Remove -f. + +2003-09-08 David Shaw <dshaw@jabberwocky.com> + + * g10.c (main): Error out if --multifile is used with the commands + that don't support it yet (--sign, --clearsign, --detach-sign, + --symmetric, and --store). + + * g10.c (main): Add --multifile as an alias to turn --encrypt into + --encrypt-files (plus --verify-files, --decrypt-files). + + * encode.c (use_mdc), g10.c (main): Use RFC1991 and RFC2440 + directly to check for MDC usability. Do not set the force_mdc or + disable_mdc flags since there is no point any longer. + +2003-09-04 David Shaw <dshaw@jabberwocky.com> + + * armor.c (parse_hash_header, armor_filter), g10.c (print_hex, + print_mds), pkclist.c (algo_available): Drop TIGER/192 support. + +2003-09-03 David Shaw <dshaw@jabberwocky.com> + + * keyedit.c (show_key_with_all_names): Fix assertion failure when + using toggle to see a secret key. Reported by Maxim Britov. + +2003-08-31 David Shaw <dshaw@jabberwocky.com> + + * g10.c (add_keyserver_url), keyedit.c (keyedit_menu), sign.c + (mk_notation_policy_etc): Clarify a few strings. It's a + "preferred keyserver URL". + + * g10.c (main): Use "keyserver-url" instead of + "preferred-keyserver" for the sake of short and simple commands. + +2003-08-30 David Shaw <dshaw@jabberwocky.com> + + * main.h, keygen.c (keygen_add_keyserver_url): Signature callback + for adding a keyserver URL. + + * keyedit.c (keyedit_menu, menu_set_keyserver_url): New command to + set preferred keyserver to specified (or all) user IDs. + + * build-packet.c (build_sig_subpkt): Set preferred keyserver flag + while building a preferred keyserver subpacket. + + * keylist.c (show_policy_url, show_keyserver_url): URLs might be + UTF8. + + * keyedit.c (menu_addrevoker): Fix leaking a few bytes. + +2003-08-29 David Shaw <dshaw@jabberwocky.com> + + * keyedit.c (show_key_with_all_names): Use list-option + show-long-keyid in main --edit-key display. + + * keyedit.c (print_and_check_one_sig): Use list-option + show-long-keyid in --edit-key "check" function. + +2003-08-28 David Shaw <dshaw@jabberwocky.com> + + * passphrase.c (agent_send_all_options): Make use of $GPG_TTY. + + * g10.c (main): Disable use-agent if passphrase-fd is given + later. Suggested by Kurt Garloff. + + * exec.c, g10.c, gpgv.c, passphrase.c, photoid.c: + s/__MINGW32__/_WIN32/ to help building on native Windows + compilers. Requested by Brian Gladman. From Werner on stable + branch. + +2003-08-25 David Shaw <dshaw@jabberwocky.com> + + * options.h, g10.c (main): Add list-option + list-preferred-keyserver. + + * keyedit.c (change_passphrase): When responding 'no' to the blank + passphrase question, re-prompt for a new passphrase. This is bug + #202. + + * mainproc.c (check_sig_and_print): Use two different preferred + keyserver displays - one if the key is not present (to tell the + user where to get the key), the other if it is present (to tell + the user where the key can be refreshed). + + * packet.h, parse-packet.c (parse_signature): Set flag if a + preferred keyserver is present. + + * keylist.c (list_keyblock_print): Show keyserver url in listings + with list-option show-keyserver-url. + +2003-08-24 David Shaw <dshaw@jabberwocky.com> + + * Makefile.am: Use NETLIBS instead of EGDLIBS. + + * mainproc.c (check_sig_and_print): Get the uid validity before + printing any sig results to avoid munging the output with trustdb + warnings. + + * g10.c (main): Don't include --show-keyring in --help as it is + deprecated. + +2003-08-21 David Shaw <dshaw@jabberwocky.com> + + * gpgv.c: Remove extra semicolon (typo). + + * options.skel: Note that keyserver.pgp.com isn't synchronized, + and explain the roundrobin a bit better. + + * sig-check.c (check_key_signature2), import.c (import_one, + import_revoke_cert, chk_self_sigs, delete_inv_parts, + collapse_uids, merge_blocks): Make much quieter during import of + slightly munged, but recoverable, keys. Use log_error for + unrecoverable import failures. + + * keyring.c (keyring_rebuild_cache): Comment. + + * sign.c (mk_notation_and_policy): Making a v3 signature with + notations or policy urls is an error, not an info (i.e. increment + the errorcount). Don't print the notation or policy url to stdout + since it can be mixed into the output stream when piping and munge + the stream. + +2003-08-12 David Shaw <dshaw@jabberwocky.com> + + * packet.h, sig-check.c (signature_check2, do_check, + do_check_messages): Provide a signing-key-is-revoked flag. Change + all callers. + + * status.h, status.c (get_status_string): New REVKEYSIG status tag + for a good signature from a revoked key. + + * mainproc.c (do_check_sig, check_sig_and_print): Use it here. + + * import.c (import_revoke_cert, merge_blocks, merge_sigs): Compare + actual signatures on import rather than using keyid or class + matching. This does not change actual behavior with a key, but + does mean that all sigs are imported whether they will be used or + not. + + * parse-packet.c (parse_signature): Don't give "signature packet + without xxxx" warnings for experimental pk algorithms. An + experimental algorithm may not have a notion of (for example) a + keyid (i.e. PGP's x.509 stuff). + +2003-08-02 David Shaw <dshaw@jabberwocky.com> + + * options.h, g10.c (main), keylist.c (list_keyblock_print), + keyedit.c (print_and_check_one_sig): New "show-sig-expire" + list-option to show signature expiration dates (if any). + +2003-07-24 David Shaw <dshaw@jabberwocky.com> + + * options.h, g10.c (main, add_keyserver_url): Add + --sig-preferred-keyserver to implant a "where to get my key" + subpacket into a signature. + + * sign.c (mk_notation_and_policy): Rename to + mk_notation_policy_etc and add preferred keyserver support for + signatures. + +2003-07-21 David Shaw <dshaw@jabberwocky.com> + + * keygen.c (do_add_key_flags): Don't set the certify flag for + subkeys. + (ask_algo): Provide key flags for DSA, Elgamal_e, and Elgamal + subkeys. + (generate_keypair): Provide key flags for the default DSA/Elgamal + keys. + + * sig-check.c (signature_check, signature_check2, + check_key_signature, check_key_signature2): Allow passing NULLs + for unused parameters in the x2 form of each function to avoid the + need for dummy variables. getkey.c, mainproc.c: Change all + callers. + + * trustdb.h, trustdb.c (read_trust_options): New. Returns items + from the trustdb version record. + + * keylist.c (public_key_list): Use it here for the new "tru" + record. + + * gpgv.c (read_trust_options): Stub. + +2003-07-20 David Shaw <dshaw@jabberwocky.com> + + * keyedit.c (show_key_with_all_names): Use list-option + show-validity in --edit-key interface as well. + +2003-07-19 David Shaw <dshaw@jabberwocky.com> + + * options.h, g10.c (main), mainproc.c (check_sig_and_print): Add + verify-options "show-validity" and "show-long-keyid" to show + trustdb validity and long keyids during (file) signature + verification. + + * packet.h, main.h, sig-check.c (signature_check2, + check_key_signature2, do_check): If ret_pk is set, fill in the pk + used to verify the signature. Change all callers in getkey.c, + mainproc.c, and sig-check.c. + + * keylist.c (list_keyblock_colon): Use the ret_pk from above to + put the fingerprint of the signing key in "sig" records during a + --with-colons --check-sigs. This requires --no-sig-cache as well + since we don't cache fingerprints. + +2003-07-10 David Shaw <dshaw@jabberwocky.com> + + * parse-packet.c (parse_signature): No need to reserve 8 bytes for + the unhashed signature cache any longer. + + * misc.c (pct_expando): Add two new expandos - signer's + fingerprint (%g), and signer's primary fingerprint (%p). + + * Makefile.am: Include W32LIBS where appropriate. + + * g10.c (main): Add --rfc2440 alias for --openpgp since in a few + months, they won't be the same thing. + + * keyserver.c (parse_keyserver_uri): Accept "http" as an alias for + "hkp", since it is occasionally written that way. + (keyserver_spawn): Use ascii_isspace to avoid locale issues. + + * keygen.c (ask_user_id): Make --allow-freeform-uid apply to the + email field as well as the name field, and allow mixing fields + when it is set. + + * options.skel: Use subkeys.pgp.net as the default keyserver. + + * trustdb.c (validate_one_keyblock): Certifications on revoked or + expired uids do not count in the web of trust. + + * signal.c (init_one_signal, pause_on_sigusr, do_block): Only use + sigprocmask() if we have sigset_t, and only use sigaction() if we + have struct sigaction. This is for Forte c89 on Solaris which + seems to define only the function call half of the two pairs by + default. + (pause_on_sigusr): Typo. + (do_block): If we can't use sigprocmask() and sigset_t, try to get + the number of signals from NSIG as well as MAXSIG, and if we + can't, fail with an explanation. + + * signal.c, tdbio.c: Comment out the transaction code. It was not + used in this version, and was causing some build problems on + quasi-posix platforms (Solaris and Forte c89). + + * keylist.c (list_keyblock_colon): Don't include validity values + when listing secret keys since they can be incorrect and/or + misleading. This is a temporary kludge, and will be handled + properly in 1.9/2.0. + + * mainproc.c (check_sig_and_print): Only show the "key available + from" preferred keyserver line if the key is not currently + present. + + * keyedit.c (sign_uids): Do not sign expired uids without --expert + (same behavior as revoked uids). Do not allow signing a user ID + without a self-signature. --expert overrides. Add additional + prompt to the signature level question. + (menu_expire): When changing expiration dates, don't replace + selfsigs on revoked uids since this would effectively unrevoke + them. There is also no point in replacing expired selfsigs. This + is bug #181 + +2003-07-10 David Shaw <dshaw@jabberwocky.com> (from Werner on stable branch) + + * g10.c (add_notation_data): Make sure that only ascii is passed + to iscntrl. Noted by Christian Biere. + * getkey.c (classify_user_id2): Replaced isspace by spacep + * keygen.c (ask_user_id): Ditto. + (get_parameter_algo): Ditto. + * keyedit.c (keyedit_menu): Ditto. + * tdbdump.c (import_ownertrust): Ditto. s/isxdigit/hexdigitp/. + * revoke.c (ask_revocation_reason): + * keyserver.c (keyserver_spawn): Dito. + +2003-06-10 Werner Koch <wk@gnupg.org> + + * parse-packet.c (parse): Disallow old style partial length for + all key material packets to avoid possible corruption of keyrings. + +2003-06-08 Werner Koch <wk@gnupg.org> + + * import.c (import_keys_internal): Invalidate the cache so that + the file descriptor gets closed. Fixes bug reported by Juan + F. Codagnone. + +2003-06-04 David Shaw <dshaw@jabberwocky.com> + + * options.skel: Use new hkp://subkeys.pgp.net as sample keyserver + since they at least handle subkeys correctly. + + * options.h, g10.c (main), main.h, keylist.c (show_keyserver_url), + mainproc.c (check_sig_and_print), parse-packet.c (dump_sig_subpkt, + parse_one_sig_subpkt, can_handle_critical): Add read-only support + for preferred keyserver subpackets. They're basically policy URLs + with a different name. Add a verify-option + "show-preferred-keyserver" to turn them on and off (on by default, + as per stable branch). + + * g10.c (main): Add "--set-notation" as alias to "--notation-data" + this is to make things consistent with --set-policy-url meaning + both sigs and certs. + +2003-06-03 David Shaw <dshaw@jabberwocky.com> + + * options.h, g10.c (main), keylist.c (list_keyblock_print): Add + "show-validity" and "show-long-keyid" list-options. + + * gpgv.c (get_validity, trust_value_to_string): Stubs. + + * g10.c (main): Use SAFE_VERSION instead of VERSION in the + version-specific gpg.conf file so it can be overridden on RISCOS. + +2003-06-01 David Shaw <dshaw@jabberwocky.com> + + * g10.c (main), keylist.c (show_policy_url, show_notation), + mainproc.c (check_sig_and_print): Emulate the old policy and + notation behavior (display by default). Send to status-fd whether + it is displayed on the screen or not. + + * g10.c (main): Since we now have some options in devel that won't + work in a stable branch gpg.conf file, try for a version-specific + gpg.conf-VERSION file before falling back to gpg.conf. + + * main.h, options.h: Move various option flags to options.h. + +2003-05-31 David Shaw <dshaw@jabberwocky.com> + + * mainproc.c (check_sig_and_print), main.h, keylist.c + (show_policy, show_notation): Collapse the old print_notation_data + into show_policy() and show_notation() so there is only one + function to print notations and policy URLs. + + * options.h, main.h, g10.c (main), keyedit.c + (print_and_check_one_sig), keylist.c (list_one, + list_keyblock_print), pkclist.c (do_edit_ownertrust), sign.c + (mk_notation_and_policy): New "list-options" and "verify-options" + commands. These replace the existing + --show-photos/--no-show-photos, + --show-notation/--no-show-notation, + --show-policy-url/--no-show-policy-url, and --show-keyring + options. The new method is more flexible since a user can specify + (for example) showing photos during sig verification, but not in + key listings. The old options are emulated. + + * main.h, misc.c (parse_options): New general option line + parser. Fix the bug in the old version that did not handle report + syntax errors after a valid entry. + + * import.c (parse_import_options), export.c + (parse_export_options): Call it here instead of duplicating the + code. + +2003-05-30 David Shaw <dshaw@jabberwocky.com> + + * keylist.c (list_one): Don't show the keyring filename when in + --with-colons mode. Actually translate "Keyring" string. + + * mainproc.c (proc_tree): We can't currently handle multiple + signatures of different classes or digests (we'd pretty much have + to run a different hash context for each), but if they are all the + same, make an exception. This is Debian bug #194292. + + * sig-check.c (check_key_signature2): Make string translatable. + + * packet.h, getkey.c (fixup_uidnode): Mark real primary uids + differently than assumed primaries. + + * keyedit.c (no_primary_warning): Use the differently marked + primaries here in a new function to warn when an --edit-key + command might rearrange the self-sig dates enough to change which + uid is primary. + (menu_expire, menu_set_preferences): Use no_primary_warning() + here. + + * Makefile.am: Use @DLLIBS@ for -ldl. + +2003-05-26 David Shaw <dshaw@jabberwocky.com> + + * getkey.c (premerge_public_with_secret): Made "no secret subkey + for" warning a verbose item and translatable. (From wk on stable + branch) + + * sig-check.c (check_key_signature2): Made "no subkey for subkey + binding packet" a verbose item instead of a !quiet one. There are + too many garbled keys out in the wild. (From wk on stable branch) + + * filter.h: Remove const from WHAT. (From wk on stable branch) + + * progress.c (handle_progress): Store a copy of + NAME. (progress_filter): Release WHAT, make sure not to print a + NULL WHAT. (From wk on stable branch) + + * openfile.c (open_sigfile): Adjust free for new progress + semantics. (From wk on stable branch) + + * plaintext.c (ask_for_detached_datafile): Don't dealloc + pfx->WHAT. (From wk on stable branch) + + * seckey-cert.c (do_check): Issue the RSA_OR_IDEA status when the + cipher algo is IDEA to make it easier to track down the + problem. (From twoaday on stable branch) + +2003-05-24 David Shaw <dshaw@jabberwocky.com> + + * armor.c, g10.c, kbnode.c, misc.c, pkclist.c, sign.c, + build-packet.c, getkey.c, keydb.c, openfile.c, plaintext.c, + status.c, gpgv.c, keygen.c, options.h, sig-check.c, tdbio.h, + encode.c, mainproc.c, parse-packet.c, signal.c, textfilter.c: Edit + all preprocessor instructions to remove whitespace before the '#'. + This is not required by C89, but there are some compilers out + there that don't like it. + +2003-05-21 David Shaw <dshaw@jabberwocky.com> + + * trustdb.h, trustdb.c (is_disabled), gpgv.c (is_disabled): Rename + is_disabled to cache_disabled_value, which now takes a pk and not + just the keyid. This is for speed since there is no need to + re-fetch a key when we already have that key handy. Cache the + result of the check so we don't need to hit the trustdb more than + once. + + * getkey.c (skip_disabled): New function to get a pk and call + is_disabled on it. (key_byname): Use it here. + + * packet.h, getkey.c (skip_disabled), keylist.c + (print_capabilities): New "pk_is_disabled" macro to retrieve the + cached disabled value if available, and fill it in via + cache_disabled_value if not available. + + * trustdb.c (get_validity): Cache the disabled value since we have + it handy and it might be useful later. + + * parse-packet.c (parse_key): Clear disabled flag when parsing a + new key. Just in case someone forgets to clear the whole key. + + * getkey.c (merge_selfsigs_main): Add an "if all else fails" path + for setting a single user ID primary when there are multiple set + primaries all at the same second, or no primaries set and the most + recent user IDs are at the same second, or no signed user IDs at + all. This is arbitrary, but deterministic. + + * exec.h, photoid.h: Add copyright message. + + * keylist.c (list_keyblock_print): Don't dump attribs for + revoked/expired/etc uids for non-colon key listings. This is for + consistency with --show-photos. + + * main.h, keylist.c (dump_attribs), mainproc.c + (check_sig_and_print): Dump attribs if --attrib-fd is set when + verifying signatures. + + * g10.c (main): New --gnupg option to disable the various + --openpgp, --pgpX, etc. options. This is the same as --no-XXXX + for those options. + + * revoke.c (ask_revocation_reason): Clear old reason if user + elects to repeat question. This is bug 153. + + * keyedit.c (sign_uids): Show keyid of the key making the + signature. + +2003-05-21 Werner Koch <wk@gnupg.org> + + * progress.c (handle_progress) + * sign.c (write_plaintext_packet) + * encode.c (encode_simple,encode_crypt): Make sure that a filename + of "-" is considered to be stdin so that iobuf_get_filelength + won't get called. This fixes bug 156 reported by Gregery Barton. + +2003-05-02 David Shaw <dshaw@jabberwocky.com> + + * packet.h, build-packet.c (build_sig_subpkt), export.c + (do_export_stream), import.c (remove_bad_stuff, import), + parse-packet.c (dump_sig_subpkt, parse_one_sig_subpkt): Remove + vestigal code for the old sig cache subpacket. This wasn't + completely harmless as it caused subpacket 101 to disappear on + import and export. + + * options.h, armor.c, cipher.c, g10.c, keyedit.c, pkclist.c, + sign.c, encode.c, getkey.c, revoke.c: The current flags for + different levels of PGP-ness are massively complex. This is step + one in simplifying them. No functional change yet, just use a + macro to check for compliance level. + + * sign.c (sign_file): Fix bug that causes spurious compression + preference warning. + + * sign.c (clearsign_file): Fix bug that prevents proper warning + message from appearing when clearsigning in --pgp2 mode with a + non-v3 RSA key. + + * main.h, misc.c (compliance_option_string, compliance_string, + compliance_failure), pkclist.c (build_pk_list), sign.c (sign_file, + clearsign_file), encode.c (encode_crypt, + write_pubkey_enc_from_list): New functions to put the "this + message may not be usable...." warning in one place. + + * options.h, g10.c (main): Part two of the simplification. Use a + single enum to indicate what we are compliant to (1991, 2440, + PGPx, etc.) + + * g10.c (main): Show errors for failure in export, send-keys, + recv-keys, and refresh-keys. + + * options.h, g10.c (main): Give algorithm warnings for algorithms + chosen against the --pgpX and --openpgp rules. + + * keydb.h, pkclist.c (algo_available): Make TIGER192 invalid in + --openpgp mode. + + * sign.c (sign_file), pkclist.c (algo_available): Allow passing a + hint of 0. + +2003-05-01 David Shaw <dshaw@jabberwocky.com> + + * tdbio.c (create_version_record): Only create new trustdbs with + TM_CLASSIC or TM_PGP. + + * trustdb.h, trustdb.c (trust_string, get_ownertrust_string, + get_validity_string, ask_ownertrust, validate_keys), pkclist.c + (do_edit_ownertrust): Rename trust_string to trust_value_to_string + for naming consistency. + + * trustdb.h, trustdb.c (string_to_trust_value): New function to + translate a string to a trust value. + + * g10.c (main): Use string_to_trust_value here for + --force-ownertrust. + + * options.h, g10.c (main), trustdb.c (trust_model_string, + init_trustdb, check_trustdb, update_trustdb, get_validity, + validate_one_keyblock): An "OpenPGP" trust model is misleading + since there is no official OpenPGP trust model. Use "PGP" + instead. + +2003-04-30 David Shaw <dshaw@jabberwocky.com> + + * build-packet.c (build_sig_subpkt): Comments. + + * exec.c (exec_write): Cast NULL to void* to properly terminate + varargs list. + + * keyedit.c (show_key_with_all_names): Just for safety, catch an + invalid pk algorithm. + + * sign.c (make_keysig_packet): Crucial that the call to mksubpkt + comes LAST before the calls to finalize the sig as that makes it + possible for the mksubpkt function to get a reliable pointer to + the subpacket area. + + * pkclist.c (do_we_trust_pre): If an untrusted key was chosen by a + particular user ID, use that ID as the one to ask about when + prompting whether to use the key anyway. + (build_pk_list): Similar change here when adding keys to the + recipient list. + + * trustdb.c (update_validity): Fix bug that prevented more than + one validity record per trust record. + (get_validity): When retrieving validity for a (user) supplied + user ID, return the validity for that user ID only, and do not + fall back to the general key validity. + (validate_one_keyblock): Some commentary on whether + non-self-signed user IDs belong in the web of trust (arguably, + they do). + +2003-04-27 David Shaw <dshaw@jabberwocky.com> + + * g10.c (main): Add --no-textmode. + + * export.c (do_export_stream), keyedit.c (show_key_with_all_names, + menu_addrevoker), mainproc.c (check_sig_and_print), photoid.c + (show_photos), sign.c (mk_notation_and_policy), trustdb.c + (get_validity, reset_trust_records, validate_keys): Make some + strings translatable. + + * mainproc.c (check_sig_and_print): Show digest algorithm and sig + class when verifying a sig with --verbose on, and add version, pk + and hash algorithms and sig class to VALIDSIG. + + * parse-packet.c (enum_sig_subpkt): Make a warning message a + --verbose warning message since we don't need to warn every time + we see an unknown critical (we only need to invalidate the + signature). + + * trustdb.c (init_trustdb): Check the trustdb options even with + TM_AUTO since the auto may become TM_CLASSIC or TM_OPENPGP. + +2003-04-26 David Shaw <dshaw@jabberwocky.com> + + * sign.c (do_sign): Show the hash used when making a signature in + verbose mode. + + * tdbio.h, tdbio.c (tdbio_read_model): New function to return the + trust model used in a given trustdb. + + * options.h, g10.c (main), trustdb.c (init_trustdb, check_trustdb, + update_trustdb): Use tdbio_read_model to implement an "auto" trust + model which is set via the trustdb. + +2003-04-23 David Shaw <dshaw@jabberwocky.com> + + * import.c (import_revoke_cert): Remove ultimate trust when + revoking an ultimately trusted key. + + * keyedit.c (sign_uids): Allow replacing expired signatures. + Allow duplicate signatures with --expert. + + * pkclist.c (check_signatures_trust): Don't display a null + fingerprint when checking a signature with --always-trust enabled. + + * filter.h (progress_filter_context_t), progress.c + (handle_progress), plaintext.c (ask_for_detached_datafile, + hash_datafiles): Fix compiler warnings. Make "what" constant. + + * build-packet.c (do_plaintext): Do not create invalid literal + packets with >255-byte names. + +2003-04-15 Werner Koch <wk@gnupg.org> + + * Makefile.am (AM_CFLAGS): Make use of AM_CFLAGS and AM_LDFLAGS. + + * g10.c, options.h: New option --enable-progress-filter. + * progress.c (handle_progress): Make use of it. + +2003-04-15 Marcus Brinkmann <marcus@g10code.de> + + * progress.c: New file. + * Makefile.am (common_source): Add progress.c. + * filter.h (progress_filter_context_t): New type. + (progress_filter, handle_progress): New prototypes. + * main.h (open_sigfile): New argument for prototype. + * openfile.c (open_sigfile): New argument to install progress + filter. + * encode.c (encode_simple): New variable PFX. Register + progress filter. Install text_filter after that. + (encode_crypt): Likewise. + * sign.c (sign_file): Likewise. + (clearsign_file): Likewise. + * decrypt.c (decrypt_message): Likewise. + (decrypt_messages): Likewise. + * verify.c (verify_signatures): Likewise. + (verify_one_file): Likewise. + * plaintext.c (hash_datafiles): Likewise. + (ask_for_detached_datafile): Likewise. + +2003-04-10 Werner Koch <wk@gnupg.org> + + * passphrase.c (read_passphrase_from_fd): Do a dummy read if the + agent is to be used. Noted by Ingo Klöcker. + (agent_get_passphrase): Inhibit caching when we have no + fingerprint. This is required for key generation as well as for + symmetric only encryption. + + * passphrase .c (agent_get_passphrase): New arg CANCELED. + (passphrase_to_dek): Ditto. Passed to above. Changed all + callers to pass NULL. + * seckey-cert.c (do_check): New arg CANCELED. + (check_secret_key): Terminate loop when canceled. + + * keyedit.c (change_passphrase): Pass ERRTEXT untranslated to + passphrase_to_dek and translate where appropriate. + * seckey-cert.c (check_secret_key): Ditto. + * keygen.c (ask_passphrase): Ditto. + * passphrase.c (agent_get_passphrase): Translate the TRYAGAIN_TEXT. + Switch the codeset to utf-8. + +2003-04-09 Werner Koch <wk@gnupg.org> + + * decrypt.c (decrypt_messages): Fixed error handling; the function + used to re-loop with same file after an error. Reported by Joseph + Walton. + +2003-04-08 David Shaw <dshaw@jabberwocky.com> + + * main.h, g10.c (main), import.c (parse_import_options, + fix_pks_corruption): It's really PKS corruption, not HKP + corruption. Keep the old repair-hkp-subkey-bug command as an + alias. + + * g10.c (main): Rename --no-version to --no-emit-version for + consistency. Keep --no-version as an alias. + +2003-04-04 David Shaw <dshaw@jabberwocky.com> + + * pkclist.c (algo_available): PGP 8 can use the SHA-256 hash. + + * sign.c (sign_file, clearsign_file, sign_symencrypt_file): Remove + unused code. + +2003-04-01 Werner Koch <wk@gnupg.org> + + * mainproc.c (check_sig_and_print): Add primary key fpr to VALIDSIG + status. + +2003-03-24 David Shaw <dshaw@jabberwocky.com> + + * keydb.h: Err on the side of making an unknown signature a SIG + rather than a CERT. + + * import.c (delete_inv_parts): Discard any key signatures that + aren't key types (i.e. 0x00, 0x01, etc.) + + * g10.c (main): Add deprecated option warning for + --list-ownertrust. Add --compression-algo alias for + --compress-algo. Change --version output strings to match + "showpref" strings, and make translatable. + + * status.c (do_get_from_fd): Accept 'y' as well as 'Y' for + --command-fd boolean input. + + * trustdb.c: Fix typo (DISABLE_REGEXP -> DISABLE_REGEX) + + * keyedit.c (show_key_with_all_names_colon): Show no-ks-modify + flag. + +2003-03-11 David Shaw <dshaw@jabberwocky.com> + + * options.h, g10.c (main), keyserver.c (kopts): Add "try-dns-srv" + keyserver option. Defaults to on. + + * passphrase.c (agent_get_passphrase): Fix memory leak with + symmetric messages. Fix segfault with symmetric messages. Fix + incorrect prompt with symmetric messages. + +2003-03-10 Werner Koch <wk@gnupg.org> + + * compress.c (init_uncompress): Use a 15 bit window size so that + the output of implementations which don't run for PGP 2 + compatibility won't get garbled. + +2003-03-04 David Shaw <dshaw@jabberwocky.com> + + * trustdb.c (validate_keys): Mask the ownertrust when building the + list of fully valid keys so that disabled keys are still counted + in the web of trust. + (get_ownertrust_with_min): Do the same for the minimum ownertrust + calculation. + + * parse-packet.c (dump_sig_subpkt): Show the notation names for + not-human-readable notations. Fix cosmetic off-by-one length + counter. + + * options.skel: Add explantion and commented-out + "no-mangle-dos-filenames". + + * mainproc.c (proc_encrypted): Make string translatable. + + * keyserver.c (keyserver_spawn): Quote ':', '%', and any 8-bit + characters in the uid strings sent to the keyserver helper. + + * keyring.c (keyring_rebuild_cache): Lock the keyring while + rebuilding the signature caches to prevent another gpg from + tampering with the temporary copy. + + * keygen.c (keygen_set_std_prefs): Include AES192 and AES256 in + default prefs. + + * keyedit.c (show_prefs): Make strings translatable. + + * keydb.c: Double the maximum number of keyrings to 40. + + * gpgv.c (main): Fix bug #113 - gpgv should accept the + --ignore-time-conflict option. + + * g10.c (main): --openpgp disables --pgpX. Double the amount of + secure memory to 32k (keys are getting bigger these days). + + * Makefile.am: Makefile.am: Use @CAPLIBS@ to link in -lcap if we + are using capabilities. + +2003-02-26 David Shaw <dshaw@jabberwocky.com> + + * keyserver.c (keyserver_spawn): Include various pieces of + information about the key in the data sent to the keyserver + helper. This allows the helper to use it in instructing a remote + server which may not have any actual OpenPGP smarts in parsing + keys. + + * main.h, export.c (export_pubkeys_stream, do_export_stream): Add + ability to return only the first match in an exported keyblock for + keyserver usage. This should be replaced at some point with a + more flexible solution where each key can be armored seperately. + +2003-02-22 David Shaw <dshaw@jabberwocky.com> + + * sign.c (sign_file): Do not push textmode filter onto an unopened + IOBUF (segfault). Noted by Marcus Brinkmann. Push and + reinitialize textmode filter for each file in a multiple file + list. + + * packet.h, getkey.c (fixup_uidnode), keyedit.c (show_prefs): Set + and show the keyserver no-modify flag. + + * keygen.c (add_keyserver_modify): New. + (keygen_upd_std_prefs): Call it here. + (keygen_set_std_prefs): Accept "ks-modify" and "no-ks-modify" as + prefs to set and unset keyserver modify flag. + + * g10.c (main): Accept "s1" in addition to "idea" to match the + other ciphers. + + * main.h, misc.c (idea_cipher_warn): We don't need this if IDEA + has been disabled. + +2003-02-21 David Shaw <dshaw@jabberwocky.com> + + * keygen.c (keygen_set_std_prefs): Don't put AES or CAST5 in + default prefs if they are disabled. + + * g10.c (main): Use 3DES instead of CAST5 if we don't have CAST5 + support. Use 3DES for the s2k cipher in --openpgp mode. + (print_mds): #ifdef all of the optional digest algorithms. + +2003-02-12 David Shaw <dshaw@jabberwocky.com> + + * keydb.h, getkey.c (classify_user_id, classify_user_id2): Make + 'exact' a per-desc item. Merge into one function since + 'force_exact' is no longer needed. + (key_byname): Use new classify_user_id function, and new exact + flag in KEYDB_SEARCH_DESC. + + * keyring.h, keyring.c (keyring_search): Return an optional index + to show which KEYDB_SEARCH_DESC was the matching one. + + * keydb.h, keydb.c (keydb_search): Rename to keydb_search2, and + pass the optional index to keyring_search. Add a macro version of + keydb_search that calls this new function. + + * export.c (do_export_stream): If the keyid! syntax is used, + export only that specified key. If the key in question is a + subkey, export the primary plus that subkey only. + +2003-02-11 David Shaw <dshaw@jabberwocky.com> + + * exec.c (set_exec_path): Add debugging line. + + * g10.c (print_hex, print_mds): Print long hash strings a lot + neater. This assumes at least an 80-character display, as there + are a few other similar assumptions here and there. Users who + need unformatted hashes can still use with-colons. Check that + SHA384 and 512 are available before using them as they are no + longer always available. + + * Makefile.am: Use a local copy of libexecdir along with @PACKAGE@ + as GNUPG_LIBEXECDIR so it can be easily overridden at make time. + +2003-02-04 David Shaw <dshaw@jabberwocky.com> + + * armor.c (parse_hash_header, armor_filter): Accept the new SHAs + in the armor Hash: header. + + * g10.c (print_hex): Print long hash strings a little neater. + (print_mds): Add the new SHAs to the hash list. + +2003-02-02 David Shaw <dshaw@jabberwocky.com> + + * keyedit.c (menu_revuid): Properly handle a nonselfsigned uid on + a v4 key (treat as a v4 revocation). + + * import.c (print_import_check): Do not re-utf8 convert user IDs. + +2003-01-27 David Shaw <dshaw@jabberwocky.com> + + * mainproc.c (list_node): Show signature expiration date in + with-colons sig records. + + * keylist.c (list_keyblock_colon), mainproc.c (list_node): Show + trust sig information in with-colons sig records. + +2003-01-16 David Shaw <dshaw@jabberwocky.com> + + * g10.c (add_group): Trim whitespace after a group name so it does + not matter where the user puts the = sign. + + * options.skel: Comment out the first three lines in case someone + manually copies the skel file to their homedir. + + * sign.c (clearsign_file): Only use pgp2mode with v3 keys and + MD5. This matches what we do when decoding such messages and + prevents creating a message (v3+RIPEMD/160) that we can't verify. + + * sig-check.c (signature_check2): Use G10ERR_GENERAL as the error + for signature digest conflict. BAD_SIGN implies that a signature + was checked and we may try and print out a user ID for a key that + doesn't exist. + +2003-01-15 David Shaw <dshaw@jabberwocky.com> + + * trustdb.c (init_trustdb, get_validity): Don't use a changed + trust model to indicate a dirty trustdb, and never auto-rebuild a + dirty trustdb with the "always" trust model. + + * g10.c (add_group): Last commit missed the \t ;) + +2003-01-14 David Shaw <dshaw@jabberwocky.com> + + * packet.h, parse-packet.c (setup_user_id), free-packet.c + (free_user_id), keydb.h, keyid.c (namehash_from_uid): New function + to rmd160-hash the contents of a user ID packet and cache it in + the uid object. + + * keylist.c (list_keyblock_colon): Use namehash in field 8 of + uids. Show dates for creation (selfsig date), and expiration in + fields 6 and 7. + + * trustdb.c (get_validity, get_validity_counts, update_validity): + Use new namehash function rather than hashing it locally. + +2003-01-14 Werner Koch <wk@gnupg.org> + + * g10.c (add_group): Fixed group parsing to allow more than one + delimiter in a row and also allow tab as delimiter. + +2003-01-12 David Shaw <dshaw@jabberwocky.com> + + * tdbio.c (tdbio_set_dbname): Fix assertion failure with + non-fully-qualified trustdb names. + +2003-01-11 David Shaw <dshaw@jabberwocky.com> + + * trustdb.c (get_validity_info, get_ownertrust_info, + trust_letter): Simplify by returning a ? for error directly. + + * keyedit.c (show_key_with_all_names): Use get_validity_string and + get_ownertrust_string to show full word versions of trust + (i.e. "full" instead of 'f'). + + * trustdb.h, trustdb.c (get_ownertrust_string, + get_validity_string): Same as get_ownertrust_info, and + get_validity_info, except returns a full string. + + * trustdb.c (get_ownertrust_with_min): New. Same as + 'get_ownertrust' but takes the min_ownertrust value into account. + +2003-01-10 David Shaw <dshaw@jabberwocky.com> + + * armor.c (armor_filter): Comment about PGP's end of line tab + problem. + + * trustdb.h, trustdb.c (trust_letter): Make + static. (get_ownertrust_info, get_validity_info): Don't mask the + trust level twice. + + * trustdb.h, gpgv.c, trustdb.c (get_validity, get_validity_info), + keylist.c (list_keyblock_colon), keyedit.c + (show_key_with_all_names_colon, menu_revuid): Pass a user ID in + rather than a namehash, so we only have to do the hashing in one + place. + + * packet.h, pkclist.c (build_pk_list), free-packet.c + (release_public_key_parts): Remove unused namehash element for + public keys. + +2003-01-07 David Shaw <dshaw@jabberwocky.com> + + * keygen.c (keygen_set_std_prefs): Warn when setting an IDEA + preference when IDEA is not available. + +2003-01-06 David Shaw <dshaw@jabberwocky.com> + + * trustdb.c (get_validity_info): 'd' for disabled is not a + validity value any more. + + * packet.h, tdbio.h, tdbio.c (tdbio_read_record, + tdbio_write_record), trustdb.c (update_validity): Store temporary + full & marginal counts in the trustdb. + (clear_validity, get_validity_counts): Return and clear temp + counts. + (store_validation_status): Keep track of which keyids have been + stored. + (validate_one_keyblock, validate_key_list): Use per-uid copies of + the full & marginal counts so they can be recalled for multiple + levels. + (validate_keys): Only use unused keys for each new round. + (reset_unconnected_keys): Rename to reset_trust_records, and only + skip specifically excluded records. + + * keylist.c (print_capabilities): Show 'D' for disabled keys in + capabilities section. + + * trustdb.c (is_disabled): Remove incorrect comment. + +2003-01-03 David Shaw <dshaw@jabberwocky.com> + + * import.c (import_one): Only do the work to create the status + display for interactive import if status is enabled. + + * keyring.c (keyring_search): skipfnc didn't work properly with + non-keyid searches. Noted by Stefan Bellon. + + * getkey.c (merge_selfsigs_main): Remove some unused code and make + sure that the pk selfsigversion member accounts for 1F direct + sigs. + +2003-01-02 Werner Koch <wk@gnupg.org> + + * keydb.c (keydb_add_resource): Don't assume that try_make_homedir + terminates but check again for the existence of the directory and + continue then. + * openfile.c (copy_options_file): Print a warning if the skeleton + file has active options. + +2002-12-29 David Shaw <dshaw@jabberwocky.com> + + * getkey.c (merge_selfsigs_main), main.h, sig-check.c + (check_key_signature2): Pass the ultimately trusted pk directly to + check_key_signature2 to avoid going through the key selection + mechanism. This prevents a deadly embrace when two keys without + selfsigs each sign the other. + +2002-12-27 David Shaw <dshaw@jabberwocky.com> + + * keyserver.c (keyserver_refresh): Don't print the "refreshing..." + line if there are no keys to refresh or if there is no keyserver + set. + + * getkey.c (merge_selfsigs_main): Any valid user ID should make a + key valid, not just the last one. This also fixes Debian bug + #174276. + +2002-12-27 Stefan Bellon <sbellon@sbellon.de> + + * import.c (print_import_check): Changed int to size_t. + +2002-12-27 David Shaw <dshaw@jabberwocky.com> + + * keyedit.c (keyedit_menu, menu_revuid): Add "revuid" feature to + revoke a user ID. This is the same as issuing a revocation for + the self-signature, but a much simpler interface to do it. + +2002-12-26 David Shaw <dshaw@jabberwocky.com> + + * keydb.h, getkey.c (key_byname): Flag to enable or disable + including disabled keys. Keys specified via keyid (i.e. 0x...) + are always included. + + * getkey.c (get_pubkey_byname, get_seckey_byname2, + get_seckey_bynames), keyedit.c (keyedit_menu, menu_addrevoker): + Include disabled keys in these functions. + + * pkclist.c (build_pk_list): Do not include disabled keys for -r + or the key prompt. Do include disabled keys for the default key + and --encrypt-to. + + * trustdb.h, trustdb.c (is_disabled): New skipfnc for skipping + disabled keys. + + * gpgv.c (is_disabled): Stub. + + * keygen.c (keygen_add_key_expire): Properly handle updating a key + expiration to a no-expiration value. + + * keyedit.c (enable_disable_key): Comment. + + * import.c (import_one): When in interactive mode and --verbose, + don't repeat some key information twice. + +2002-12-22 Timo Schulz <ts@winpt.org> + + * import.c (print_import_check): New. + (import_one): Use it here. + Use merge_keys_and_selfsig in the interactive mode to avoid + wrong key information. + * status.h: Add new status code. + * status.c: Ditto. + +2002-12-13 David Shaw <dshaw@jabberwocky.com> + + * pkclist.c (do_we_trust): Tweak language to refer to the "named + user" rather than "owner". Noted by Stefan Bellon. + + * trustdb.h, trustdb.c (trustdb_pending_check): New function to + check if the trustdb needs a check. + + * import.c (import_keys_internal): Used here so we don't rebuild + the trustdb if it is still clean. + (import_one, chk_self_sigs): Only mark trustdb dirty if the key + that is being imported has any sigs other than self-sigs. + Suggested by Adrian von Bidder. + + * options.skel: Include the required '=' sign in the sample + 'group' option. Noted by Stefan Bellon. + + * import.c (chk_self_sigs): Don't try and check a subkey as if it + was a signature. + +2002-12-11 David Shaw <dshaw@jabberwocky.com> + + * tdbio.c (tdbio_read_record, tdbio_write_record): Compact the + RECTYPE_TRUST records a bit. + + * g10.c (main): Comment out --list-trust-path until it can be + implemented. + + * import.c (import_one): Warn when importing an Elgamal primary + that this may take some time (to verify self-sigs). + (chk_self_sigs): Try and cache all self-sigs so the keyblock is + written to the keyring with a good rich cache. + + * keygen.c (ask_algo): Make the Elgamal sign+encrypt warning + stronger, and remove the RSA sign+encrypt warning. + +2002-12-06 Stefan Bellon <sbellon@sbellon.de> + + * options.h: Fixed typo (mangle_dos_names instead of + mangle_dos_filenames). + +2002-12-05 Werner Koch <wk@gnupg.org> + + * g10.c: New options --[no-]mangle-dos-filenames. + * options.h (opt): Added mangle-dos-filenames. + * openfile.c (open_outfile) [USE_ONLY_8DOT3]: Truncate the + filename only when this option is set; this is the default. + +2002-12-04 David Shaw <dshaw@jabberwocky.com> + + * main.h, keyedit.c, keygen.c: Back out previous (2002-12-01) + change. Minimal isn't always best. + + * sign.c (update_keysig_packet): Use the current time rather then + a modification of the original signature time. Make sure that + this doesn't cause a time warp. + + * keygen.c (keygen_add_key_expire): Properly handle a key + expiration date in the past (use a duration of 0). + + * keyedit.c (menu_expire): Use update_keysig_packet so any sig + subpackets are maintained during the update. + + * build-packet.c (build_sig_subpkt): Mark sig expired or unexpired + when the sig expiration subpacket is added. + (build_sig_subpkt_from_sig): Handle making an expiration subpacket + from a sig that has already expired (use a duration of 0). + + * packet.h, sign.c (update_keysig_packet), keyedit.c + (menu_set_primary_uid, menu_set_preferences): Add ability to issue + 0x18 subkey binding sigs to update_keysig_packet and change all + callers. + + * trustdb.c (validate_keys): Show trust parameters when building + the trustdb, and make sure that the version record update was + successful. + (init_trustdb): If the current parameters aren't what was used for + building the trustdb, the trustdb is invalid. + + * tbio.c (tdbio_db_matches_options): Update to work with new + trustdbs. + +2002-12-03 David Shaw <dshaw@jabberwocky.com> + + * tdbio.h, tdbio.c (tdbio_read_record, tdbio_write_record): Store + trust model in the trustdb version record. + (tdbio_update_version_record): New function to update version + record values during a trustdb check or update. + (tdbio_dump_record): Show trust model in dump. + + * trustdb.c (validate_keys): Call tdbio_update_version_record on + success so that the correct options are stored in the trustdb. + + * options.h: rearrange trust models so that CLASSIC is 0 and + OPENPGP is 1. + + * options.h, g10.c (main), encode.c (write_pubkey_enc_from_list), + pkclist.c (algo_available), revoke.c (gen_revoke): Add --pgp8 + mode. This is basically identical to --pgp7 in all ways except + that signing subkeys, v4 data sigs (including expiration), and SK + comments are allowed. + + * getkey.c (finish_lookup): Comment. + + * main.h, keylist.c (reorder_keyblock), keyedit.c (keyedit_menu): + Reorder user ID display in the --edit-key menu to match that of + the --list-keys display. + + * g10.c (add_notation_data): Fix initialization. + +2002-12-01 David Shaw <dshaw@jabberwocky.com> + + * keyedit.c (menu_expire): Don't lose key flags when changing the + expiration date of a subkey. This is not the most optimal + solution, but it is minimal change on the stable branch. + + * main.h, keygen.c (do_copy_key_flags): New function to copy key + flags, if any, from one sig to another. + (do_add_key_expire): New function to add key expiration to a sig. + (keygen_copy_flags_add_expire): New version of + keygen_add_key_expire that also copies key flags. + (keygen_add_key_flags_and_expire): Use do_add_key_expire. + + * import.c (fix_hkp_corruption): Comment. + +2002-11-25 Stefan Bellon <sbellon@sbellon.de> + + * plaintext.c (handle_plaintext) [__riscos__]: If nooutput is set, + no filetype is needed obviously. + +2002-11-24 David Shaw <dshaw@jabberwocky.com> + + * main.h, misc.c (default_cipher_algo, default_compress_algo): + New. Return the default algorithm by trying + --cipher-algo/--compress-algo, then the first item in the pref + list, then s2k-cipher-algo or ZIP. + + * sign.c (sign_file, sign_symencrypt_file), encode.c + (encode_simple, encode_crypt): Call default_cipher_algo and + default_compress_algo to get algorithms. + + * g10.c (main): Allow pref selection for compress algo with + --openpgp. + + * mainproc.c (proc_encrypted): Use --s2k-digest-algo for + passphrase mangling rather than --digest-algo. + + * sign.c (hash_for): If --digest-algo is not set, but + --personal-digest-preferences is, then use the first hash + algorithm in the personal list. If the signing algorithm is DSA, + then use the first 160-bit hash algorithm in the personal list. + If --pgp2 is set and it's a v3 RSA key, use MD5. + + * g10.c (main), keydb.c (keydb_add_resource, + keydb_locate_writable): Rename --default-keyring as + --primary-keyring. Stefan wins the naming contest. + +2002-11-23 David Shaw <dshaw@jabberwocky.com> + + * g10.c (add_notation_data): Disallow notation names that do not + contain a '@', unless --expert is set. This is to help prevent + people from polluting the (as yet unused) IETF namespace. + + * main.h: Comments about default algorithms. + + * photoid.c (image_type_to_string): Comments about 3-letter file + extensions. + + * encode.c (encode_simple), passphrase.c (passphrase_to_dek), + sign.c (sign_symencrypt_file): Use --s2k-digest-algo for + passphrase mangling rather than --digest-algo. + +2002-11-21 David Shaw <dshaw@jabberwocky.com> + + * keygen.c (keygen_set_std_prefs): Properly handle an empty + preference string. + + * misc.c (string_to_compress_algo): "none" is a bad choice since + it conflicts with the "none" in setpref. + +2002-11-14 David Shaw <dshaw@jabberwocky.com> + + * g10.c (main): Allow compression algorithm names as the argument + to --compress-algo. The old algorithm names still work for + backwards compatibility. + + * misc.c (string_to_compress_algo): Allow "none" as an alias for + "uncompressed". + +2002-11-13 Stefan Bellon <sbellon@sbellon.de> + + * getkey.c (get_pubkey_byfprint_fast): Fixed type incompatibility, + was unsigned char instead of byte. + +2002-11-13 David Shaw <dshaw@jabberwocky.com> + + * encode.c (encode_simple): Make sure that files larger than about + 4G use partial length encoding. This is required because OpenPGP + allows only for 32 bit length fields. From Werner on stable + branch. + + * getkey.c (get_pubkey_direct): Renamed to... + (get_pubkey_fast): this and made extern. + (get_pubkey_byfprint_fast): New. From Werner on stable branch. + + * keydb.h, import.c (import_one): Use get_pubkey_fast instead of + get_pubkey. We don't need a merged key and actually this might + lead to recursions. + (revocation_present): Likewise for search by fingerprint. From + Werner on stable branch. + + * g10.c (main): Try to create the trustdb even for non-colon-mode + list-key operations. This is required because getkey needs to + know whether a a key is ultimately trusted. From Werner on stable + branch. + + * exec.c [__CYGWIN32__]: Keep cygwin separate from Mingw32; + we don't need it here as it behaves more like a Posix system. + From Werner on stable branch. + + * passphrase.c (agent_get_passphrase): Ditto. From Werner on + stable branch. + + * tdbio.c (MY_O_BINARY): Need binary mode with Cygwin. From + Werner on stable branch. + + * g10.c, gpgv.c (main) [__CYGWIN32__]: Don't get the homedir from + the registry. From Werner on stable branch. + + * keyedit.c (show_key_with_all_names_colon): Make --with-colons + --edit display match the validity and trust of --with-colons + --list-keys. + + * passphrase.c (agent_send_all_options): Fix compile warning. + + * keylist.c (list_keyblock_colon): Validity for subkeys should + match that of the primary key, and not that of the last user ID. + + * getkey.c (merge_selfsigs): Revoked/expired/invalid primary keys + carry these facts onto all their subkeys, but only after the + subkey has a chance to be marked valid. This is to fix an + incorrect "invalid public key" error verifying a signature made by + a revoked signing subkey, with a valid unrevoked primary key. + +2002-11-09 Werner Koch <wk@gnupg.org> + + * passphrase.c (agent_send_all_options): Use tty_get_ttyname to + get the default ttyname. + +2002-11-07 David Shaw <dshaw@jabberwocky.com> + + * keyring.h, keyring.c (keyring_register_filename): Return the + pointer if a given keyring is registered twice. + + * keydb.h, keydb.c (keydb_add_resource): Use flags to indicate a + default keyring. + (keydb_locate_writable): Prefer the default keyring if possible. + + * g10.c (main): Add --default-keyring option. + +2002-11-06 David Shaw <dshaw@jabberwocky.com> + + * options.h, g10.c (main), trustdb.c (ask_ownertrust): Add + --force-ownertrust option for debugging purposes. This allows + setting a whole keyring to a given trust during an + --update-trustdb. Not for normal use - it's just easier than + hitting "4" all the time to test a large trustdb. + + * pubkey-enc.c (get_session_key): With hidden recipients or try a + given passphrase against all secret keys rather than trying all + secret keys in turn. Don't if --try-all-secrets or --status-fd is + enabled. + + * passphrase.c (passphrase_to_dek): Mode 1 means do a regular + passphrase query, but don't prompt with the key info. + + * seckey-cert.c (do_check, check_secret_key): A negative ask count + means to enable passphrase mode 1. + + * keydb.h, getkey.c (enum_secret_keys): Add flag to include + secret-parts-missing keys (or not) in the list. + +2002-11-05 David Shaw <dshaw@jabberwocky.com> + + * keyserver.c (keyserver_search_prompt): When --with-colons is + enabled, don't try and fit the search output to the screen size - + just dump the whole list. + +2002-11-04 David Shaw <dshaw@jabberwocky.com> + + * keyserver.c (keyserver_search_prompt): When --with-colons is + enabled, just dump the raw keyserver protocol to stdout and don't + print the menu. + + * keyserver.c (show_prompt): Don't show a prompt when command-fd + is being used. + + * trustdb.c (trust_model_string, check_trustdb, update_trustdb, + validate_one_keyblock): It's not clear what a trustdb rebuild or + check means with a trust model other than "classic" or "openpgp", + so disallow this. + +2002-11-03 David Shaw <dshaw@jabberwocky.com> + + * options.h, g10.c (main): Add --trust-model option. Current + models are "openpgp" which is classic+trustsigs, "classic" which + is classic only, and "always" which is the same as the current + option --always-trust (which still works). Default is "openpgp". + + * trustdb.c (validate_one_keyblock): Use "openpgp" trust model to + enable trust sigs. + + * gpgv.c (main), mainproc.c (check_sig_and_print), pkclist.c + (do_we_trust, do_we_trust_pre, check_signatures_trust): Use new + --trust-model option in place of --always-trust. + + * keyedit.c (sign_mk_attrib, trustsig_prompt, sign_uids, + keyedit_menu): Prompt for and create a trust signature with + "tsign". This is functional, but needs better UI text. + + * build-packet.c (build_sig_subpkt): Able to build trust and + regexp subpackets. + + * pkclist.c (do_edit_ownertrust): Comment. + +2002-11-02 David Shaw <dshaw@jabberwocky.com> + + * keygen.c (set_one_pref, keygen_set_std_prefs): Allow using the + full algorithm name (CAST5, SHA1) rather than the short form (S3, + H2). + + * main.h, keygen.c (keygen_get_std_prefs), keyedit.c + (keyedit_menu): Return and use a fake uid packet rather than a + string since we already have a nice parser/printer in + keyedit.c:show_prefs. + + * main.h, misc.c (string_to_compress_algo): New. + +2002-11-01 David Shaw <dshaw@jabberwocky.com> + + * g10.c (main): Add --no-throw-keyid. + + * keydb.h, encode.c (write_pubkey_enc_from_list), g10.c (main), + pkclist.c (build_pk_list): Add --hidden-recipient (-R) and + --hidden-encrypt-to, which do a single-user variation on + --throw-keyid. The "hide this key" flag is carried in bit 0 of + the pk_list flags field. + + * keyserver.c (parse_keyrec): Fix shadowing warning. + +2002-10-31 Stefan Bellon <sbellon@sbellon.de> + + * compress.c (init_compress) [__riscos__]: Use + riscos_load_module() to load ZLib module. + + * g10.c (main) [__riscos__]: Renames due to changes in riscos.c + (e.g. prefixes all RISC OS specific functions with riscos_*). + * photoid.c (show_photos) [__riscos__]: Likewise. + * signal.c (got_fatal_signal) [__riscos__]: Likewise. + + * trustdb.c (check_regexp) [__riscos__]: Branch to RISC OS RegEx + handling. + +2002-10-31 David Shaw <dshaw@jabberwocky.com> + + * build-packet.c (do_plaintext), encode.c (encode_sesskey, + encode_simple, encode_crypt), sign.c (write_plaintext_packet): Use + wipememory() instead of memset() to wipe sensitive memory as the + memset() might be optimized away. + +2002-10-30 David Shaw <dshaw@jabberwocky.com> + + * trustdb.c (check_regexp): Modern regexps require REG_EXTENDED. + +2002-10-29 David Shaw <dshaw@jabberwocky.com> + + * packet.h, trustdb.h, trustdb.c (trust_string): New. Return a + string like "fully trusted", "marginally trusted", etc. + (get_min_ownertrust): New. Return minimum ownertrust. + (update_min_ownertrust): New. Set minimum ownertrust. + (check_regexp): New. Check a regular epression against a user ID. + (ask_ownertrust): Allow specifying a minimum value. + (get_ownertrust_info): Follow the minimum ownertrust when + returning a letter. + (clear_validity): Remove minimum ownertrust when a key becomes + invalid. + (release_key_items): Release regexp along with the rest of the + info. + (validate_one_keyblock, validate_keys): Build a trust sig chain + while validating. Call check_regexp for regexps. Use the minimum + ownertrust if the user does not specify a genuine ownertrust. + + * pkclist.c (do_edit_ownertrust): Only allow user to select a + trust level greater than the minimum value. + + * parse-packet.c (can_handle_critical): Can handle critical trust + and regexp subpackets. + + * trustdb.h, trustdb.c (clear_ownertrusts), delkey.c + (do_delete_key), import.c (import_one): Rename clear_ownertrust to + clear_ownertrusts and have it clear the min_ownertrust value as + well. + + * keylist.c (list_keyblock_print): Indent uid to match pub and + sig. + + * keyedit.c (print_and_check_one_sig, show_key_and_fingerprint, + menu_addrevoker), keylist.c (list_keyblock_print, + print_fingerprint): Show "T" or the trust depth for trust + signatures, and add spaces to some strings to make room for it. + + * packet.h, parse-packet.c (dump_sig_subpkt, parse_one_sig_subpkt, + parse_signature): Parse trust signature values. + + * tdbio.h, tdbio.c (tdbio_read_record, tdbio_write_record): + Reserve a byte for the minimum ownertrust value (for use with + trust signatures). + +2002-10-29 Stefan Bellon <sbellon@sbellon.de> + + * build-packet.c (calc_plaintext, do_plaintext): Removed RISC OS + specific filetype parts (it's now done in make_basename()). + + * plaintext.c (handle_plaintext): Tidied up RISC OS specific + filetype parts. + + * encode.c (encode_simple, encode_crypt): Added argument to + make_basename() call. + + * sign.c (write_plaintext_packet): Added argument to + make_basename() call. + +2002-10-28 Stefan Bellon <sbellon@sbellon.de> + + * build-packet.c (calc_plaintext, do_plaintext): Added filetype + handling for RISC OS' file types. + + * plaintext.c (handle_plaintext) [__riscos__]: Added filetype + handling for RISC OS' file types. + +2002-10-23 David Shaw <dshaw@jabberwocky.com> + + * main.h, import.c (sec_to_pub_keyblock, import_secret_one, + parse_import_options), g10.c (main): New import-option + "convert-sk-to-pk" to convert a secret key into a public key + during import. It is on by default. + +2002-10-23 Werner Koch <wk@gnupg.org> + + * pubkey-enc.c (get_it): Fix segv, test for revoked only when PK + has been assigned. + +2002-10-18 Timo Schulz <ts@winpt.org> + + * keylist.c: (print_pubkey_info): New. + (print_seckey_info): New. + * main.h: Prototypes for the new functions. + * delkey.c (do_delete_key): Use it here. + * revoke.c (gen_desig_revoke): Ditto. + +2002-10-17 Werner Koch <wk@gnupg.org> + + * pkclist.c (do_edit_ownertrust): Show all user IDs. This should + be enhanced to also show the current trust level. Suggested by + Florian Weimer. + +2002-10-17 David Shaw <dshaw@jabberwocky.com> + + * g10.c (main): Handle --strict and --no-strict from the command + line before the options file is loaded. + +2002-10-15 David Shaw <dshaw@jabberwocky.com> + + * g10.c (main): Disable --textmode when encrypting (symmetric or + pk) in --pgp2 mode as PGP 2 can't handle the unknown length + literal packet. Reported by Michael Richardson. + +2002-10-14 David Shaw <dshaw@jabberwocky.com> + + * keyserver-internal.h, keyserver.c (print_keyrec, parse_keyrec, + show_prompt, keyserver_search_prompt, keyserver_spawn): Go to + version 1 of the keyserver protocol. This is a better design, + similar to --with-colons, that allows for keys with multiple user + IDs rather than using multiple keys. It also matches the machine + readable pksd format. Also use a prettier --search-keys listing + format that can fill different size windows (currently set at 24 + lines). + +2002-10-12 Werner Koch <wk@gnupg.org> + + * keygen.c (print_status_key_created): New. + (do_generate_keypair): Use it to print the fingerprint. + (generate_subkeypair): Likewise. + +2002-10-11 David Shaw <dshaw@jabberwocky.com> + + * keyedit.c (menu_addrevoker): Properly back out if the signature + fails. Also, do not allow appointing the same revoker twice, and + report ALREADY_SIGNED if the user tries it. + +2002-10-07 David Shaw <dshaw@jabberwocky.com> + + * import.c (import_keys_internal): Missed one s/inp/inp2/. + + * keylist.c (print_capabilities): Properly indicate per-key + capabilities of sign&encrypt primary keys that have + secret-parts-missing (i.e. no capabilities at all) + + * mainproc.c (symkey_decrypt_sesskey): Fix compiler warning. + +2002-10-04 David Shaw <dshaw@jabberwocky.com> + + * getkey.c (get_pubkey_direct): Don't cache keys retrieved via + this function as they may not have all their fields filled in. + + * sig-check.c (signature_check2): Use new is_primary flag to check + rather than comparing main_keyid with keyid as this still works in + the case of a not fully filled in pk. + +2002-10-04 Werner Koch <wk@gnupg.org> + + * import.c (import_keys_internal): s/inp/inp2/ to avoid shadowing + warning. + + * passphrase.c (agent_get_passphrase): Fixed signed/unsigned char + problem in %-escaping. Noted by Ingo Klöcker. + +2002-10-03 David Shaw <dshaw@jabberwocky.com> + + * options.h, g10.c (main): Add --strict and --no-strict to switch + the log_warning severity level from info to error. + + * keylist.c (print_capabilities): Secret-parts-missing keys should + show that fact in the capabilities, and only primary signing keys + can certify other keys. + + * packet.h, parse_packet.c (parse_key): Add is_primary flag for + public keys (it already exists for secret keys). + +2002-10-02 David Shaw <dshaw@jabberwocky.com> + + * import.c (import_secret_one): Check for an illegal (>110) + protection cipher when importing a secret key. + + * keylist.c (list_keyblock_print): Show a '#' for a + secret-parts-missing key. + + * parse_packet.c (parse_key): Some comments. + + * revoke.c (gen_revoke): Remove some debugging code. + + * trustdb.c (verify_own_keys): Make trusted-key a non-deprecated + option again. + + * seckey-cert.c (do_check): Don't give the IDEA warning unless the + cipher in question is in fact IDEA. + +2002-10-01 David Shaw <dshaw@jabberwocky.com> + + * import.c (import_one): Make sure that a newly imported key + starts with a clean ownertrust. + +2002-10-01 Werner Koch <wk@gnupg.org> + + * getkey.c (get_pubkey_direct): New. + (merge_selfsigs_main): Use it here to look for an ultimately + trusted key. Using the full get_pubkey might lead to an + infinitive recursion. + +2002-09-29 David Shaw <dshaw@jabberwocky.com> + + * keyserver.c (parse_keyserver_uri): Force the keyserver URI + scheme to lowercase to be case-insensitive. + +2002-09-28 David Shaw <dshaw@jabberwocky.com> + + * export.c (do_export_stream): Comment. + + * sig-check.c (check_key_signature2): Properly handle a + non-designated revocation import. + +2002-09-26 Werner Koch <wk@gnupg.org> + + * g10.c (set_homedir): New. Changed all direct assignments to use + this. + * gpgv.c (set_homedir): Ditto. + +2002-09-25 David Shaw <dshaw@jabberwocky.com> + + * Makefile.am: Link gpg with EGDLIBS (i.e. NETLIBS) as EGD uses + sockets. Remove the old NETLIBS variable since the keyserver + stuff is no longer internal. + +2002-09-24 David Shaw <dshaw@jabberwocky.com> + + * import.c (import_keys_stream): Fix compiler type warning. + + * keyring.c (keyring_rebuild_cache), sig-check.c + (check_key_signature2), import.c (import, chk_self_sigs): Minor + language cleanups. + +2002-09-23 Stefan Bellon <sbellon@sbellon.de> + + * main.h: Introduced fast-import as import option. Removed + fast as separate option from prototypes. + * import.c (parse_import_options): Added fast-import option. + (import_*): Removed fast as separate option. + * g10.c (main): Added option fast-import, removed old fast + as separate argument. + * keyserver.c (keyserver_spawn): Removed old fast as separate + argument. + +2002-09-22 Stefan Bellon <sbellon@sbellon.de> + + * import.c (import_keys, import_keys_stream, + import_keys_internal): Added trustdb update/check to key import if + not fast-import and interactive set/no-auto-check-trustdb unset. + Avoided function clone by introducing import_keys_internal. + +2002-09-19 David Shaw <dshaw@jabberwocky.com> + + * keyserver.c (keyserver_spawn): Properly handle line truncation. + Don't leak memory (~10-20 bytes) on searches. + (keyserver_search_prompt): Cleanup. + + * keylist.c (list_keyblock_colon): Show 1F direct key signatures + in --with-colons listing. + +2002-09-16 David Shaw <dshaw@jabberwocky.com> + + * keyedit.c (menu_addrevoker): The direct key signature for + revocation keys must be at least v4 to carry the revocation key + subpacket. Add a PGP 2.x warning for revocation keys. + +2002-09-14 David Shaw <dshaw@jabberwocky.com> + + * g10.c (check_permissions): Rearrange strings to make translating + easier (don't incorporate string parts). + + * keyedit.c (sign_uids): Make strings translatable. + + * sig-check.c (check_key_signature2): Make string translatable. + +2002-09-13 David Shaw <dshaw@jabberwocky.com> + + * getkey.c (check_revocation_keys): Move.... + * main.h, sig-check.c (check_revocation_keys): to here. Also + return the signature_check error code rather than 0/1 and cache + the sig result. + + * sig-check.c (check_key_signature2): Divert to + check_revocation_keys if a revocation sig is made by someone other + than the pk owner. + + * getkey.c (merge_selfsigs_main): Tidy. + +2002-09-13 Werner Koch <wk@gnupg.org> + + * g10.c (main) [__MINGW32__]: Activate oLoadExtension. + +2002-09-12 David Shaw <dshaw@jabberwocky.com> + + * Makefile.am, hkp.c, hkp.h, keyserver.c (keyserver_work): Remove + internal HKP support. + + * keyserver.c (keyserver_spawn): Remove whitespace after keyserver + commands. + +2002-09-10 David Shaw <dshaw@jabberwocky.com> + + * exec.c (expand_args): Remove loop left over from earlier + implementation. + (exec_write): Missed one tick. + +2002-09-10 Werner Koch <wk@gnupg.org> + + * g10.c, options.h: Removed option --emulate-checksum-bug. + * misc.c (checksum_u16_nobug): Removed. + (checksum_u16): Removed the bug emulation. + (checksum_mpi): Ditto. + (checksum_mpi_counted_nbits): Removed and replaced all calls + with checksum_mpi. + + * parse-packet.c (read_protected_v3_mpi): New. + (parse_key): Use it here to store it as an opaque MPI. + * seckey-cert.c (do_check): Changed the v3 unprotection to the new + why to store these keys. + (protect_secret_key): Likewise. + * build-packet.c (do_secret_key): And changed the writing. + + * tdbio.c (tdbio_set_dbname, open_db): Use new macro MY_O_BINARY + to avoid silly ifdefs. + (open_db): Fallback to RDONLY so that gpg may be used from a + RO-medium. + + * encode.c (encode_simple): Make sure we don't use an ESK packet + when we don't have a salt in the S2K. + + * misc.c (pct_expando) <case f>: Make sure that LEN is initialized. + + * exec.c (exec_finish): Use ticks to denote filenames in messages. + (make_tempdir, exec_write): Changed format of messages. + + * keyserver.c (print_keyinfo): Release USERID in on error. + (keyserver_work) [!DISABLE_KEYSERVER_HELPERS]: Exclude the unused + code. + +2002-09-09 Werner Koch <wk@gnupg.org> + + * parse-packet.c (make_attribute_uidname): Add new ar MAX_NAMELEN + for sanity checks. Changed both callers. Limit the size of an %s. + + * options.skel: Comment lock-once out, so that this file does not + change anything when copied to a new home directory. + * openfile.c (try_make_homedir): Don't exit after copying the + option skeleton. + + * options.h: Don't use a comma when declaring variables over more + than one line. + + * mainproc.c (symkey_decrypt_sesskey): Check length of the session + key. + + * hkp.c (dehtmlize): Use ascii_tolower to protect against weird + locales. Cast the argument for isspace for the sake of broken + HP/UXes. + (parse_hkp_index): s/ascii_memcasecmp/ascii_strncasecmp/. + + * g10.c: Removed option --emulate-3des-s2k-bug. + + * passphrase.c (hash_passphrase): Was used here. + + * export.c (parse_export_options) + * keyserver.c (parse_keyserver_options) + * import.c (parse_import_options) + * g10.c (check_permissions): s/ascii_memcasecmp/ascii_strncasecmp/. + +2002-09-09 David Shaw <dshaw@jabberwocky.com> + + * g10.c (add_group): Use '=' to separate group name from group + members. Use a better error message for when no = is found. + + * hkp.c (hkp_export): Use CRLF in headers. + +2002-09-03 David Shaw <dshaw@jabberwocky.com> + + * mainproc.c (print_pkenc_list): Don't increment the error counter + when printing the list of keys a message was encrypted to. This + would make gpg give a non-zero exit code even for completely valid + messages if the message was encrypted to more than one key that + the user owned. + +2002-09-02 Werner Koch <wk@gnupg.org> + + * g10.c (main): Try to set a default character set. Print the + used one in verbosity level 3. + * gpgv.c (main): Try to set a default character set. + + * status.c, status.h (STATUS_IMPORT_OK): New. + * import.c (import_one,import_secret_one): Print new status. + +2002-08-30 David Shaw <dshaw@jabberwocky.com> + + * pkclist.c (build_pk_list): Add new status code to indicate an + untrusted user. This (or a disabled key) fail with "unavailable + pubkey" (G10ERR_UNU_PUBKEY). + + * pkclist.c (build_pk_list): Fail if any recipient keys are + unusable. + + * options.skel: The PGP LDAP keyserver is back. Use MIT keyserver + as a sample rather than cryptnet as cryptnet does not support + searching yet. + + * keyedit.c (show_key_with_all_names): Fix error message + (preferences are userid/selfsig and not key specific). + +2002-08-30 Werner Koch <wk@gnupg.org> + + * pkclist.c (do_we_trust_pre): Changed the wording of a warning. + + * encode.c (encode_simple,encode_crypt): Use new style CTB for + compressssed packets when using MDC. We need to do this so that + concatenated messages are properly decrypted. Old style + compression assumes that it is the last packet; given that we + can't determine the length in advance, the uncompressor does not + know where to start. Actually we should use the new CTB always + but this would break PGP 2 compatibility. + + * parse-packet.c (parse): Special treatment for new style CTB + compressed packets. + + * build-packet.c (do_mdc): Removed. Was not used. + (do_encrypted_mdc): Count in the version number and the MDC packet. + +2002-08-28 David Shaw <dshaw@jabberwocky.com> + + * sig-check.c (do_check_messages, do_check): Show keyid in error + messages. + + * keyserver.c (print_keyinfo): More readable key listings for + --search-keys responses. + +2002-08-26 David Shaw <dshaw@jabberwocky.com> + + * hkp.c (parse_hkp_index, dehtmlize): Move HTML functionality into + new "dehtmlize" function. Remove HTML before trying to parse each + line from the keyserver. If the keyserver provides key type + information in the listing, use it. + +2002-08-23 David Shaw <dshaw@jabberwocky.com> + + * sig-check.c (do_check, do_check_messages): Emit the usual sig + warnings even for cached sigs. This also serves to protect + against missing a sig expiring while cached. + + * getkey.c (merge_selfsigs_main): Don't check UID self-sigs twice. + +2002-08-22 David Shaw <dshaw@jabberwocky.com> + + * import.c (clean_subkeys, chk_self_sigs): Merge clean_subkeys + into chk_self_sigs. This improves efficiency as the same + signatures are not checked multiple times. Clarify when a subkey + is revoked (any revocation signature, even if it is dated before + the binding signature). + + * getkey.c (merge_selfsigs_subkey): Subkey revocation comments. + + * keylist.c (list_one): Stats are only for public key listings. + + * g10.c (main), options.skel: Default should be include-revoked + for keyserver operations. + +2002-08-21 Werner Koch <wk@gnupg.org> + + * import.c (import_print_stats): Print new non_imported counter + which is currently not used because we terminate on errors. + +2002-08-20 David Shaw <dshaw@jabberwocky.com> + + * options.skel: Document no-include-attributes for + keyserver-options. + + * keylist.c, keyedit.c, keyserver.c, sign.c: Some TODOs and + comments. + + * export.c (do_export_stream): Fix noop bug in exporting sensitive + revocation keys. + + * pkclist.c (do_edit_ownertrust): Comment out the option for + showing trust paths until it can be implemented. + +2002-08-19 Werner Koch <wk@gnupg.org> + + * getkey.c (get_user_id_native): Renamed to .. + (get_user_id_printable): this. Filter out all dangerous + characters. Checked all usages. + (get_user_id_string_native): Renamed to.. + (get_user_id_string_printable): this. Filter out all dangerous + characters. Checked all usages. + * keyedit.c (show_basic_key_info): New. + * keylist.c (print_fingerprint): New mode 3. + * import.c (import_one): Use new function to display the user ID. + +2002-08-16 Timo Schulz <ts@winpt.org> + + * g10.c (main): Enable opt.interactive. + + * import.c (import_one): Ask the user if the key shall be + imported when the interactive mode is used. Useful to extract + selected keys from a file. + +2002-08-16 Werner Koch <wk@gnupg.org> + + * seckey-cert.c: Workaround to allow decryption of v3 keys created + with a bug in the mpi_get_secure_buffer. + +2002-08-14 David Shaw <dshaw@jabberwocky.com> + + * hkp.c (parse_hkp_index): Properly handle really large keys + (5 digit key length) in HKP searches. + +2002-08-13 David Shaw <dshaw@jabberwocky.com> + + * encode.c (encode_simple): Fix problem with using compression + algo 2 and symmetric compressed files. + + * encode.c (encode_simple, encode_crypt): If we are not using a + MDC, compress even if a file is already compressed. This is to + help against the chosen ciphertext attack. + + * pkclist.c (select_algo_from_prefs): Fix requested algorithm bug + so the request succeeds even if the requested algorithm is not the + first found. + + * cipher.c (write_header), encode.c (use_mdc, encode_simple, + encode_crypt, encrypt_filter), g10.c (main): Be more eager to use + a MDC. We use a MDC if the keys directly support it, if the keys + list AES (any) or TWOFISH anywhere in the prefs, or if the cipher + chosen does not have a 64 bit blocksize. + +2002-08-08 David Shaw <dshaw@jabberwocky.com> + + * options.skel: Some language tweaks, and remove the + load-extension section for random gatherers. + + * keyring.c (create_tmp_file, rename_tmp_file): Create tmp files + with user-only permissions, but restore the original permissions + if the user has something special set. + + * openfile.c (copy_options_file): Create new options file + (gpg.conf) with user-only permissions. + + * keydb.c (keydb_add_resource): Create new keyrings with user-only + permissions. + + * tdbio.c (tdbio_set_dbname): Create new trustdbs with user-only + permissions. + +2002-08-07 David Shaw <dshaw@jabberwocky.com> + + * sig-check.c (signature_check2): Sanity check that the md has a + context for the hash that the sig is expecting. This can happen + if a onepass sig header does not match the actual sig, and also if + the clearsign "Hash:" header is missing or does not match the + actual sig. + + * keyedit.c (menu_revsig): Properly show a uid is revoked without + restarting gpg. This is Debian bug 124219, though their supplied + patch will not do the right thing. + + * main.h, tdbio.c (tdbio_set_dbname), misc.c (removed + check_permissions), keydb.c (keydb_add_resource), g10.c (main, + check_permissions): Significant reworking of the permission check + mechanism. The new behavior is to check everything in the homedir + by checking the homedir itself. If the user wants to put + (possibly shared) keyrings outside the homedir, they are not + checked. The options file and any extension files are checked + wherever they are, as well as their enclosing directories. This + is Debian bug 147760. + +2002-08-06 Stefan Bellon <sbellon@sbellon.de> + + * g10.c (main): Use of EXTSEP_S in new gpg.conf string. + * openfile.c (copy_options_file): Ditto. + +2002-08-06 David Shaw <dshaw@jabberwocky.com> + + * options.h, g10.c (main), mainproc.c (proc_encrypted): + --ignore-mdc-error option to turn a MDC check error into a + warning. + + * encode.c (encode_crypt), g10.c (main), sign.c (sign_file, + clearsign_file): Use the same --pgpX warning string everywhere to + ease translations. + + * encode.c (write_pubkey_enc_from_list): Warn when using + --throw-keyid with --pgpX. Noted by Vedaal Nistar. + + * revoke.c (export_minimal_pk, gen_desig_revoke, gen_revoke): + Export a minimal pk along with the revocation cert when in --pgpX + mode so that PGP can import it. + +2002-08-06 Werner Koch <wk@gnupg.org> + + * options.skel: Changed comments. + + * g10.c (main): Try to use "gpg.conf" as default option file. + * openfile.c (copy_options_file): Changed name of created file. + +2002-08-02 Werner Koch <wk@gnupg.org> + + * Makefile.am (LDFLAGS): Removed DYNLINK_LDFLAGS. + +2002-07-30 David Shaw <dshaw@jabberwocky.com> + + * options.h, g10.c (main), mainproc.c (proc_encrypted): Return a + decryption failed error if a MDC does not verify. Warn if a MDC + is not present (can disable via --no-mdc-warning). + + * exec.c (exec_write), g10.c (main), keyserver.c + (keyserver_spawn): Use new DISABLE_KEYSERVER_PATH rather than + FIXED_EXEC_PATH. + +2002-07-28 David Shaw <dshaw@jabberwocky.com> + + * sig-check.c (do_check): Properly validate v4 sigs with no hashed + section at all. + +2002-07-25 Werner Koch <wk@gnupg.org> + + * delkey.c (do_delete_key): Always allow to delete a key in batch mode + when specified by fingerprint. Suggested by Enzo Michelangeli. + +2002-07-25 David Shaw <dshaw@jabberwocky.com> + + * keyedit.c (menu_revsig): Change "revsig" to honor selected uids + so the user can revoke sigs from particular uids only. + + * keylist.c (list_keyblock_print): Don't display expired uids in + --list-keys unless -v and not --list-sigs (just like revoked + uids). + + * exec.c, export.c, import.c, keyedit.c, keyserver.c, misc.c: + "Warning" -> "WARNING" + +2002-07-24 David Shaw <dshaw@jabberwocky.com> + + * main.h, import.c (parse_import_options, fix_hkp_corruption, + import_one, delete_inv_parts), g10.c (main): New import-option + "repair-hkp-subkey-bug", which repairs as much as possible the HKP + mangling multiple subkeys bug. It is on by default for keyserver + receives, and off by default for regular --import. + + * main.h, import.c (import, import_one, delete_inv_parts), hkp.c + (hkp_ask_import), keyserver.c (keyserver_spawn): Use keyserver + import options when doing keyserver receives. + + * options.h, exec.h, exec.c (set_exec_path, exec_write), g10.c + (main), keyserver.c (keyserver_spawn): If the user does not use + "exec-path", completely replace $PATH with GNUPG_LIBEXECDIR before + calling the keyserver helper. If the user does use "exec-path", + append GNUPG_LIBEXECDIR after the specified path. + +2002-07-23 David Shaw <dshaw@jabberwocky.com> + + * import.c (parse_import_options), export.c + (parse_export_options): Fix offset problem with reversed ("no-") + meanings. + + * import.c (delete_inv_parts): Discard subkey signatures (0x18 and + 0x28) if found in the userid section of the key. + + * sig-check.c (signature_check2): Signatures made by invalid + subkeys (bad/missing binding sig) are also invalid. + + * keylist.c (print_fingerprint): Show the primary as well as the + secondary key fingerprint in modes 1 & 2. + +2002-07-22 David Shaw <dshaw@jabberwocky.com> + + * options.h, main.h, g10.c (main), import.c + (parse_import_options, delete_inv_parts), keyserver.c + (parse_keyserver_options): add new --import-options option. The + only current flag is "allow-local-sigs". + + * g10.c (main): Don't disable MDC in pgp7 mode. + + * options.h, g10.c (main), keyserver.c (parse_keyserver_options): + Remove old keyserver-option include-attributes now that there is + an export-option for the same thing. + + * options.h, main.h, export.c (parse_export_options, + do_export_stream), g10.c (main): add new --export-options option. + Current flags are "include-non-rfc", "include-local-sigs", + "include-attributes", and "include-sensitive-revkeys". + + * options.h, hkp.c (hkp_export), keyserver.c + (parse_keyserver_options, keyserver_spawn): try passing unknown + keyserver options to export options, and if successful, use them + when doing a keyserver --send-key. + + * build-packet.c (build_sig_subpkt): We do not generate + SIGSUBPKT_PRIV_VERIFY_CACHE anymore. + + * revoke.c (gen_desig_revoke): Lots more comments about including + sensitive revkeys along with the revocation sig itself. + + * keyserver.c (parse_keyserver_options): Simpler implementation + that can skip one pass over the options. + +2002-07-18 David Shaw <dshaw@jabberwocky.com> + + * keyedit.c (keyedit_menu, menu_addrevoker): Allow specifying + "sensitive" as an argument to an addrevoker command. This sets + the 0x40 sensitive revoker flag. + + * revoke.c (gen_desig_revoke): When generating a designated + revocation, include the direct key sig that contains the + designated revoker subpacket. This allows sensitive designated + revocation subpackets to be exported. Also indicate which + revokers are sensitive in the first place. + +2002-07-17 David Shaw <dshaw@jabberwocky.com> + + * keyedit.c (show_key_with_all_names_colon): The 0x40 class bit in + a designated revoker means "sensitive", not "local". It's + exportable under the right circumstances. + + * main.h, options.h, export.c (do_export_stream), g10.c (main), + hkp.c (hkp_export), keyserver.c (keyserver_spawn: Add a flag to + skip attribute packets and their signatures while exporting. This + is to accomodate keyservers (pksd again) that choke on attributes. + Use keyserver-option "include-attributes" to control it. This + defaults to ON (i.e. don't skip). + +2002-07-09 David Shaw <dshaw@jabberwocky.com> + + * options.h, keyserver.c (parse_keyserver_uri, keyserver_spawn, + keyserver_work), hkp.c (hkp_ask_import, hkp_export, hkp_search): + Use a much more strict reading of RFC-2396 for the keyserver URIs. + Specifically, don't try and be smart about checking the value of + ":port" so long as it is all digits, and properly handle opaque + data (those scheme specific parts that do not start with "//"). + +2002-07-04 David Shaw <dshaw@jabberwocky.com> + + * photoid.c (get_default_photo_command, show_photos): Honor + FIXED_PHOTO_VIEWER and DISABLE_PHOTO_VIEWER. + + * mainproc.c (check_sig_and_print): Use --show-photos to show + photos when verifying a sig made by a key with a photo. + + * keyserver.c (parse_keyserver_uri): Properly parse a URI with no + :port section and an empty file path, but with a terminating '/'. + (keyserver_work): Honor DISABLE_KEYSERVER_HELPERS. + + * hkp.c (hkp_ask_import): Display keyserver URI as a URI, but only + if verbose. + + * exec.c, g10.c: USE_EXEC_PATH -> FIXED_EXEC_PATH + +2002-07-03 David Shaw <dshaw@jabberwocky.com> + + * exec.h, exec.c (set_exec_path, exec_write), g10.c (main): If + USE_EXEC_PATH is defined at compile time, use it to lock the + exec-path and not allow the user to change it. + +2002-07-02 David Shaw <dshaw@jabberwocky.com> + + * options.h, g10.c (main), keyserver.c (keyserver_refresh): + Maintain and use the original keyserver URI for cosmetics rather + than trying to recreate it when needed. + + * mainproc.c (check_sig_and_print): Properly disregard expired + uids. Make sure that the first uid listed is a real uid and not + an attribute (attributes should only be listed in the "aka" + section). When there are no valid textual userids, try for an + invalid textual userid before using any attribute uid. + +2002-07-01 David Shaw <dshaw@jabberwocky.com> + + * options.skel: Fix a few typos, clarify "group", and remove + sample photo viewers for Win32 since they are the defaults now. + + * parse-packet.c (make_attribute_uidname), keylist.c + (dump_attribs): Fix two typecast warnings. + + * packet.h, build-packet.c (build_attribute_subpkt), exec.c + (expand_args), mkdtemp.c (mkdtemp), photoid.c + (parse_image_header): Fix some signedness compiler warnings. + +2002-07-01 Werner Koch <wk@gnupg.org> + + * photoid.c (get_default_photo_command): Also use __MINGW32__ + instead of HAVE_DOSISH_SYSTEM. + + * encode.c (encode_symmetric): Do not use the new encryption code. + +2002-06-30 Werner Koch <wk@gnupg.org> + + * photoid.c: Use __MINGW32__ to include windows because + HAVE_DOSISH_SYSTEM is also set for OS/2 and plain DOS. Provide + constant missing in older mingw installations. + +2002-06-21 Stefan Bellon <sbellon@sbellon.de> + + * g10.c [__riscos__]: Moved RISC OS specific stuff to util/riscos.c + and include/util.h. + + * gpgv.c [__riscos__]: Likewise. + +2002-06-20 David Shaw <dshaw@jabberwocky.com> + + * keydb.h, pkclist.c (select_algo_from_prefs): Allow passing a + suggested algorithm which will be used if available. + + * encode.c (encode_crypt, encrypt_filter), sign.c (sign_file): Use + new select_algo_from_prefs feature to check if forcing an + algorithm would violate the recipient preferences. + + * photoid.c (get_default_photo_command, show_photos): Use + different default viewers on different platforms. Currently we + have Win 9x, Win NT (2k, xp), Mac OSX, RISC OS, and "everybody + else". These are #ifdefs as much as possible to avoid clutter. + + * g10.c (strusage, build_list), keyedit.c (show_prefs), main.h, + misc.c (compress_algo_to_string, check_compress_algo), pkclist.c + (algo_available), keygen.c (keygen_set_std_prefs): New + algo_to_string and check functions for compress algorithms. + +2002-06-20 Werner Koch <wk@gnupg.org> + + * misc.c (setsysinfo): Removed a #warning for Alpha's uniligedn + trap disabling - it is quite possible that this is a debug relict. + +2002-06-20 Stefan Bellon <sbellon@sbellon.de> + + * g10.c [__riscos__]: Added image file system feature. + + * gpgv.c [__riscos__]: Added image file system feature. + + * photoid.c (show_photos) [__riscos__]: Set RISC OS filetype of + photo id according to MIME type. + +2002-06-19 David Shaw <dshaw@jabberwocky.com> + + * hkp.c (parse_hkp_index): Don't leak memory when failing out of a + bad HKP keyserver. + + * g10.c (add_notation_data): Relax slightly the rules as to what + can go into a notation name - 2440 allows "@", for example. + +2002-06-17 David Shaw <dshaw@jabberwocky.com> + + * import.c (clean_subkeys, import_one): Only allow at most 1 + binding sig and at most 1 revocation sig on a subkey, as per + 2440:11.1. + + * hkp.c (parse_hkp_index, hkp_search): Error if the keyserver + returns an unparseable HKP response. + +2002-06-15 David Shaw <dshaw@jabberwocky.com> + + * keyedit.c (show_key_with_all_names), keylist.c + (list_keyblock_print): Show "[expired]" before expired uids. + + * keyedit.c (show_key_with_all_names_colon), mainproc.c + (list_node), keylist.c (list_keyblock_colon): Show flag 'e' for + expired user ids. Use "uat" for user attribute packets instead of + "uid". Also use '<count> <length>' rather than the fake user id + string on attributes. + + * keygen.c (keygen_add_revkey): Remove unused code. + + * misc.c (check_permissions): Check directory permissions + properly - they are not special files. + + * pkclist.c (expand_id, expand_group, build_pk_list): When + expanding groups before building a pk list, inherit flags from the + original pre-expanded string. + + * pubkey-enc.c (is_algo_in_prefs): Don't use prefs from expired + uids. + +2002-06-14 David Shaw <dshaw@jabberwocky.com> + + * free-packet.c (copy_signature): Properly copy a signature that + carries a revocation key on it. + + * pkclist.c (expand_id, expand_group, build_pk_list): Groups now + work properly when used in the "Enter the user ID" prompt. + +2002-06-14 David Shaw <dshaw@jabberwocky.com> + + * keyedit.c (show_key_with_all_names): Display warning if a user + tries to show prefs on a v3 key with a v3 selfsig. + + * kbnode.c (dump_kbnode): Show if a uid is expired. + + * import.c (merge_blocks, import_revoke_cert): Show user ID + receiving a revocation certificate. + + * free-packet.c (cmp_user_ids): Properly compare attribute ids. + + * pkclist.c (expand_groups): Maintain the strlist flags while + expanding. Members of an expansion inherit their flags from the + expansion key. + + * options.h, cipher.c (write_header), g10.c (main), keygen.c + (keygen_set_std_prefs): remove the personal_mdc flag. It no + longer serves a purpose now that the personal preference lists are + split into cipher/digest/zip. + +2002-06-14 Timo Schulz <ts@winpt.org> + + * skclist.c (is_insecure): Implemented. + +2002-06-12 David Shaw <dshaw@jabberwocky.com> + + * keyserver.c (keyserver_spawn): Properly handle PROGRAM responses + when they have a CRLF ending. Noted by Keith Ray. + + * keyserver.c (keyserver_spawn): Handle CRLF endings from + keyserver helpers. Also don't leak the last line worth of memory + from the keyserver response. + + * main.h, misc.c (deprecated_warning): New function to warn about + deprecated options and commands. + + * g10.c (main), keyserver-internal.h, keyserver.c + (parse_keyserver_uri): Use new deprecated function to warn about + honor-http-proxy, auto-key-retrieve, and x-broken-hkp. + +2002-06-11 David Shaw <dshaw@jabberwocky.com> + + * Makefile.am: link gpg with NETLIBS for the built-in HKP access. + +2002-06-10 David Shaw <dshaw@jabberwocky.com> + + * options.h, keyserver.c (keyserver_opts), g10.c (main): New + keyserver option "include-subkeys". This feature already existed, + but now can be turned off. It defaults to on. + + * options.h, keyserver.c (parse_keyserver_options, + keyserver_spawn): There are now enough options to justify making a + structure for the keyserver options rather than a page of + if-then-else-if-then-etc. + + * getkey.c (merge_keys_and_selfsig, merge_selfsigs_main): Fix bug + in calculating key expiration dates. + +2002-06-09 David Shaw <dshaw@jabberwocky.com> + + * keydb.h, getkey.c (get_user_id_native), import.c (import_one): + Display user ID while importing a key. Note this applies to both + --import and keyserver --recv-keys. + + * exec.c (exec_finish): Log unnatural exit (core dump, killed + manually, etc) for fork/exec/pipe child processes. + +2002-06-08 Timo Schulz <ts@winpt.org> + + * encode.c (encode_symmetric): Disable the compat flag + when the expert mode is enabled. + +2002-06-07 David Shaw <dshaw@jabberwocky.com> + + * options.skel, options.h, main.h, keydb.h, pkclist.c + (build_pk_list, expand_groups), g10.c (main, add_group): Add new + "group" command to allow one name to expand into multiple keys. + For simplicity, and to avoid potential loops, we only expand once + - you can't make an alias that points to an alias. + + * main.h, g10.c (main), keygen.c (build_personal_digest_list): + Simplify the default digest list - there is really no need for the + other hashes since they will never be used after SHA-1 in the + list. + + * options.skel, options.h, g10.c (main), hkp.c (hkp_ask_import, + hkp_export, hkp_search), keyserver.c (parse_keyserver_options, + parse_keyserver_uri, keyserver_work, keyserver_refresh): Make the + "x-broken-hkp" keyserver scheme into keyserver-option + "broken-http-proxy". Move honor_http_proxy into + keyserver_options. Canonicalize the three variations of "hkp", + "x-hkp", and "x-broken-hkp" into "hkp". + +2002-06-07 Stefan Bellon <sbellon@sbellon.de> + + * g10.c [__riscos__]: Added --attribute-file to do the same as + --attribute-fd, but with a filename not a fd as argument. + Added magic symbol for RISC OS to use different memory management. + + * gpgv.c [__riscos__]: Added magic symbol for RISC OS to use + different memory management. + +2002-06-06 David Shaw <dshaw@jabberwocky.com> + + * main.h, g10.c (main), keygen.c (build_personal_digest_list): Put + in a default digest preference list consisting of SHA-1, followed + by every other installed digest except MD5. Note this is the same + as having no digest preference at all except for SHA-1 being + favored. + + * options.h, g10.c (main), keygen.c (keygen_set_std_prefs), + pkclist.c (select_algo_from_prefs): Split + --personal-preference-list into three: + --personal-{cipher|digest|compress}-preferences. This allows a + user to set one without affecting another (i.e. setting only a + digest pref doesn't imply an empty cipher pref). + + * exec.c (exec_read): This is a safer way of guessing the return + value of system(). Noted by Stefan Bellon. + +2002-06-05 David Shaw <dshaw@jabberwocky.com> + + * hkp.c (parse_hkp_index): Be more robust with keyservers + returning very unparseable responses. + + * exec.c (exec_read): Catch and display an error when the remote + process exits unnaturally (i.e. segfault) so the user knows what + happened. Also fix exec_write stub which has a different number + of arguments now. + +2002-06-05 Timo Schulz <ts@winpt.org> + + * encode.c (encode_simple): Ignore the new mode for RFC1991. + * mainproc.c (symkey_decrypt_sesskey): Better check for weird + keysizes. + +2002-06-05 Timo Schulz <ts@winpt.org> + + * encode.c (encode_sesskey): New. + (encode_simple): Use it here. But by default we use the compat + mode which supress to generate encrypted session keys. + +2002-06-05 Timo Schulz <ts@winpt.org> + + * mainproc.c (symkey_decrypt_sesskey): New. + (proc_symkey_enc): Support for encrypted session keys. + +2002-06-04 David Shaw <dshaw@jabberwocky.com> + + * sign.c (hash_for, sign_file): When encrypting and signing at the + same time, consult the various hash prefs to pick a hash algorithm + to use. Pass in a 160-bit hint if any of the signing keys are + DSA. + + * keydb.h, pkclist.c (select_algo_from_prefs, algo_available): + Pass a "hints" opaque pointer in to let the caller give hints as + to what algorithms would be acceptable. The only current hint is + for PREFTYPE_HASH to require a 160-bit hash for DSA. Change all + callers in encode.c (encode_crypt, encrypt_filter) and sign.c + (sign_file). If we settle on MD5 as the best algorithm based + solely on recepient keys and SHA1 is also a possibility, use SHA1 + unless the user intentionally chose MD5. This is as per 2440:13. + + * exec.c (make_tempdir): Fix duplicated filename problem. + +2002-06-03 David Shaw <dshaw@jabberwocky.com> + + * packet.h, parse-packet.c (enum_sig_subpkt): Report back from + enum_sig_subpkt when a subpacket is critical and change all + callers in keylist.c (show_policy_url, show_notation), mainproc.c + (print_notation_data), and pkclist.c (do_show_revocation_reason). + + * keylist.c (show_policy_url, show_notation): Display if the + policy or notation is critical. + +2002-06-03 David Shaw <dshaw@jabberwocky.com> + + * main.h, g10.c (main), keylist.c (dump_attribs, set_attrib_fd, + list_keyblock_print, list_keyblock_colon), status.h, status.c + (get_status_string): New --attribute-fd feature to dump the + contents of attribute subpackets for frontends. If --status-fd is + also used, then a new status tag ATTRIBUTE is provided for each + subpacket. + + * packet.h, getkey.c (fixup_uidnode, merge_selfsigs_main, + merge_selfsigs_subkey), parse-packet.c (setup_user_id): Keep track + of the expiration time of a user ID, and while we're at it, use + the expired flag from the selfsig rather than reparsing the + SIG_EXPIRE subpacket. + + * photoid.c (generate_photo_id): When adding a new photo ID, + showing the photo for confirmation is not safe when noninteractive + since the "user" may not be able to dismiss a viewer window. + Noted by Timo Schulz. + +2002-06-03 David Shaw <dshaw@jabberwocky.com> + + * options.skel: Sample photo viewers for Win32. + + * misc.c (pct_expando): Use the seckey for %k/%K if the pubkey is + not available. + + * photoid.h, photoid.c (show_photos): Include the seckey in case a + user tries to view a photo on a secret key, and change all callers + in keyedit.c (menu_showphoto), keylist.c (list_keyblock_print), + and photoid.c (generate_photo_id). + +2002-06-02 David Shaw <dshaw@jabberwocky.com> + + * photoid.c (show_photos): Work properly when not called with a + public key. + +2002-05-31 David Shaw <dshaw@jabberwocky.com> + + * sign.c (mk_notation_and_policy): Free unneeded buffer. + + * hkp.c (parse_hkp_index): Properly handle the '&' character + (i.e. "&") in HKP responses. + + * getkey.c (merge_selfsigs_main): Fix reversed expiration time + check with self-sigs. + + * keyedit.c (sign_uids): When making a new self-sig on a v3 key, + make a v3 self-sig unless it is currently a v3 self-sig being + promoted to v4. + +2002-05-31 Timo Schulz <ts@winpt.org> + + * pkclist.c (do_show_revocation_reason): Don't use capital + letters for non-interactive output. + (show_revocation_reason): Now it is global. + * pubkey-enc.c (get_it): Show if the key has been revoked. + +2002-05-30 David Shaw <dshaw@jabberwocky.com> + + * sign.c (write_signature_packets, sign_file, clearsign_file, + sign_symencrypt_file): Make a v4 signature if a policy URL or + notation is set, unless v3 sigs are forced via rfc1991 or + force-v3-sigs. Also remove some doubled code and clarify an error + message (we don't sign in PGP2 mode - just detach-sign). + + * parse-packet.c (parse_one_sig_subpkt): Add KS_FLAGS to the "any + size" section. + +2002-05-29 David Shaw <dshaw@jabberwocky.com> + + * keygen.c (keygen_set_std_prefs, add_feature_mdc): Use "mdc" and + "no-mdc" in the prefs string to allow switching on and off the MDC + feature. This is needed to properly export a key from GnuPG for + use on PGP which does not support MDC - without this, MDC-capable + implementations will still try and generate MDCs which will break + PGP. + + * keygen.c (keygen_get_std_prefs): Show "[mdc]" in prefs string if + it is enabled. + + * options.h, g10.c (main), cipher.c (write_header), keygen.c + (keygen_set_std_prefs): For consistency, allow the user to specify + mdc/no-mdc in the --personal-preference-list. If disabled, it + acts just like --disable-mdc. + +2002-05-29 David Shaw <dshaw@jabberwocky.com> + + * options.h, exec.c: Add some debugging info, using the 1024 debug + flag. + + * exec.c (win_system): New system()-like function for win32 that + does not return until the child process terminates. Of course, + this doesn't help if the process itself exits before it is + finished. + +2002-05-29 Werner Koch <wk@gnupg.org> + + * encode.c (encode_simple): Intialize PKT when --no-literal is used. + + * keyedit.c (show_key_with_all_names_colon): Renamed the record + for revocation keys to "rvk". + +2002-05-27 Werner Koch <wk@gnupg.org> + + * keyedit.c (show_key_with_all_names_colon): New. + (show_key_with_all_names): Divert to new function when required. + Sanitize printing of revoker name. + +2002-05-27 David Shaw <dshaw@jabberwocky.com> + + * build-packet.c (build_sig_subpkt): Handle setting sig flags for + certain subpacket types (notation, policy url, exportable, + revocable). keyedit.c (sign_mk_attrib): Flags no longer need to + be set here. + + * packet.h, parse-packet.c (parse_one_sig_subpkt), build-packet.c + (build_sig_subpkt): Call parse_one_sig_subpkt to sanity check + buffer lengths before building a sig subpacket. + +2002-05-26 David Shaw <dshaw@jabberwocky.com> + + * sign.c (mk_notation_and_policy): Include secret key to enable %s + expandos, and pass notations through pct_expando as well. + + * main.h, misc.c (pct_expando): Add %s and %S expandos for + signer's keyid. + +2002-05-25 David Shaw <dshaw@jabberwocky.com> + + * g10.c (strusage, build_list): Add compress algorithms to + --version list. Show algorithm numbers when --verbose --version + is done. + +2002-05-22 David Shaw <dshaw@jabberwocky.com> + + * options.h, main.h, keygen.c (keygen_set_set_prefs, + keygen_get_std_prefs, keygen_upd_std_prefs), keyedit.c + (keyedit_menu), g10.c (main), pkclist.c (select_algo_from_prefs): + Add --personal-preference-list which allows the user to factor in + their own preferred algorithms when the preference lists are + consulted. Obviously, this does not let the user violate a + recepient's preferences (and the RFC) - this only influences the + ranking of the agreed-on (and available) algorithms from the + recepients. Suggested by David Hollenberg. + + * options.h, keygen.c (keygen_set_std_prefs), g10.c (main): Rename + --preference-list to --default-preference-list (as that is what it + really is), and make it a true default in that if the user selects + "default" they get this list and not the compiled-in list. + +2002-05-22 Werner Koch <wk@gnupg.org> + + * g10.c (main): Add missing LF in a info printout and made it + translatable. Noted by Michael Tokarev. + +2002-05-21 Werner Koch <wk@gnupg.org> + + * g10.c (main): Removed the undef of USE_SHM_COPROCESSING which + was erroneously introduced on 2002-01-09. + + * signal.c (got_fatal_signal): Don't write the Nul to stderr. + Reported by David Hollenberg. + +2002-05-18 David Shaw <dshaw@jabberwocky.com> + + * main.h, g10.c (main), revoke.c (gen_desig_revoke): Generate a + designated revocation via --desig-revoke + + * keyedit.c (keyedit_menu, menu_addrevoker): New "addrevoker" + command to add a designated revoker to a key. + +2002-05-17 David Shaw <dshaw@jabberwocky.com> + + * gpgv.c: Add stub for get_ownertrust(). + + * g10.c (main): --allow-freeform-uid should be implied by + OpenPGP. Add --no-allow-freeform-uid. + + * keyedit.c (sign_uids): Issue a warning when signing a + non-selfsigned uid. + + * getkey.c (merge_selfsigs_main): If a key has no selfsigs, and + allow-non-selfsigned-uid is not set, still try and make the key + valid by checking all uids for a signature from an ultimately + trusted key. + +2002-05-16 David Shaw <dshaw@jabberwocky.com> + + * main.h, keygen.c (keygen_add_revkey): Add revocation key + subpackets to a signature (callable by + make_keysig_packet). (write_direct_sig): Write a 1F direct key + signature. (parse_revocation_key): Parse a string in + algo:fpr:sensitive format into a revocation + key. (get_parameter_revkey, do_generate_keypair): Call above + functions when prompted from a batch key generation file. + + * build-packet.c (build_sig_subpkt): Allow multiple revocation key + subpackets in a single sig. + + * keydb.h, getkey.c (get_seckey_byfprint): Same as + get_pubkey_byfprint, except for secret keys. We only know the + fingerprint of a revocation key, so this is needed to retrieve the + secret key needed to issue a revokation. + + * packet.h, parse-packet.c (parse_signature, parse_revkeys): Split + revkey parsing off into a new function that can be used to reparse + after manipulating the revkey list. + + * sign.c (make_keysig_packet): Ability to make 1F direct key + signatures. + +2002-05-15 David Shaw <dshaw@jabberwocky.com> + + * options.skel: keyserver.pgp.com is gone, so list pgp.surfnet.nl + as a sample LDAP server instead. + + * getkey.c (merge_selfsigs_main): Properly handle multiple + revocation keys in a single packet. Properly handle revocation + keys that are in out-of-order packets. Remove duplicates in + revocation key list. + +2002-05-14 Timo Schulz <ts@winpt.org> + + * exec.c (make_tempdir) [MINGW32]: Added missing '\'. + +2002-05-14 Stefan Bellon <sbellon@sbellon.de> + + * exec.c (make_tempdir): Make use of EXTSEP_S instead of hardcoded + dot as extension separator. + +2002-05-13 David Shaw <dshaw@jabberwocky.com> + + * photoid.c (show_photos): Use the long keyid as the filename for + the photo. Use the short keyid as the filename on 8.3 systems. + + * exec.h, exec.c (make_tempdir, exec_write, exec_finish): Allow + caller to specify filename. This should make things easier on + windows and macs where the file extension is required, but a whole + filename is even better. + + * keyedit.c (show_key_with_all_names, show_prefs): Show proper + prefs for a v4 key uid with no selfsig at all. + + * misc.c (check_permissions): Don't check permissions on + non-normal files (pipes, character devices, etc.) + +2002-05-11 Werner Koch <wk@gnupg.org> + + * mainproc.c (proc_symkey_enc): Avoid segv in case the parser + encountered an invalid packet. + + * keyserver.c (keyserver_export): Get confirmation before sending + all keys. + +2002-05-10 Stefan Bellon <sbellon@sbellon.de> + + * g10.c, hkp.c, keyedit.c, keyserver.c: Replaced all occurrances + of strcasecmp with ascii_strcasecmp and all occurrances of + strncasecmp with ascii_memcasecmp. + +2002-05-10 David Shaw <dshaw@jabberwocky.com> + + * packet.h, getkey.c (fixup_uidnode), keyedit.c (show_prefs): Show + assumed prefs for hash and compression as well as the cipher pref. + Show assumed prefs if there are no prefs at all on a v4 + self-signed key. + + * options.h, g10.c (main), sign.c (make_keysig_packet): New + --cert-digest-algo function to override the default key signing + hash algorithm. + +2002-05-09 David Shaw <dshaw@jabberwocky.com> + + * getkey.c (merge_selfsigs_main): Make sure the revocation key + list starts clean as this function may be called more than once + (e.g. from functions in --edit). + + * g10.c, encode.c (encode_crypt), sign.c (sign_file, + sign_symencrypt_file): Make --compress-algo work like the + documentation says. It should be like --cipher-algo and + --digest-algo in that it can override the preferences calculation + and impose the setting the user wants. No --compress-algo setting + allows the usual preferences calculation to take place. + + * main.h, compress.c (compress_filter): use new + DEFAULT_COMPRESS_ALGO define, and add a sanity check for compress + algo value. + +2002-05-08 David Shaw <dshaw@jabberwocky.com> + + * pkclist.c (select_algo_from_prefs): There is an assumed + compression preference for uncompressed data. + +2002-05-07 David Shaw <dshaw@jabberwocky.com> + + * options.h, g10.c (main), getkey.c (finish_lookup), pkclist.c + (algo_available): --pgp7, identical to --pgp6 except that it + permits a few algorithms that PGP 7 added: AES128, AES192, AES256, + and TWOFISH. Any more of these --pgpX flags, and it'll be time to + start looking at a generic --emulate-pgp X option. + + * export.c (do_export_stream): Warn the user when exporting a + secret key if it or any of its secret subkeys are protected with + SHA1 while simple_sk_checksum is set. + + * parse-packet.c (parse_key): Show when the SHA1 protection is + used in --list-packets. + + * options.h, build-packet.c (do_comment), g10.c (main): Rename + --no-comment as --sk-comments/--no-sk-comments (--no-comment still + works) and make the default be --no-sk-comments. + +2002-05-07 Werner Koch <wk@gnupg.org> + + * keygen.c (get_parameter_algo): Never allow generation of the + deprecated RSA-E or RSA-S flavors of PGP RSA. + (ask_algo): Allow generation of RSA sign and encrypt in expert + mode. Don't allow ElGamal S+E unless in expert mode. + * helptext.c: Added entry keygen.algo.rsa_se. + +2002-05-07 David Shaw <dshaw@jabberwocky.com> + + * keyedit.c (sign_uids): If --expert is set, allow re-signing a + uid to promote a v3 self-sig to a v4 one. This essentially + deletes the old v3 self-sig and replaces it with a v4 one. + + * packet.h, parse-packet.c (parse_key), getkey.c + (merge_keys_and_selfsig, merge_selfsigs_main): a v3 key with a v4 + self-sig must never let the v4 self-sig express a key expiration + time that extends beyond the original v3 expiration time. + +2002-05-06 David Shaw <dshaw@jabberwocky.com> + + * keyedit.c (sign_uids): When making a self-signature via "sign" + don't ask about sig level or expiration, and include the usual + preferences and such for v4 self-sigs. (menu_set_preferences): + Convert uids from UTF8 to native before printing. + + * keyedit.c (sign_uids): Convert uids from UTF8 to native before + printing. (menu_set_primary_uid): Show error if the user tries to + make a uid with a v3 self-sig primary. + +2002-05-05 David Shaw <dshaw@jabberwocky.com> + + * import.c (import_one): When merging with a key we already have, + don't let a key conflict (same keyid but different key) stop the + import: just skip the bad key and continue. + + * exec.c (make_tempdir): Under Win32, don't try environment + variables for temp directories - GetTempDir tries environment + variables internally, and it's better not to second-guess it in + case MS adds some sort of temp dir handling to Windows at some + point. + +2002-05-05 Timo Schulz <ts@winpt.org> + + * mainproc.c (proc_symkey_enc): Don't ask for a passphrase + in the list only mode. + +2002-05-05 David Shaw <dshaw@jabberwocky.com> + + * keyserver.c (keyserver_refresh): --refresh-keys implies + --merge-only so as not to import keys with keyids that match the + ones being refreshed. Noted by Florian Weimer. + +2002-05-04 Stefan Bellon <sbellon@sbellon.de> + + * free-packet.c (copy_public_key): Don't call m_alloc(0), therefore + added consistency check for revkey and numrefkeys. + + * getkey.c (check_revocation_keys): Added consistency check for + revkey and numrefkeys. + + * keyedit.c (show_key_with_all_names): Likewise. + +2002-05-03 David Shaw <dshaw@jabberwocky.com> + + * photoid.c: Provide default image viewer for Win32. + + * misc.c (pct_expando): %t means extension, not name ("jpg", not + "jpeg"). + + * keyserver.c (keyserver_spawn), photoid.c (show_photos), exec.h, + exec.c: Allow the caller to determine the temp file extension when + starting an exec_write and change all callers. + + * keyedit.c (sign_uids): Nonrevocable key signatures cause an + automatic promotion to v4. + + * exec.c: Provide stubs for exec_ functions when NO_EXEC is + defined. + +2002-05-02 David Shaw <dshaw@jabberwocky.com> + + * photoid.h, photoid.c (parse_image_header, image_type_to_string): + Useful functions to return data about an image. + + * packet.h, parse-packet.c (make_attribute_uidname, + parse_attribute_subpkts, parse_attribute), photoid.h, photoid.c + (show_photos): Handle multiple images in a single attribute + packet. + + * main.h, misc.c (pct_expando), sign.c (mk_notation_and_policy), + photoid.c (show_photos): Simpler expando code that does not + require using compile-time string sizes. Call + image_type_to_string to get image strings (i.e. "jpg", + "image/jpeg"). Change all callers. + + * keyedit.c (menu_showphoto), keylist.c (list_keyblock_print): + Allow viewing multiple images within a single attribute packet. + + * gpgv.c: Various stubs for link happiness. + +2002-05-02 David Shaw <dshaw@jabberwocky.com> + + * build-packet.c (build_sig_subpkt), keyedit.c (sign_uids), + options.h, sign.c (mk_notation_and_policy), g10.c (main, + add_notation_data, add_policy_url (new), check_policy_url + (removed)): Allow multiple policy URLs on a given signature. + Split "--notation-data" into "--cert-notation" and + "--sig-notation" so the user can set different policies for key + and data signing. For backwards compatibility, "--notation-data" + sets both, as before. + +2002-05-02 Werner Koch <wk@gnupg.org> + + * options.skel: Removed the comment on trusted-keys because this + option is now deprecated. + +2002-05-01 David Shaw <dshaw@jabberwocky.com> + + * keyedit.c (menu_adduid): 2440bis04 says that multiple attribute + packets on a given key are legal. + + * keyserver.c (keyserver_refresh): the fake v3 keyid hack applies + to "mailto" URLs as well since they are also served by pksd. + +2002-04-29 Werner Koch <wk@gnupg.org> + + Added a copyright year for files changed this year. + +2002-04-25 Werner Koch <wk@gnupg.org> + + * g10.c, options.h: New options --display, --ttyname, --ttytype, + --lc-ctype, --lc-messages to be used with future versions of the + gpg-agent. + * passphrase.c (agent_send_option,agent_send_all_options): New. + (agent_open): Send options to the agent. + + * trustdb.c (update_ownertrust, clear_ownertrust): Do an explicit + do_sync because revalidation_mark does it only if when the + timestamp actually changes. + +2002-04-23 David Shaw <dshaw@jabberwocky.com> + + * main.h, keygen.c (do_generate_keypair), keylist.c + (print_signature_stats, list_all, list_one, list_keyblock, + list_keyblock_print, list_keyblock_colon): After generating a new + key, show the key information (name, keyid, fingerprint, etc.) + Also do not print uncheckable signatures (missing key..) in + --check-sigs. Print statistics (N missing keys, etc.) after + --check-sigs. + + * keyedit.c (sign_uids): When signing a key with an expiration + date on it, the "Do you want your signature to expire at the same + time?" question should default to YES. + +2002-04-22 David Shaw <dshaw@jabberwocky.com> + + * parse-packet.c (parse_plaintext), packet.h, plaintext.c + (handle_plaintext): Fix bug in handling literal packets with + zero-length data (no data was being confused with partial body + length). + + * misc.c (pct_expando), options.skel: %t means extension ("jpg"). + %T means MIME type ("image/jpeg"). + + * import.c (import_one): Only trigger trust update if the keyring + is actually changed. + + * export.c (do_export_stream): Missing a m_free. + +2002-04-22 Stefan Bellon <sbellon@sbellon.de> + + * keyid.c (expirestr_from_sk, expirestr_from_sig): Added _() to + string constant. + + * exec.c (make_tempdir) [__riscos__]: Better placement of + temporary file. + +2002-04-20 David Shaw <dshaw@jabberwocky.com> + + * keygen.c (generate_subkeypair): 2440bis04 adds that creating + subkeys on v3 keys is a MUST NOT. + + * getkey.c (finish_lookup): The --pgp6 "use the primary key" + behavior should only apply while data signing and not encryption. + Noted by Roger Sondermann. + +2002-04-19 Werner Koch <wk@gnupg.org> + + * keygen.c (keygen_set_std_prefs): Put back 3DES because the RFC + says it is good form to do so. + +2002-04-19 David Shaw <dshaw@jabberwocky.com> + + * keyedit.c (menu_deluid): Only cause a trust update if we delete + a non-revoked user id. + + * hkp.c (hkp_ask_import), keyserver.c (parse_keyserver_options, + keyserver_spawn), options.h: Remove fast-import keyserver option + (no longer meaningful). + + * g10.c (main), keyedit.c (sign_uids), options.h: Change + --default-check-level to --default-cert-check-level as it makes + clear what it operates on. + + * g10.c (main): --pgp6 also implies --no-ask-sig-expire. + + * delkey.c (do_delete_key): Comment. + + * keyedit.c (sign_uids, keyedit_menu, menu_deluid, menu_delsig, + menu_expire, menu_revsig, menu_revkey): Only force a trustdb check + if we did something that changes it. + + * g10.c: add "--auto-check-trustdb" to override a + "--no-auto-check-trustdb" + +2002-04-19 Werner Koch <wk@gnupg.org> + + * tdbio.c (tdbio_write_nextcheck): Return a status whether the + stamp was actually changed. + * trustdb.c (revalidation_mark): Sync the changes. Removed the + sync operation done by its callers. + (get_validity): Add logic for maintaining a pending_check flag. + (clear_ownertrust): New. + + * keyedit.c (sign_uids): Don't call revalidation_mark depending on + primary_pk. + (keyedit_menu): Call revalidation_mark after "trust". + (show_key_with_all_names): Print a warning on the wrong listed key + validity. + + * delkey.c (do_delete_key): Clear the owenertrust information when + deleting a public key. + +2002-04-18 Werner Koch <wk@gnupg.org> + + * seskey.c (encode_md_value): Print an error message if a wrong + digest algorithm is used with DSA. Changed all callers to cope + with a NULL return. Problem noted by Imad R. Faiad. + +2002-04-18 David Shaw <dshaw@jabberwocky.com> + + * trustdb.c (mark_usable_uid_certs): Properly handle nonrevocable + signatures that can expire. In short, the only thing that can + override an unexpired nonrevocable signature is another unexpired + nonrevocable signature. + + * getkey.c (finish_lookup): Always use primary signing key for + signatures when --pgp6 is on since pgp6 and 7 do not understand + signatures made by signing subkeys. + +2002-04-18 Werner Koch <wk@gnupg.org> + + * trustdb.c (validate_keys): Never schedule a nextcheck into the + past. + (validate_key_list): New arg curtime use it to set next_expire. + (validate_one_keyblock): Take the current time from the caller. + (clear_validity, reset_unconnected_keys): New. + (validate_keys): Reset all unconnected keys. + + * getkey.c (premerge_public_with_secret): Fixed 0x12345678! syntax + for use with secret keys. + (lookup): Advance the searchmode after a search FIRST. + + * seckey-cert.c (do_check): Always calculate the old checksum for + use after unprotection. + + * g10.c, options.skel: New option --no-escape-from. Made + --escape-from and --force-v3-sigs the default and removed them + from the options skeleton. + +2002-04-16 Werner Koch <wk@gnupg.org> + + * parse-packet.c (parse_key): Support a SHA1 checksum as per + draft-rfc2440-bis04. + * packet.h (PKT_secret_key): Add field sha1chk. + * seckey-cert.c (do_check): Check the SHA1 checksum + (protect_secret_key): And create it. + * build-packet.c (do_secret_key): Mark it as sha-1 protected. + * g10.c, options.h: New option --simple-sk-checksum. + +2002-04-13 David Shaw <dshaw@jabberwocky.com> + + * parse-packet.c (parse_signature): Minor fix - signatures should + expire at their expiration time and not one second later. + + * keygen.c (proc_parameter_file): Allow specifying preferences + string (i.e. "s5 s2 z1 z2", etc) in a batchmode key generation + file. + + * keyedit.c (keyedit_menu): Print standard error message when + signing a revoked key (no new translation). + + * getkey.c (merge_selfsigs): Get the default set of key prefs from + the real (not attribute) primary uid. + +2002-04-12 David Shaw <dshaw@jabberwocky.com> + + * pkclist.c (build_pk_list): Fix bug that allowed a key to be + selected twice in batch mode if one instance was the default + recipient and the other was an encrypt-to. Noted by Stefan + Bellon. + + * parse-packet.c (dump_sig_subpkt): Show data in trust and regexp + sig subpackets. + + * keyedit.c (keyedit_menu): Use new function real_uids_left to + prevent deleting the last real (i.e. non-attribute) uid. Again, + according to the attribute draft. (menu_showphoto): Make another + string translatable. + +2002-04-11 David Shaw <dshaw@jabberwocky.com> + + * build-packet.c (build_sig_subpkt): Delete subpackets from both + hashed and unhashed area on update. (find_subpkt): No longer + needed. + + * keyedit.c (sign_uids): With --pgp2 on, refuse to sign a v3 key + with a v4 signature. As usual, --expert overrides. Try to tweak + some strings to a closer match so they can all be translated in + one place. Use different helptext keys to allow different help + text for different questions. + + * keygen.c (keygen_upd_std_prefs): Remove preferences from both + hashed and unhashed areas if they are not going to be used. + +2002-04-10 David Shaw <dshaw@jabberwocky.com> + + * misc.c (pct_expando), options.skel: Use %t to indicate type of a + photo ID (in this version, it's always "jpeg"). Also tweak string + expansion loop to minimize reallocs. + + * mainproc.c (do_check_sig): Variable type fix. + + * keyedit.c (menu_set_primary_uid): Differentiate between true + user IDs and attribute user IDs when making one of them primary. + That is, if we are making a user ID primary, we alter user IDs. + If we are making an attribute packet primary, we alter attribute + packets. This matches the language in the latest attribute packet + draft. + + * keyedit.c (sign_uids): No need for the empty string hack. + + * getkey.c (fixup_uidnode): Only accept preferences from the + hashed segment of the self-sig. + +2002-04-10 Werner Koch <wk@gnupg.org> + + * tdbio.c (migrate_from_v2): Fixed the offset to read the old + ownertrust value and only add entries to the table if we really + have a value. + +2002-04-08 David Shaw <dshaw@jabberwocky.com> + + * status.h, status.c (get_status_string): Add KEYEXPIRED, EXPSIG, + and EXPKEYSIG. Add "deprecated-use-keyexpired-instead" to + SIGEXPIRED. + + * sig-check.c (do_check): Start transition from SIGEXPIRED to + KEYEXPIRED, since the actual event is signature verification by an + expired key and not an expired signature. (do_signature_check, + packet.h): Rename as signature_check2, make public, and change all + callers. + + * mainproc.c (check_sig_and_print, do_check_sig): Use status + EXPSIG for an expired, but good, signature. Add the expiration + time (or 0) to the VALIDSIG status line. Use status KEYEXPSIG for + a good signature from an expired key. + + * g10.c (main): remove checks for no arguments now that argparse + does it. + +2002-04-06 Werner Koch <wk@gnupg.org> + + * keyring.c (keyring_get_keyblock): Disable the keylist mode here. + + * encode.c (encode_simple, encode_crypt): Only test on compressed + files if a compress level was not explicity set. + + * keygen.c (keygen_set_std_prefs): Removed Blowfish and Twofish + from the list of default preferences, swapped the preferences of + RMD160 and SHA1. Don't include a preference to 3DES unless the + IDEA kludge gets used. + + * free-packet.c (free_packet): call free_encrypted also for + PKT_ENCRYPTED_MDC. + + * compress.c (release_context): New. + (handle_compressed): Allocate the context and setup a closure to + release the context. This is required because there is no + guarabntee that the filter gets popped from the chain at the end + of the function. Problem noted by Timo and probably also the + cause for a couple of other reports. + (compress_filter): Use the release function if set. + + * tdbio.c [__CYGWIN32__]: Don't rename ftruncate. Noted by + Disastry. + + * parse-packet.c (parse_signature): Put parens around a bit test. + + * exec.c (make_tempdir): Double backslash for TMP directory + creation under Windows. Better strlen the DIRSEP_S constants for + allocation measurements. + + * decrypt.c (decrypt_messages): Release the passphrase aquired + by get_last_passphrase. + +2002-04-02 Werner Koch <wk@gnupg.org> + + * Makefile.am (EXTRA_DIST): Removed OPTIONS an pubring.asc - they + are no longer of any use. + +2002-04-03 David Shaw <dshaw@jabberwocky.com> + + * keyserver.c (parse_keyserver_options): fix auto-key-retrieve to + actually work as a keyserver-option (noted by Roger Sondermann). + + * keylist.c (reorder_keyblock): do not reorder the primary + attribute packet - the first user ID must be a genuine one. + +2002-03-31 David Shaw <dshaw@jabberwocky.com> + + * keylist.c (list_keyblock_colon): Fix ownertrust display with + --with-colons. + + * keygen.c (generate_user_id), photoid.c (generate_photo_id): + Properly initialize the user ID refcount. A few more "y/n" -> + "y/N" in photoid.c. + + * keyedit.c (ask_revoke_sig): Warn the user if they are about to + revoke an expired sig (not a problem, but they should know). Also + tweak a few prompts to change "y/n" to "y/N", which is how most + other prompts are written. + + * keyserver.c (keyserver_search_prompt): Control-d escapes the + keyserver search prompt. + + * pkclist.c (show_revocation_reason & callers): If a subkey is + considered revoked solely because the parent key is revoked, print + the revocation reason from the parent key. + + * trustdb.c (get_validity): Allow revocation/expiration to apply + to a uid/key with no entry in the trustdb. + +2002-03-29 David Shaw <dshaw@jabberwocky.com> + + * keyserver.c (printunquoted): unquote backslashes from keyserver + searches + + * hkp.c (write_quoted): quote backslashes from keyserver searches + +2002-03-26 Werner Koch <wk@gnupg.org> + + * keygen.c (ask_keysize): Removed the warning for key sizes > 1536. + +2002-03-25 Werner Koch <wk@gnupg.org> + + * keyedit.c (sign_uids): Use 2 strings and not a %s so that + translations can be done the right way. + * helptext.c: Fixed small typo. + +2002-03-23 David Shaw <dshaw@jabberwocky.com> + + * import.c (append_uid, merge_sigs): it is okay to import + completely non-signed uids now (with --allow-non-selfsigned-uid). + + * getkey.c (get_primary_uid, merge_selfsigs_main): do not choose + an attribute packet (i.e. photo) as primary uid. This prevents + oddities like "Good signature from [image of size 2671]". This is + still not perfect (one can still select an attribute packet as + primary in --edit), but is closer to the way the draft is going. + + * g10.c (build_list): algorithms should include 110. + + * g10.c (main): --pgp2 implies --no-ask-sig-expire and + --no-ask-cert-expire as those would cause a v4 sig/cert. + + * armor.c (is_armor_header): be more lenient in what constitutes a + valid armor header (i.e. -----BEGIN blah blah-----) as some + Windows programs seem to add spaces at the end. --openpgp makes + it strict again. + +2002-03-18 David Shaw <dshaw@jabberwocky.com> + + * keyserver.c (keyserver_search_prompt): Properly handle a "no + keys found" case from the internal HKP code (external HKP is ok). + Also, make a COUNT -1 (i.e. streamed) keyserver response a little + more efficient. + + * g10.c (main): Add --no-allow-non-selfsigned-uid + +2002-03-17 David Shaw <dshaw@jabberwocky.com> + + * g10.c (main): --openpgp implies --allow-non-selfsigned-uid. + + * getkey.c (merge_selfsigs_main): If none of the uids are primary + (because none are valid) then pick the first to be primary (but + still invalid). This is for cosmetics in case some display needs + to print a user ID from a non-selfsigned key. Also use + --allow-non-selfsigned-uid to make such a key valid and not + --always-trust. The key is *not* automatically trusted via + --allow-non-selfsigned-uid. + + * mainproc.c (check_sig_and_print): Make sure non-selfsigned uids + print [uncertain] on verification even though one is primary now. + + * getkey.c (merge_selfsigs): If the main key is not valid, then + neither are the subkeys. + + * import.c (import_one): Allow --allow-non-selfsigned-uid to work + on completely unsigned keys. Print the uids in UTF8. Remove + mark_non_selfsigned_uids_valid(). + + * keyedit.c (show_key_with_all_names): Show revocation key as + UTF8. + + * sign.c (clearsign_file): Allow --not-dash-escaped to work with + v3 keys. + +2002-03-14 Werner Koch <wk@gnupg.org> + + * main.h: Changed the default algorithms to CAST5 and SHA1. + +2002-03-13 David Shaw <dshaw@jabberwocky.com> + + * import.c (chk_self_sigs): Show which user ID a bad self-sig + (invald sig or unsupported public key algorithm) resides on. + + * import.c (chk_self_sigs): any valid self-sig should mark a user + ID or subkey as valid - otherwise, an attacker could DoS the user + by inventing a bogus invalid self-signature. + +2002-03-07 David Shaw <dshaw@jabberwocky.com> + + * g10.c (main): make a few more strings translatable. + + * options.h, options.skel, g10.c (main), gpgv.c, mainproc.c + (check_sig_and_print), keyserver.c (parse_keyserver_options): + --auto-key-retrieve should really be a keyserver-option variable. + + * import.c (revocation_present): new function to print a warning + if a key is imported that has been revoked by designated revoker, + but the designated revoker is not present to verify the + revocation. If keyserver-options auto-key-retrieve is set, try + and fetch the designated revoker from the keyserver. + + * import.c (import_one): call revocation_present after importing a + new key. Note that this applies to --import, --recv-keys, and + --search-keys. + + * keyserver-internal.h, keyserver.c (keyserver_import_fprint): + import via fingerprint (for revocation keys). + + * keyserver.c (keyserver_import_keyid): much simpler + implementation now that we're using KEYDB_SEARCH_DESC internally. + +2002-03-04 David Shaw <dshaw@jabberwocky.com> + + * revoke.c (gen_revoke): do not prompt for revocation reason for + v3 revocations (unless force-v4-certs is on) since they wouldn't + be used anyway. + + * keyedit.c (menu_revsig): show the status of the sigs + (exportable? revocable?) to the user before prompting for which + sig to revoke. Also, make sure that local signatures get local + revocations. + + * keyedit.c (ask_revoke_sig): remind the user which sigs are + local. + + * g10.c (main): Add "exec-path" variable to override PATH for + execing programs. + + * export.c (do_export_stream): properly check return code from + classify_user_id to catch unclassifiable keys. + +2002-03-03 David Shaw <dshaw@jabberwocky.com> + + * parse-packet.c (parse_signature): variable type tweak for RISC + OS (from Stefan) + +2002-02-28 David Shaw <dshaw@jabberwocky.com> + + * getkey.c (check_revocation_keys): New function to check a + revocation against a list of potential revocation keys. Note the + loop-breaking code here. This is to prevent blowing up if A is + B's revocation key, while B is also A's. Note also that this is + written so that a revoked revoker can still issue revocations: + i.e. If A revokes B, but A is revoked, B is still revoked. I'm + not completely convinced this is the proper behavior, but it + matches how PGP does it. It does at least have the advantage of + much simpler code - my first version of this had lots of loop + maintaining code so you could chain revokers many levels deep and + if D was revoked, C was not, which meant that B was, and so on. + It was sort of scary, actually. + + * getkey.c (merge_selfsigs_main): Add any revocation keys onto the + pk. This is particularly interesting since we normally only get + data from the most recent 1F signature, but you need multiple 1F + sigs to properly handle revocation keys (PGP does it this way, and + a revocation key could be marked "sensitive" and hence in a + different signature). Also, if a pk has a revocation key set, + check for revocation sigs that were not made by us - if made by a + valid revocation key, mark the pk revoked. + + * packet.h, getkey.c (cache_public_key): do not cache key if + "dont_cache" is set. This allows the revocation key code to look + up a key and return information that may be inaccurate to prevent + loops without caching the fake data. + + * packet.h, sig-check.c (do_signature_check): Record if a + signature was made by a revoked pk. + + * packet.h, parse-packet.c (parse_one_sig_subpkt, + can_handle_critical, parse_signature): Get revocation key + information out of direct sigs. + + * keylist.c (list_keyblock_print): don't assume that the presence + of a 0x20 signature means the key is revoked. With revocation + keys, this may not be true if the revocation key is not around to + verify it or if verification failed. Also, 0x1F should get listed + as "sig", and not "unexpected signature class". + + * keyedit.c (show_key_with_all_names): Add a flag for printing + revoker information and change all callers. + + * import.c (merge_blocks): merge in any new direct key (0x1F) + sigs. + + * import.c (import_revoke_cert): don't keep processing after a + revocation is rejected. + + * import.c (delete_inv_parts): Allow importing a revocation + signature even if it was not issued by the key. This allows a + revocation key to issue it. Of course, the sig still needs to be + checked before we trust it. + + * free-packet.c (copy_public_key): Include a new copy of the + revocation keys when duping a pk. + + * free-packet.c (free_seckey_enc, release_public_key_parts): Free + any revocation keys that are attached to a sig or pk. + + * export.c (do_export_stream): Do not export signatures with + "sensitive" revocation keys in them. + +2002-02-27 David Shaw <dshaw@jabberwocky.com> + + * export.c (do_export_stream): Do not include v3 keys in a + --export-secret-subkeys export. + + * getkey.c (merge_selfsigs_main): If a key isn't valid (say, + because of no self-signature), allow --always-trust to force it + valid so it can be trusted. + +2002-02-25 David Shaw <dshaw@jabberwocky.com> + + * hkp.c (hkp_ask_import), hkp.h, keyserver.c (all): treat key + lists internally as fingerprints when possible. All this is via + KEYDB_SEARCH_DESC - no point in reinventing the wheel. This allows + the helper program to search the keyserver by fingerprint if + desired (and the keyserver supports it). Note that automatic + fingerprint promotion during refresh only applies to v4 keys as a + v4 fingerprint can be easily changed into a long or short key id, + and a v3 cannot. + + * pubkey-enc.c, getkey.c, misc.c, main.h: Take two copies of + hextobyte() from pubkey-enc.c and getkey.c and make them into one + copy in misc.c. + +2002-02-22 David Shaw <dshaw@jabberwocky.com> + + * keyserver.c (keyserver_search_prompt): Detect a "no keys found" + case even if the helper program does not explicitly say how many + keys were found. + + * hkp.c (parse_hkp_index): Bug fix - don't report non-revoked keys + as revoked in HKP key searches. + +2002-02-19 Werner Koch <wk@gnupg.org> + + * parse-packet.c (parse_trust): Made parsing more robust. + +2002-02-19 David Shaw <dshaw@jabberwocky.com> + + * hkp.c (parse_hkp_index): Catch corruption in HKP index lines + (can be caused by broken or malicious keyservers). + + * keyserver.c (keyserver_work): Add KEYSERVER_NOT_SUPPORTED for + unsupported actions (say, a keyserver that has no way to search, + or a readonly keyserver that has no way to add). Also add a + USE_EXTERNAL_HKP define to disable the internal HKP keyserver + code. + +2002-02-14 Werner Koch <wk@gnupg.org> + + * g10.c: New option --no-use-agent. + + * pkclist.c (check_signatures_trust): Always print the warning for + unknown and undefined trust. Removed the did_add cruft. Reported + by Janusz A. Urbanowicz. + +2002-02-11 David Shaw <dshaw@jabberwocky.com> + + * hkp.c (parse_hkp_index): Bug fix - properly handle user IDs with + colons (":") in them while HKP searching. + +2002-02-09 David Shaw <dshaw@jabberwocky.com> + + * misc.c (pct_expando): More comments. + + * keydb.h, sign.c (mk_notation_and_policy): Clarify what is a sig + and what is a cert. A sig has sigclass 0x00, 0x01, 0x02, or 0x40, + and everything else is a cert. + + * g10.c (main), keyedit.c (keyedit_menu): Add a "nrlsign" for + nonrevocable and local key signatures. + + * g10.c (main): Add a --no-force-mdc to undo --force-mdc. + + * options.h, g10.c (main), cipher.c (write_header): Add a knob to + --disable-mdc/--no-disable-mdc. Off by default, of course, but is + used in --pgp2 and --pgp6 modes. + + * pkclist.c (build_pk_list): Allow specifying multiple users in + the "Enter the user ID" loop. Enter a blank line to stop. Show + each key+id as it is added. + + * keylist.c (show_policy_url), mainproc.c (print_notation_data): + It is not illegal (though possibly silly) to have multiple policy + URLs in a given signature, so print all that are present. + + * hkp.c (hkp_search): More efficient implementation of URL-ifying + code. + +2002-02-04 David Shaw <dshaw@jabberwocky.com> + + * main.h, misc.c (pct_expando): New function to generalize + %-expando processing in any arbitrary string. + + * photoid.c (show_photo): Call the new pct_expando function rather + than expand strings internally. + + * sign.c (mk_notation_and_policy): Show policy URLs and notations + when making a signature if show-policy/show-notation is on. + %-expand policy URLs during generation. This lets the user have + policy URLs of the form "http://notary.jabberwocky.com/keysign/%K" + which will generate a per-signature policy URL. + + * main.h, keylist.c (show_policy_url, show_notation): Add amount + to indent so the same function can be used in key listings as well + as during sig generation. Change all callers. + +2002-02-04 David Shaw <dshaw@jabberwocky.com> + + * keyserver.c, options.h (parse_keyserver_options, keyidlist): + Workaround for the pksd and OKS keyserver bug that calculates v4 + RSA keyids as if they were v3. The workaround/hack is to fetch + both the v4 (e.g. 99242560) and v3 (e.g. 68FDDBC7) keyids. This + only happens for key refresh while using the HKP scheme and the + refresh-add-fake-v3-keyids keyserver option must be set. This + should stay off by default. + +2002-02-03 David Shaw <dshaw@jabberwocky.com> + + * keyserver.c (keyserver_spawn): Bug fix - do not append keys to + each other when --sending more than one. + +2002-02-02 David Shaw <dshaw@jabberwocky.com> + + * options.h, g10.c (main), keyedit.c (sign_uids), sign.c + (mk_notation_and_policy): Split "--set-policy-url" into + "--cert-policy-url" and "--sig-policy-url" so the user can set + different policies for key and data signing. For backwards + compatibility, "--set-policy-url" sets both, as before. + +2002-01-30 Werner Koch <wk@gnupg.org> + + * g10.c (main): --gen-random --armor does now output a base64 + encoded string. + +2002-01-28 David Shaw <dshaw@jabberwocky.com> + + * g10.c (main), options.h, pkclist.c (algo_available): --pgp6 + flag. This is not nearly as involved as --pgp2. In short, it + turns off force_mdc, turns on no_comment, escape_from, and + force_v3_sigs, and sets compression to 1. It also restricts the + user to IDEA (if present), 3DES, CAST5, MD5, SHA1, and RIPEMD160. + See the comments above algo_available() for lots of discussion on + why you would want to do this. + +2002-01-27 David Shaw <dshaw@jabberwocky.com> + + * keygen.c (keygen_set_std_prefs): Comment + + * keyedit.c (sign_uids): Bug fix - when signing with multiple + secret keys at the same time, make sure each key gets the sigclass + prompt. + + * exec.c (exec_finish): Close the iobuf and FILE before trying to + waitpid, so the remote process will get a SIGPIPE and exit. This + is only a factor when using a pipe to communicate. + + * exec.c (exec_write): Disable cache-on-close of the fd iobuf (is + this right? Why is a fd iobuf cached at all?) + +2002-01-26 Werner Koch <wk@gnupg.org> + + * g10.c, options.h: New option --gpg-agent-info + * passphrase.c (agent_open): Let it override the environment info. + * seckey-cert.c (check_secret_key): Always try 3 times when the + agent is enabled. + * options.skel: Describe --use-agent. + +2002-01-24 David Shaw <dshaw@jabberwocky.com> + + * pubkey-enc.c (is_algo_in_prefs, get_it): Only check preferences + against keys with v4 self sigs - there is really little point in + warning for every single non-IDEA message encrypted to an old key. + + * pkclist.c (select_algo_from_prefs): Only put in the fake IDEA + preference if --pgp2 is on. + + * mainproc.c (check_sig_and_print): Print "Expired" for expired + but good signatures (this still prints "BAD" for expired but bad + signatures). + +2002-01-23 David Shaw <dshaw@jabberwocky.com> + + * keygen.c (ask_keysize): Cosmetic: don't present a RSA signing + key as a "keypair" which can be 768 bits long (as RSA minimum is + 1024). + + * pubkey-enc.c (is_algo_in_prefs): Allow IDEA as a fake preference + for v3 keys with v3 selfsigs. + +2002-01-22 David Shaw <dshaw@jabberwocky.com> + + * packet.h, getkey.c (merge_selfsigs_main), pkclist.c + (select_algo_from_prefs): Implement the fake IDEA preference as + per RFC2440:12.1. This doesn't mean that IDEA will be used (the + plugin may not be present), but it does mean that a v3 key with a + v3 selfsig has an implicit IDEA preference instead of 3DES. v3 + keys with v4 selfsigs use preferences as normal. + + * encode.c (encode_crypt): if select_algo_from_prefs fails, this + means that we could not find a cipher that both keys like. Since + all v4 keys have an implicit 3DES preference, this means there is + a v3 key with a v3 selfsig in the list. Use 3DES in this case as + it is the safest option (we know the v4 key can handle it, and + we'll just hope the v3 key is being used in an implementation that + can handle it). If --pgp2 is on, warn the user what we're doing + since it'll probably break PGP2 compatibility. + + * g10.c (main): Do not force using IDEA for encrypted files in + --pgp2 mode - let the fake IDEA preference choose this for us for + better compatibility when encrypting to multiple keys, only some + of which are v3. + + * keygen.c (keygen_set_std_prefs): Put 3DES on the end of the + default cipher pref list (RFC2440: "...it is good form to place it + there explicitly."). If the user has the IDEA plugin installed, + put a preference for IDEA *after* 3DES to effectively disable its + use for everything except encrypting along with v3 keys. + + * encode.c, g10.c, sign.c: Change the PGP2 warning line from + "... will not be usable ..." to "... may not be usable ..." as the + user could be using one of the enhanced PGP2 variations. + + * helptext.c: Revise the sign_uid.class help text as suggested by + Stefan. + +2002-01-20 Werner Koch <wk@gnupg.org> + + * passphrase.c (passphrase_to_dek): Add tryagain_text arg to be + used with the agent. Changed all callers. + (agent_get_passphrase): Likewise and send it to the agent + * seckey-cert.c (do_check): New arg tryagain_text. + (check_secret_key): Pass the string to do_check. + * keygen.c (ask_passphrase): Set the error text is required. + * keyedit.c (change_passphrase): Ditto. + + * passphrase.c (agent_open): Disable opt.use_agent in case of a + problem with the agent. + (agent_get_passphrase): Ditto. + (passphrase_clear_cache): Ditto. + +2002-01-19 Werner Koch <wk@gnupg.org> + + * passphrase.c (agent_open): Add support for the new Assuan based + gpg-agent. New arg to return the used protocol version. + (agent_get_passphrase): Implemented new protocol here. + (passphrase_clear_cache): Ditto. + (readline): New. + +2002-01-15 Timo Schulz <ts@winpt.org> + + * encode.c (encode_crypt_files): Fail if --output is used. + + * g10.c: New command --decrypt-files. + + * decrypt.c (decrypt_messages): New. + +2002-01-09 David Shaw <dshaw@jabberwocky.com> + + * g10.c, misc.c, gpgv.c: move idea_cipher_warn to misc.c so gpgv.c + doesn't need a stub for it any longer. + + * g10.c (get_temp_dir), main.h: no longer used (it's in exec.c now) + + * g10.c (main), delkey.c (delete_keys), main.h : Allow + --delete-key (now --delete-keys, though --delete-key still works, + of course) to delete multiple keys in one go. This applies to + --delete-secret-key(s) and --delete-secret-and-public-key(s) as + well. + +2002-01-09 Timo Schulz <ts@winpt.org> + + * encode.c (encode_crypt_files): Now it behaves like verify_files. + + * g10.c (main): We don't need to check argc for encode_crypt_files + any longer. + +2002-01-09 Timo Schulz <ts@winpt.org> + + * exec.c: Include windows.h for dosish systems. + +2002-01-08 Timo Schulz <ts@winpt.org> + + * g10.c (main): New description for --encrypt-files. + +2002-01-08 Werner Koch <wk@gnupg.org> + + * g10.c (main): Must register the secring for encryption because + it is needed to figure out the default recipient. Reported by + Roger Sondermann. + +2002-01-05 David Shaw <dshaw@jabberwocky.com> + + * keyedit.c (menu_adduid): Require --expert before adding a photo + ID to a v3 key, and before adding a second photo ID to any key. + + * keyedit.c (keyedit_menu): Don't allow adding photo IDs in + rfc1991 or pgp2 mode. + + * getkey.c (merge_selfsigs_subkey): Permit v3 subkeys. Believe it + or not, this is allowed by rfc 2440, and both PGP 6 and PGP 7 work + fine with them. + + * g10.c, options.h, keyedit.c, sign.c: Move the "ask for + expiration" switch off of --expert, which was getting quite + overloaded, and onto ask-sig-expire and ask-cert-expire. Both + default to off. + + * g10.c (main): Change the default compression algo to 1, to be + more OpenPGP compliant (PGP also uses this, so it'll help with + interoperability problems as well). + + * encode.c (encode_crypt): Handle compression algo 2, since the + default is now 1. + + * build-packet.c (build_attribute_subpkt): Fix off-by-one error. + +2002-01-05 Werner Koch <wk@gnupg.org> + + * g10.c (main): Do not register the secret keyrings for certain + commands. + + * keydb.c (keydb_add_resource): Use access to test for keyring + existence. This avoids cached opened files which are bad under + RISC OS. + +2002-01-04 David Shaw <dshaw@jabberwocky.com> + + * sign.c (sign_file, sign_symencrypt_file): always use one-pass + packets unless rfc1991 is enabled. This allows a signature made + with a v3 key to work in PGP 6 and 7. Signatures made with v4 + keys are unchanged. + + * g10.c (main): Disallow non-detached signatures in PGP2 mode. + Move the "you must use files and not pipes" PGP2 warning up so all + the PGP2 stuff is together. + + * encode.c (encode_simple): Use the actual filesize instead of + partial length packets in the internal literal packet from a + symmetric message. This breaks PGP5(?), but fixes PGP2, 6, and 7. + It's a decent tradeoff. Note there was only an issue with + old-style RFC1991 symmetric messages. 2440-style messages in 6 + and 7 work with or without partial length packets. + +2002-01-03 David Shaw <dshaw@jabberwocky.com> + + * g10.c (main): Removed --no-default-check-level option, as it is + not consistent with other "default" options. Plus, it is the same + as saying --default-check-level 0. + + * exec.c (exec_read): Disallow caching tempfile from child + process, as this keeps the file handle open and can cause unlink + problems on some platforms. + + * keyserver.c (keyserver_search_prompt): Minor tweak - don't + bother to transform keyids into textual form if they're just going + to be transformed back to numbers. + +2002-01-03 Timo Schulz <ts@winpt.org> + + * g10.c: New command --encrypt-files. + + * verify.c (print_file_status): Removed the static because + encode_crypt_files also uses this function. + + * main.h (print_files_status): New. + (encode_crypt_files): New. + + * encode.c (encode_crypt_files): New. + +2002-01-02 Stefan Bellon <sbellon@sbellon.de> + + * keyserver.c: Moved util.h include down in order to avoid + redefinition problems on RISC OS. + + * keyring.c (keyring_lock): Only lock keyrings that are writable. + + * keyring.c (keyring_update_keyblock): Close unused iobuf. + + * hkp.c (parse_hkp_index, hkp_search) [__riscos__]: Changed + unsigned char* to char* because of compiler issues. + + * exec.c (exec_finish) [__riscos__]: Invalidate close cache so + that file can be unlinked. + +2001-12-28 David Shaw <dshaw@jabberwocky.com> + + * g10.c (main): Use a different strlist to check extensions since + they need to be handled seperately now. + + * misc.c,main.h (check_permissions): Properly handle permission + and ownership checks on files in the lib directory + (e.g. /usr/local/lib/gnupg), which are owned by root and are + world-readable, and change all callers to specify extension or + per-user file. + + * photoid.c (show_photo), keyserver.c (keyserver_spawn): Bug fix - + don't call exec_finish if exec_write fails. + + * keyserver.c (keyserver_spawn): Look for OPTIONS from the + keyserver helper - specifically, a "OUTOFBAND" option for the + email keyserver. + + * mainproc.c (list_node), keylist.c (list_keyblock_colon), + import.c (delete_inv_parts), export.c (do_export_stream): Use + signature flags for exportability check rather than re-parsing the + subpacket. + + * keyid.c, keydb.h (get_lsign_letter): No longer needed. + +2001-12-27 David Shaw <dshaw@jabberwocky.com> + + * exec.c (exec_finish): Show errors when temp files cannot be + deleted for whatever reason. + + * exec.c (exec_read): Don't rely on WEXITSTATUS being present. + + * exec.c (make_tempdir): Add temp file creator for win32. Don't + create an incoming temp file if the exec is write-only. + + * keyserver.c (keyserver_spawn): Clean up error handling, for when + the spawn fails. + + * photoid.c (show_photo): Clean up error handling. + + * misc.c (check_permissions): Neaten. + +2001-12-25 David Shaw <dshaw@jabberwocky.com> + + * mkdtemp.c (mkdtemp): Add copyleft info and tweak the 'X' counter + to be a bit simpler. + + * keyserver.c, photoid.c: Remove unused headers left over from + when the exec functions lived there. + +2001-12-23 Timo Schulz <ts@winpt.org> + + * misc.c (check_permissions): Do not use it for W32 systems. + + * tdbio.c (migrate_from_v2): Define ftruncate as chsize() for W32. + + * mkdtemp.c: W32 support. + + * photoid.c: Ditto. + + * exec.c: Ditto. + +2001-12-22 David Shaw <dshaw@jabberwocky.com> + + * exec.c (make_tempdir): avoid compiler warning with const + + * mkdtemp.c (mkdtemp): catch the empty ("") string case in case + someone repurposes mkdtemp at some point. + + * photoid.c (generate_photo_id, show_photo): some type changes + from Stefan Bellon. + + * exec.c (make_tempdir): handle Win32 systems, suggested by Timo + Schulz. + +2001-12-22 Werner Koch <wk@gnupg.org> + + * encode.c (encode_simple, encode_crypt): i18n 2 strings. + +2001-12-22 Timo Schulz <ts@winpt.org> + + * encode.c (encode_simple, encode_crypt): Use is_file_compressed + to avoid to compress compressed files. + +2001-12-22 Werner Koch <wk@gnupg.org> + + * keyserver.c (keyserver_spawn): Removed some variables + declaration due to shadowing warnings. + + * build-packet.c (build_attribute_subpkt): s/index/idx/ to avoid + compiler warnig due to index(3). + + * getkey.c (get_ctx_handle): Use KEYDB_HANDLE as return value. + * keylist.c (list_one): Made resname const. + + * keyedit.c (keyedit_menu): Allow "addphoto" only when --openpgp is + not used. + + * options.skel: Changed one example photo viewer to qiv. + +2001-12-21 David Shaw <dshaw@jabberwocky.com> + + * Makefile.am: add exec.c, exec.h, photoid.c, and photoid.h + + * build-packet.c (build_attribute_subpkt): new function to build + the raw attribute subpacket. Note that attribute subpackets have + the same format as signature subpackets. + + * exec.c: new file with generic exec-a-program functionality. + Used by both photo IDs and keyserver helpers. This is pretty much + the same code that used to be keyserver specific, with some + changes to be usable generically. + + * free-packet.c (free_attributes (new)): function to free an + attribute packet. + + * gpgv.c: added stub show_photo + + * keyedit.c (keyedit_menu, menu_adduid, menu_showphoto): can add a + photo (calls generate_photo_id), or display a photo (calls + show_photo) from the --edit menu. New commands are "addphoto", + and "delphoto" (same as "deluid"). + + * keylist.c (list_keyblock_print): show photos during key list if + --show-photos enabled. + + * keyserver.c (keyserver_spawn): use the generic exec_xxx + functions to call keyserver helper. + + * g10.c, options.h: three new options - --{no-}show-photos, and + --photo-viewer to give the command line to display a picture. + + * options.skel: instructions for the photo viewer + + * parse-packet.c (parse_user_id, setup_user_id (new)): common code + for both user IDs and attribute IDs moved to setup_user_id. + + * parse-packet.c (make_attribute_uidname (new)): constructs a fake + "name" for attribute packets (e.g. "[image of size ...]") + + * parse-packet.c (parse_attribute (replaces parse_photo_id), + parse_attribute_subpkts): Builds an array of individual + attributes. Currently only handles attribute image / type jpeg + subpackets. + + * sign.c (hash_uid): Fix bug in signing attribute (formerly + photo_id) packets. + + * packet.h, and callers: globally change "photo_id" to "attribute" + and add structures for attributes. The packet format is generic + attributes, even though the only attribute type thus far defined + is jpeg. + +2001-12-21 David Shaw <dshaw@jabberwocky.com> + + * parse-packet.c (can_handle_critical): Can handle critical + revocation subpackets now. + + * trustdb.c (mark_usable_uid_certs): Disregard revocations for + nonrevocable sigs. Note that this allows a newer revocable + signature to override an older nonrevocable signature. + + * sign.c (make_keysig_packet): add a duration field and change all + callers. This makes make_keysig_packet closer to + write_signature_packets and removes some duplicated expiration + code. + + * keyedit.c (keyedit_menu, menu_revsig, sign_uids, + sign_mk_attrib): Add nrsign command, don't allow revoking a + nonrevocable signature, + + * g10.c (main): Add --nrsign option to nonrevocably sign a key + from the command line. + + * build-packet.c (build_sig_subpkt_from_sig): Comment to explain + the use of CRITICAL. + +2001-12-21 Werner Koch <wk@gnupg.org> + + * g10.c. options.h : New option --show-keyring + * getkey.c (get_ctx_handle): New. + * keylist.c (list_one): Implement option here. By David Champion. + +2001-12-20 David Shaw <dshaw@jabberwocky.com> + + * keyserver.c (keyserver_spawn): Use mkdtemp() to make temp + directory. + + * mkdtemp.c: replacement function for those platforms that don't + have mkdtemp (make a temp directory securely). + +2001-12-19 David Shaw <dshaw@jabberwocky.com> + + * misc.c (check_permissions): New function to stat() and ensure + the permissions of GNUPGHOME and the files have safe permissions. + + * keydb.c (keydb_add_resource): Check keyring permissions. + + * tdbio.c (tdbio_set_dbname): Check permissions of trustdb.gpg + + * keyserver.c (keyserver_spawn): Disable keyserver schemes that + involve running external programs if the options file has unsafe + permissions or ownership. + + * g10.c, options.h: New option --no-permission-warning to disable + the permission warning message(s). This also permits use of the + keyserver if it had been disabled (see above). Also check the + permissions/ownership of random_seed. + + * keyserver.c (keyserver_spawn): The new glibc prints a warning + when using mktemp() (the code was already secure, but the warning + was bound to cause confusion). Use a different implementation + based on get_random_bits() instead. Also try a few times to get + the temp dir before giving up. + +2001-12-19 Werner Koch <wk@gnupg.org> + + * g10.c, passphrase.c [CYGWIN32]: Allow this as an alias for MINGW32. + +2001-12-18 David Shaw <dshaw@jabberwocky.com> + + * g10.c (idea_cipher_warn): Add a flag to show the warning always + or once per session and change all callers (show always except for + the secret key protection and unknown cipher from an encrypted + message errors). Also make the strings translatable. + + * pubkey-enc.c (get_it): Add the IDEA cipher warning if the user + tries to decrypt an IDEA encrypted message without the IDEA + plugin. + + * keyserver.c (parse_keyserver_uri): More strict checking of the + keyserver URI. Specifically, fail if the ":port" section is + anything except a number between 1 and 65535. + +2001-12-17 David Shaw <dshaw@jabberwocky.com> + + * keyserver.c (print_keyinfo): No need to check for + control/illegal characters, as utf8_to_native does this for us. + + * mainproc.c (proc_encrypted): Use generic IDEA warning. + + * gpgv.c: add stub for idea_cipher_warn + + * g10.c, hkp.c, keyserver.c: Fix capitalization and plural issues. + + * encode.c (encode_crypt), sign.c (sign_file, clearsign_file): + disable pgp2 mode after the message is no longer pgp2 compatible. + + * g10.c (main): Tweak the PGP2.x IDEA warning to use the generic + warning, and not merely fail if the IDEA plugin isn't there. + + * g10.c (main, idea_cipher_warn), keygen.c (set_one_pref), + seckey-cert.c (do_check): Add a generic IDEA warning for when the + IDEA plugin is not present. This pops up when the user uses + "--cipher-algo idea", when setpref is used to set a "S1" + preference, and when a secret key protected with IDEA is used. + +2001-12-15 Werner Koch <wk@gnupg.org> + + * keyserver.c (keyserver_spawn): Assert that we have dropped privs. + +2001-12-13 Werner Koch <wk@gnupg.org> + + * pubkey-enc.c (get_session_key): Check that the public key + algorithm is indeed usable for en/decryption. This avoid a + strange error message from pubkey_decrypt if for some reasons a + bad algorithm indentifier is passed. + +2001-12-12 David Shaw <dshaw@jabberwocky.com> + + * Fixed some types for portability. Noted by Stefan Bellon. + +2001-12-11 Werner Koch <wk@gnupg.org> + + * hkp.c (hkp_export): Do not print possible control characters + from a keyserver response. + (parse_hkp_index): Made uid an unsigned char* because it is passed to + isspace(). + (hkp_search): Ditto for the char* vars. + + * g10.c (main): Print the IDEA warning also for -c and -se. + + * g10.c (get_temp_dir): Assert that we have dropped privs + + * encode.c (encode_crypt): Include the first key into the --pgp2 + check. + +2001-12-07 David Shaw <dshaw@jabberwocky.com> + + * g10.c, options.h: New option --pgp2. This is identical to + "--rfc1991 --cipher-algo idea --compress-algo 1 --digest-algo md5 + --force_v3_sigs" with the addition of an warning to advise the + user not to use a pipe (which would break pgp2 compatibility). + + * encode.c (encode_crypt): warn if the user tries to encrypt to + any key that is not RSA and <= 2048 bits when the --pgp2 option is + used. + + * sign.c (sign_file, clearsign_file): When using --pgp2, make a v3 + sig, and warn if the signature is made with a non-v3 key. + +2001-12-05 David Shaw <dshaw@jabberwocky.com> + + * sign.c (sign_file, clearsign_file, sign_symencrypt_file): Prompt + for sig expiration if --expert is set and --force-v3-sigs is not + set (v3 sigs cannot expire). + + * mainproc.c (check_sig_and_print): After checking a sig, print + expiration status. This causes a error return if the sig is + expired. + + * build-packet.c (build_sig_subpkt_from_sig): Include a critical + sig expiration subpacket if the sig is to expire. + + * keyedit.c (sign_uids): Do not sign an expired key unless + --expert is set, in which case prompt. Also, offer to expire a + signature when the key the user is signing expires. + + * keygen.c (ask_expire_interval): Add a value to determine whether + to prompt for a key or sig expiration and change all callers. + + * keyid.c: New functions: expirestr_from_sig and + colon_expirestr_from_sig. + + * keylist.c (list_keyblock_colon): Show sig expiration date in the + --with-colons listing. + + * sign.c (make_keysig_packet, write_signature_packets): Pass in an + optional timestamp for the signature packet, and change all + callers. + + * keyedit.c (sign_mk_attrib): Include a critical expiration + subpacket in the signature if an expiration date is given. + +2001-12-04 David Shaw <dshaw@jabberwocky.com> + + * keyedit.c (sign_uids): If the user tries to sign a + locally-signed key, allow the cert to be promoted to a full + exportable signature. This essentially deletes the old + non-exportable sig, and replaces it with a new exportable one. + +2001-12-04 David Shaw <dshaw@jabberwocky.com> + + * keyedit.c (keyedit_menu): Do not allow signing a revoked key + unless --expert is set, and ask even then. + + * keyedit.c (sign_uids): Do not allow signing a revoked UID unless + --expert is set, and ask even then. + + * g10.c, options.h : New option --expert + +2001-11-16 David Shaw <dshaw@jabberwocky.com> + + * Allow the user to select no compression via "--compress-algo 0" + on the command line. + + * keyedit.c (show_prefs): Show compression preferences in the + long-form "showpref" style. + + * keygen.c (set_one_pref): Permit setting a no-compression ("Z0") + preference. + + * getkey.c (fixup_uidnode): Fix compression preference corruption + bug. + +2001-12-02 David Shaw <dshaw@jabberwocky.com> + + * g10.c: Add advisory --for-your-eyes-only option as per section + 5.9 of 2440. + +2001-12-05 David Shaw <dshaw@jabberwocky.com> + + * Force a V4 sig if the user has a notation or policy URL set. + +2001-12-04 David Shaw <dshaw@jabberwocky.com> + + * g10.c: Add options --keyserver-options, --temp-directory, and + auto-key-retrieve (the opposite of no-auto-key-retrieve). + + * hkp.c (hkp_search): New function to handle searching a HKP + keyserver for a key + + * hkp.c (hkp_ask_import, hkp_export): Pretty large changes to make + them communicate via the generic functions in keyserver.c + + * keyserver.c: new file with generic keyserver routines for + getting keys from a keyserver, sending keys to a keyserver, and + searching for keys on a keyserver. Calls the internal HKP stuff + in hkp.c for HKP keyserver functions. Other calls are handled by + an external program which is spawned and written to and read from + via pipes. Platforms that don't have pipes use temp files. + +2001-11-20 David Shaw <dshaw@jabberwocky.com> + + * options.h, g10.c: New options show-notation, no-show-notation, + default-check-level, no-default-check-level, show-policy-url, + no-show-policy-url. + + * packet.h, sign.c (make_keysig_packet), parse-packet.c + (parse_signature), free-packet.c (free_seckey_enc): Fill in + structures for notation, policy, sig class, exportability, etc. + + * keyedit.c, keylist.c (print_and_check_one_sig, + list_keyblock_print): Show flags in signature display for cert + details (class, local, notation, policy, revocable). If selected, + show the notation and policy url. + + * keyedit.c (sign_uids): Prompt for and use different key sig + classes. + + * helptext.c (helptexts): Add help text to explain different + key signature classes + +2001-11-26 David Shaw <dshaw@jabberwocky.com> + + * trustdb.c (mark_usable_uid_certs): Fix segfault from bad + initialization and fix reversed key signature expiration check. + +2001-11-09 Werner Koch <wk@gnupg.org> + + * export.c (do_export_stream): Put all given names into a search + description and change the loop so that all matching names are + returned. + +2001-11-08 Werner Koch <wk@gnupg.org> + + * pubkey-enc.c (get_it): To reduce the number of questions on the + MLs print the the name of cipher algorithm 1 with the error message. + + * mainproc.c: Changed the way old rfc1991 encryption cipher is + selected. Based on a patch by W Lewis. + + * pkclist.c (do_edit_ownertrust): Allow to skip over keys, the non + working "show info" is now assigned to "i" + * trustdb.c (ask_ownertrust, validate_keys): Implement a real quit + here. Both are by David Shaw. + + * trustdb.c (validate_keys): Make sure next_exipire is initialized. + + * sign.c (make_keysig_packet): Use SHA-1 with v4 RSA keys. + + * g10.c, options.h : New option --[no-]froce-v4-certs. + * sign.c (make_keysig_packet): Create v4 sigs on v4 keys even with + a v3 key. Use that new option. By David Shaw + + * revoke.c (ask_revocation_reason): Allow to select "no reason". + By David Shaw. + + * keyid.c (fingerprint_from_sk): Calculation of an v3 fpr was + plain wrong - nearly the same code in fingerprint_from_pk is correct. + + * build-packet.c (do_secret_key): Added a few comments to the code. + +2001-11-07 Werner Koch <wk@gnupg.org> + + * g10.c (main): Print a warning when -r is used w/o encryption. + Suggested by Pascal Scheffers. + +2001-10-23 Werner Koch <wk@gnupg.org> + + * keyedit.c (keyedit_menu): Changed helptext for showpref + command. Suggested by Reinhard Wobst. + + * keyring.c (keyring_search): When marking the offtbl ready, take + into account that we may have more than one keyring. + +2001-10-22 Werner Koch <wk@gnupg.org> + + * Makefile.am: Do not use OMIT_DEPENDENCIES + + * build-packet.c (build_sig_subpkt): Default is now to put all + types of subpackets into the hashed area and only list those which + should go into the unhashed area. + +2001-10-18 Werner Koch <wk@gnupg.org> + + * keydb.c (keydb_add_resource): Rearranged the way we keep track + of the resource. There will now be an entry for each keyring here + and not in keyring.c itself. Store a token to allow creation of a + keyring handle. Changed all functions to utilize this new design. + (keydb_locate_writable): Make a real implementation. + * keyring.c (next_kr): Removed and changed all callers to set the + resource directly from the one given with the handle. + (keyring_is_writable): New. + (keyring_rebuild_cache): Add an arg to pass the token from keydb. + +2001-10-17 Werner Koch <wk@gnupg.org> + + * keyring.c (keyring_search): Enabled word search mode but print a + warning that it is buggy. + +2001-10-11 Werner Koch <wk@gnupg.org> + + * hkp.c (hkp_ask_import): No more need to set the port number for + the x-hkp scheme. + (hkp_export): Ditto. + +2001-10-06 Stefan Bellon <sbellon@sbellon.de> + + * passphrase.c [__riscos__]: Disabled agent specific stuff. + * g10.c: New option --no-force-v3-sigs. + +2001-10-04 Werner Koch <wk@gnupg.org> + + * export.c (do_export_stream): Do not push the compress filter + here because the context would run out of scope due to the + iobuf_close done by the caller. + (do_export): Do it here instead. + +2001-09-28 Werner Koch <wk@gnupg.org> + + * keyedit.c (sign_uids): Always use the primary key to sign keys. + * getkey.c (finish_lookup): Hack to return only the primary key if + a certification key has been requested. + + * trustdb.c (cmp_kid_for_make_key_array): Renamed to + (validate_one_keyblock): this and changed arg for direct calling. + (make_key_array): Renamed to + (validate_one_keyblock): this and changed args for direct calling. + (mark_usable_uid_certs, validate_one_keyblock) + (validate_key_list): Add next_expire arg to keep track of + expiration times. + (validate_keys): Ditto for UTKs and write the stamp. + + * tdbio.c (migrate_from_v2): Check return code of tbdio_sync. + + * tdbdump.c (import_ownertrust): Do a tdbio_sync(). + + * keyring.c: Made the offtbl an global object. + +2001-09-27 Werner Koch <wk@gnupg.org> + + * pkclist.c (do_edit_ownertrust): Allow settin of ultimate trust. + + * trustdb.c (mark_keyblock_seen): New. + (make_key_array): Use it to mark the subkeys too. + (validate_keys): Store validity for ultimatly trusted keys. + +2001-09-26 Werner Koch <wk@gnupg.org> + + * pkclist.c (check_signatures_trust, do_we_trust): Removed the + invocation of add_ownertrust. Minor changes to the wording. + (add_ownertrust, add_ownertrust_cb): Removed. + + * trustdb.c (get_validity): Allow to lookup the validity using a + subkey. + + * trustdb.c (new_key_hash_table): Increased the table size to 1024 + and changed the masks accordingly. + (validate): Changed stats printing. + (mark_usable_uid_certs): New. + (cmp_kid_for_make_key_array): Does now check the signatures and + figures out a usable one. + +2001-09-25 Werner Koch <wk@gnupg.org> + + * keyring.c (new_offset_item,release_offset_items) + (new_offset_hash_table, lookup_offset_hash_table) + (update_offset_hash_table, update_offset_hash_table_from_kb): New. + (keyring_search): Use a offset table to optimize search for + unknown keys. + (keyring_update_keyblock, keyring_insert_keyblock): Insert new + offsets. + * getkey.c (MAX_UNK_CACHE_ENTRIES): Removed the unknown keys + caching code. + + * g10.c, options.h, import.c: Removed the entire + allow-secret-key-import stuff because the validity is now + controlled by other means. + + * g10.c: New command --rebuild-keydb-caches. + * keydb.c (keydb_rebuild_caches): New. + * keyring.c (do_copy): Moved some code to + (create_tmp_file, rename_tmp_file, write_keyblock): new functions. + (keyring_rebuild_cache): New. + + * packet.h (PKT_ring_trust): Add sigcache field. + * parse-packet.c (parse_trust): Parse sigcache. + * keyring.c (do_copy): Always insert a sigcache packet. + (keyring_get_keyblock): Copy the sigcache packet to the signature. + * sig-check.c (cache_sig_result): Renamed from + cache_selfsig_result. Changed implementation to use the flag bits + and changed all callers. + (mdc_kludge_check): Removed this unused code. + (do_check): Do not set the sig flags here. + + * import.c (read_block): Make sure that ring_trust packets are + never imported. + * export.c (do_export_stream): and never export them. + + * trustdb.c (make_key_array): Skip revoked and expired keys. + +2001-09-24 Werner Koch <wk@gnupg.org> + + * g10.c, options.h: New option --no-auto-check-trustdb. + + * keygen.c (do_generate_keypair): Set newly created keys to + ultimately trusted. + + * tdbio.h, tdbio.c: Removed all support for records DIR, KEY, UID, + PREF, SIG, SDIR and CACH. Changed migration function to work + direct on the file. + (tdbio_read_nextcheck): New. + (tdbio_write_nextcheck): New. + +2001-09-21 Werner Koch <wk@gnupg.org> + + Revamped the entire key validation system. + * trustdb.c: Complete rewrite. No more validation on demand, + removed some functions, adjusted to all callers to use the new + and much simpler interface. Does not use the LID anymore. + * tdbio.c, tdbio.h: Add new record types trust and valid. Wrote a + migration function to convert to the new trustdb layout. + * getkey.c (classify_user_id2): Do not allow the use of the "#" + prefix. + * keydb.h: Removed the TDBIDX mode add a skipfnc to the + descriptor. + * keyring.c (keyring_search): Implemented skipfnc. + + * passphrase.c (agent_open): Add missing bracket. Include windows.h. + +2001-09-19 Werner Koch <wk@gnupg.org> + + * keylist.c (print_fingerprint): Renamed from fingerprint, made + global available. Added new arg to control the print style. + * mainproc.c (print_fingerprint): Removed. + * pkclist.c (print_fpr, fpr_info): Removed and changed callers to + use print_fingerprint. + * keyedit.c (show_fingerprint): Ditto. + + * passphrase.c (writen, readn) + (agent_open, agent_close) + (agent_get_passphrase) + (passphrase_clear_cache): Support for W32. Contributed by Timo. + + * import.c (import_one): Release keydb handles at 2 more places. + + * keyring.c (keyring_release): Close the iobuf. + (keyring_get_keyblock): Init ret_kb to NULL and store error contidion. + + * import.c (import_new_stats_handle): New. + (import_release_stats_handle): New. + (import_print_stats): Renamed from static fnc print_stats. + (import_keys, import_keys_stream): Add an optional status handle + arg and changed all callers. + * hkp.c (hkp_ask_import): Add an stats_handle arg and changed all + callers. + + * mainproc.c (print_pkenc_list): Use print_utf8_string2(). + +2001-09-18 Werner Koch <wk@gnupg.org> + + * g10.c: New command --refresh-keys. + * hkp.c (hkp_refresh_keys): New. Contributed by Timo Schulz. + + * parse-packet.c (parse): Stop on impossible packet lengths. + +2001-09-17 Werner Koch <wk@gnupg.org> + + * mainproc.c (print_notation_data): Wrap notation data status lines + after 50 chars. + + * mainproc.c (proc_pubkey_enc): Make option try-all-secrets work. + By disastry@saiknes.lv. + +2001-09-14 Werner Koch <wk@gnupg.org> + + * parse-packet.c (dump_sig_subpkt): List key server preferences + and show the revocable flag correctly. Contributed by David Shaw. + +2001-09-09 Werner Koch <wk@gnupg.org> + + * keyedit.c (keyedit_menu): No need to define another p. + + * keylist.c (print_capabilities): s/used/use/ so that it + does not shadow a global. + * sign.c (sign_file): Renamed arg encrypt to encryptflag + * keygen.c: Replaced all "usage" by "use". + * misc.c (openpgp_pk_algo_usage): Ditto. + + * pubkey-enc.c (get_it): Renamed arg k to enc so that the later + defined k does not shadow it. + + * parse-packet.c (parse_gpg_control): No need to define another i. + + * getkey.c (get_pubkey_byfprint): Must use the enum values and not + the fprint_len. + * keyring.c (keyring_search): Removed a non-sense break. Both + bugs pointed out by Stefan. + +2001-09-07 Werner Koch <wk@gnupg.org> + + * status.c, status.h: Added NO_RECP and ALREADY_SIGNED. + * pkclist.c (build_pk_list): Issue NO_RECP. + * keyedit.c (sign_uids): Added experimental ALREADY_SIGNED + + * hkp.c (hkp_import): Use log_error. Bug reported by Neal H + Walfield. + + * getkey.c (classify_user_id2): Change args to take the desc union + direct. It was a stupid idea to pass the individual fields of an + union to this function. Changed all callers. + (classify_user_id): Ditto and allow to pass NULL as the description. + +2001-09-06 Werner Koch <wk@gnupg.org> + + * getkey.c (fixup_uidnode): Features flag is now a bit vector. + * keygen.c (add_feature_mdc): Ditto. + + Revamped the entire key I/O code to be prepared for other ways of + key storages and to get rid of the existing shit. GDBM support has + gone. + * keydb.c: New + * keyring.c, keyring.h: New. + * ringedit.c: Removed. Moved some stuff to keyring.c + * getkey.c: Changed everything related to the key retrieving + functions which are now using the keydb_ functions. + (prepare_search, word_match_chars, word_match) + (prepare_word_match, compare_name): Moved to keyring.c + (get_pubkey_byname): Removed ctx arg and add ret_kdbhd + arg. Changed all callers. + (key_byname): Use get_pubkey_end to release the context and take + new ret_kbdhd arg. Changed all callers. + (classify_user_id2): Fill the 16 byte fingerprint up with 4 null + bytes not with zero bytes of value 4, tsss. + * import.c (import_one): Updated to use the new keydb interface. + (import_secret_one): Ditto. + (import_revoke_cert): Ditto. + * delkey.c (do_delete_key): Ditto. + * keyedit.c (keyedit_menu): Ditto. + (get_keyblock_byname): Removed. + * revoke.c (gen_revoke): Ditto. + * export.c (do_export_stream): Ditto. + * trustdb.c (update_trustdb): Ditto. + * g10.c, gpgv.c (main): Renamed add_keyblock_resource to + keydb_add_resource. + * Makefile.am: Added and removed files. + + * keydb.h: Moved KBNODE typedef and MAX_FINGERPRINT_LEN to + * global.h: this new header. + +2001-09-03 Werner Koch <wk@gnupg.org> + + * passphrase.c (agent_get_passphrase): Changed nread to size_t. + (passphrase_clear_cache): Ditto. + + * keyid.c (mk_datestr): Avoid trigraphs. + (fingerprint_from_pk): Cache the keyid in the pk. + + * options.h: Add opt.with_fingerprint so that we know whether the + corresponding options was used. + * g10.c (main): Set it here. + * pkclist.c (check_signatures_trust): Always print fingerprint + when this option is used. Mixed a minor memory leak. + + * status.c, status.h: New status INV_RECP. + * pkclist.c (build_pk_list): Issue this status. + +2001-08-31 Werner Koch <wk@gnupg.org> + + * parse-packet.c (parse_key,parse_pubkeyenc) + (parse_signature): Return error on reading bad MPIs. + + * mainproc.c (check_sig_and_print): Always print the user ID even + if it is not bound by a signature. Use the primary UID in the + status messages and encode them in UTF-8 + * status.c (write_status_text_and_buffer): New. + +2001-08-30 Werner Koch <wk@gnupg.org> + + * packet.h (sigsubpkttype_t): Add SIGSUBPKT_FEATURES. + (PKT_public_key, PKT_user_id): Add a flag for it. + * parse-packet.c, build-packet.c: Add support for them. + * getkey.c (fixup_uidnode, merge_selfsigs): Set the MDC flags. + * keygen.c (add_feature_mdc): New. + (keygen_upd_std_prefs): Always set the MDC feature. + * keyedit.c (show_prefs): List the MDC flag + * pkclist.c (select_mdc_from_pklist): New. + * encode.c (encode_crypt, encrypt_filter): Test whether MDC + should be used. + * cipher.c (write_header): Set MDC use depending on the above test. + Print more status info. + + * delkey.c (do_delete_key): Kludge to delete a secret key with no + public key available. + + * ringedit.c (find_secret_keyblock_direct): New. + * getkey.c (seckey_available): Simplified. + + * ringedit.c (cmp_seckey): Now compares the secret key against the + public key while ignoring all secret parts. + (keyring_search): Use a public key packet as arg. Allow to search + for subnkeys + (search): Likewise. Changed all callers. + (find_secret_keyblock_bypk): New. + (find_secret_keyblock_byname): First locate the pubkey and then + find the correponding secret key. + * parse-packet.c (parse): Renamed pkttype arg to onlykeypkts and + changed code accordingly. Changed all callers. + (search_packet): Removed pkttype arg. + * keyedit.c (keyedit_menu): First locate the public key and then + try to locate a secret key. + + * ringedit.c (locate_keyblock_by_fpr): Removed. + (locate_keyblock_by_keyid): Removed. + (find_keyblock_bysk): Removed. + + * sig-check.c (check_key_signature2): Print the keyid along with + the wrong sig class errors. + +2001-08-24 Werner Koch <wk@gnupg.org> + + * sign.c (sign_file): Stripped the disabled comment packet code. + (sign_file, sign_symencrypt_file): Moved common code to .. + (write_onepass_sig_packets): .. this new function. + (sign_file, clearsign_file, sign_symencrypt_file): Moved common + code to + (write_signature_packets): this new function. + (write_signature_packets, make_keysig_packet) + (update_keysig_packet): Moved common code to + (hash_uid, hash_sigclass_to_magic): these new functions + (sign_file, sign_symencrypt_file): Moved common code to + (write_plaintext_packet): this new function. + +2001-08-21 Stefan Bellon <sbellon@sbellon.de> + + * trustdb.c (query_trust_info): Changed trustlevel to signed int. + * g10.c [__riscos__]: Fixed handling of --use-agent --lock-multiple. + +2001-08-20 Werner Koch <wk@gnupg.org> + + * encr-data.c (decrypt_data): Keep track on whether we already + printed information about the used algorithm. + * mainproc.c (proc_encrypted): Removed the non-working IDEA hack + and print a message about the assumed algorithm. + * passphrase.c (passphrase_to_dek): Use the same algorithm as above. + (proc_symkey_enc): Print the algorithm, so that the user knows it + before entering the passphrase. + (proc_pubkey_enc, proc_pubkey_enc): Zero the DEK out. + * encode.c (encode_crypt, encrypt_filter): Ditto. + + * g10.c: Allow for --sign --symmetric. + * sign.c (sign_and_symencrypt): New. + + Applied patches from Stefan Bellon <sbellon@sbellon.de> to support + RISC OS. Nearly all of these patches are identified by the + __riscos__ macro. + * compress.c: Added a couple of casts. + * g10.c [__riscos__]: Some patches and new options foo-file similar + to all foo-fd options. + * gpgv.c, openfile.c, ringedit.c, tdbio.c: Minor fixes. Mainly + replaced hardcoded path separators with EXTSEP_S like macros. + * passprase.c [__riscos__]: Disabled agent stuff + * trustdb.c (check_trust): Changed r_trustlevel to signed int to + avoid mismatch problems in pkclist.c + * pkclist.c (add_ownertrust): Ditto. + * plaintext.c (handle_plaintext) [__riscos__]: Print a note when + file can't be created. + * options.h [__riscos__]: Use an extern unless included from the + main module. + * signal.c (got_fatal_signal) [__riscos__]: Close all files. + +2001-08-14 Werner Koch <wk@gnupg.org> + + * keygen.c (ask_algo): New arg r_usage. Allow for RSA keys. + (gen_rsa): Enabled the code. + (do_create): Enabled RSA branch. + (parse_parameter_usage): New. + (proc_parameter_file): Handle usage parameter. + (read_parameter_file): Ditto. + (generate_keypair): Ditto. + (generate_subkeypair): Ditto. + (do_generate_keypair): Ditto. + (do_add_key_flags): New. + (keygen_add_std_prefs): Use the new function. + (keygen_add_key_flags_and_expire): New. + (write_selfsig, write_keybinding): Handle new usage arg. + * build-packet.c (build_sig_subpkt): Make sure that key flags go + into the hashed area. + + * keygen.c (write_uid): Initialize the reference cunter. + + * keyedit.c (keyedit_menu): No more need to update the trustdb for + preferences. Added calls to merge keblock. + + * kbnode.c (dump_kbnode): Print some more flags. + +2001-08-10 Werner Koch <wk@gnupg.org> + + Revamped the preference handling. + + * packet.h (prefitem_t, preftype_t): New. + (PKT_public_key): Added a uid field. + (PKT_user_id): Added field to store preferences and a reference + counter. + * parse-packet.c (parse_user_id,parse_photo_id): Initialize them + * free-packet.c (free_user_id): Free them. + (copy_user_id): Removed. + (scopy_user_id): New. + (cmp_user_ids): Optimized for identical pointers. + (release_public_key_parts): Release the uid. + (copy_public_key_with_new_namehash): Removed. + (copy_prefs): New. + * keyedit.c (menu_adduid): Use the new shallow copy user id. + (show_prefs): Adjusted implementation. + (keyedit_menu): No more need to update the trustdb after changing + preferences. + * getkey.c (fixup_uidnode): Store preferences. + (find_by_name): Return a user id packet and remove namehash stuff. + (lookup): Removed the unused namehash stuff. + (finish_lookup): Added foundu arg. + (pk_from_block): Removed the namehash arg and changed all callers. + (merge_selfsigs): Copy prefs to all keys. + * trustdb.c (get_pref_data): Removed. + (is_algo_in_prefs): Removed. + (make_pref_record): Deleted and removed all class. + * pkclist.c (select_algo_from_prefs): Adjusted for the new + preference implementation. + * pubkey-enc.c (is_algo_in_prefs): New. + (get_it): Use that new function. + +2001-08-09 Werner Koch <wk@gnupg.org> + + * build-packet.c (build_sig_subpkt): Fixed calculation of + newarea->size. + + * g10.c (main): New option "--preference-list" + * keyedit.c (keyedit_menu): New commands "setpref" and "updpref". + (menu_set_preferences): New. + * keygen.c (keygen_set_std_prefs): New. + (set_one_pref): New. + (check_zip_algo): New. + (keygen_get_std_prefs): New. + (keygen_upd_std_prefs): New + (keygen_add_std_prefs): Move the pref setting code into the above fnc. + * build-packet.c (build_sig_subpkt): Updated the list of allowed + to update subpackets. + +2001-08-08 Werner Koch <wk@gnupg.org> + + * packet.h (subpktarea_t): New. + (PKT_signature): Use that type for hashed_data and unhashed_data and + removed the _data prefix from those fields. Changed all users. + * parse-packet.c (parse_signature): Changed allocation for that. + (parse_sig_subpkt): Changed declaration + (enum_sig_subpkt): Ditto and changed implementation accordingly. + * free-packet.c (cp_subpktarea): Renamed from cp_data_block and + adjusted implementation. Changed caller. + * sig-check.c (mdc_kludge_check): Adjusted the hashing. + (do_check): Ditto. + * sign.c (sign_file, clearsign_file, make_keysig_packet, + update_keysig_packet): Ditto. + * build-packet.c (build_sig_subpkt): Partial rewrite. + (find_subpkt): Adjusted and made static. + (delete_sig_subpkt): Adjusted. + (do_signature): Ditto. + + * keygen.c (ask_keysize): Do not print the notes about suggested + key sizes if just a DSA key is generated. + + * trustdb.c (add_ultimate_key): s/log_error/log_info/ for + duplicated inserted trusted keys. + +2001-08-07 Werner Koch <wk@gnupg.org> + + * sign.c (sleep): Redefine for W32. + + * g10.c, options.h: Set new flag opt.no_homedir_creation when + --no-options is given. + * openfile.c (try_make_homedir): Don't create the homedir in that case. + +2001-08-03 Werner Koch <wk@gnupg.org> + + * armor.c (armor_filter): Removed the default comment string + because it could get us in trouble due to translations using non + ascii characters. + +2001-08-01 Werner Koch <wk@gnupg.org> + + * keylist.c (list_keyblock_print): Do not list revoked UIDs unless + in verbose mode and we do no signature listing. + + * getkey.c (finish_lookup): Skip subkeys which are not yet valid. + * g10.c, options.h: New option --ignore-valid-from. + + * sign.c (make_keysig_packet): Added new sigversion argument to + allow the caller to force generation of required signature + version. Changed all callers. Suggested by Thomas Roessler. + + * keyedit.c (sign_uids): Force v4 signature generation for local + sigs. Removed the check for local signature and pre-v4 keys. + +2001-07-27 Werner Koch <wk@gnupg.org> + + * keyedit.c (sign_uids): Check that we are not trying to to a + lsign with a pre-v4 key. Bug noticed by Thomas Roessler. + +2001-07-26 Werner Koch <wk@gnupg.org> + + * parse-packet.c (parse_photo_id): Reset all variables. + * getkey.c (merge_selfsigs_main): Removed checks on PHOTO_ID + because this is handled identically to a user ID. + +2001-07-06 Werner Koch <wk@gnupg.org> + + * cipher.c (write_header): Don't use MDC with --rfc1991. Suggested + by disastry@saiknes.lv. + +2001-07-05 Werner Koch <wk@gnupg.org> + + * g10.c, options.h: New option --preserve-permissions. + * ringedit.c (add_keyblock_resource): Use it here + (keyring_copy): and here. + + * trustdb.c (verify_own_keys): Be more silent on --quiet. + Suggested by Thomas Roessler. + * sig-check.c (check_key_signature2): Ditto. + * mainproc.c (proc_encrypted, proc_tree): Ditto + * getkey.c (lookup): Ditto. + +2001-07-04 Werner Koch <wk@gnupg.org> + + * ringedit.c (add_keyblock_resource): Restore filename in case of error. + +2001-06-25 Werner Koch <wk@gnupg.org> + + * kbnode.c (dump_kbnode): Print the signature timestamp. + + * keyedit.c (keyedit_menu): New menu point "primary". + (change_primary_uid_cb): New. + (menu_set_primary_uid): New. + * sign.c (update_keysig_packet): New. + * build-packet.c (build_sig_subpkt): Put the primary UID flag into + the hashed area. Allow update of some more packets. + +2001-06-15 Werner Koch <wk@gnupg.org> + + * getkey.c (merge_selfsigs): Exit gracefully when a secret key is + encountered. May happen if a secret key is in public keyring. + Reported by Francesco Potorti. + +2001-06-12 Werner Koch <wk@gnupg.org> + + * getkey.c (compare_name): Use ascii_memistr(), ascii_memcasecmp() + * keyedit.c (keyedit_menu): Use ascii_strcasecmp(). + * armor.c (radix64_read): Use ascii_toupper(). + * ringedit.c (do_bm_search): Ditto. + * keygen.c (read_parameter_file): Ditto. + * openfile.c (CMP_FILENAME): Ditto. + * g10.c (i18n_init): We can now use just LC_ALL. + +2001-05-29 Werner Koch <wk@gnupg.org> + + * keygen.c (generate_subkeypair): Print a warning if a subkey is + created on a v3 key. Suggested by Brian M. Carlson. + +2001-05-27 Werner Koch <wk@gnupg.org> + + * keyid.c (get_lsign_letter): New. + * keylist.c (list_keyblock_colon): Use it here. + * mainproc.c (list_node): and here. + + * getkey.c, packet.h, free-packet.c: Removed that useless key + created field; I dunno why I introducded this at all - the + creation time is always bound to the key packet and subject to + fingerprint calculation etc. + + * getkey.c (fixup_uidnode): Add keycreated arg and use this + instead of the signature timestamp to calculate the + help_key_expire. Bug reported by David R. Bergstein. + (merge_selfsigs_main): Correct key expiration time calculation. + (merge_selfsigs_subkey): Ditto. + +2001-05-25 Werner Koch <wk@gnupg.org> + + * revoke.c (gen_revoke): Add a cast to a tty_printf arg. + * delkey.c (do_delete_key): Ditto. + * keyedit.c (print_and_check_one_sig): Ditto. + (ask_revoke_sig): Ditto. + (menu_revsig): Ditto. + (check_all_keysigs): Removed unused arg. + +2001-05-23 Werner Koch <wk@gnupg.org> + + * g10.c (opts): Typo fix by Robert C. Ames. + +2001-05-06 Werner Koch <wk@gnupg.org> + + * revoke.c: Small typo fix + +2001-05-04 Werner Koch <wk@gnupg.org> + + * passphrase.c (passphrase_clear_cache): Shortcut if agent usage + is not enabled. + +2001-05-01 Werner Koch <wk@gnupg.org> + + * passphrase.c (writen): Replaced ssize_t by int. Thanks to + to Robert Joop for reporting that SunOS 4.1.4 does not have it. + +2001-04-28 Werner Koch <wk@gnupg.org> + + * getkey.c (merge_public_with_secret): pkttype was not set to subkey. + +2001-04-27 Werner Koch <wk@gnupg.org> + + * skclist.c (build_sk_list): Changed one log_debug to log_info. + +2001-04-25 Werner Koch <wk@gnupg.org> + + * keyedit.c (show_prefs): Add a verbose mode. + (show_key_with_all_names): Pass verbose flag for special value of + with_pref. + (keyedit_menu): New command "showpref" + (show_key_with_all_names): Mark revoked uids and the primary key. + +2001-04-24 Werner Koch <wk@gnupg.org> + + * getkey.c (get_primary_uid): Return a different string in case of + error and made it translatable. + + * build-packet.c (do_secret_key): Ugly, we wrote a zero + instead of the computed ndays. Thanks to M Taylor for complaining + about a secret key import problem. + +2001-04-23 Werner Koch <wk@gnupg.org> + + * hkp.c (hkp_ask_import): Allow to specify a port number for the + keyserver. Add a kudge to set the no_shutdown flag. + (hkp_export): Ditto. + * options.skel: Document the changes + +2001-04-20 Werner Koch <wk@gnupg.org> + + * options.skel: Add some more comments. + +2001-04-19 Werner Koch <wk@gnupg.org> + + * keyid.c (mk_datestr): New. Handles negative times. We must do + this because Windoze segvs on negative times passed to gmtime(). + Changed all datestr_from function to use this one. + + * keyid.c, keyid.h (colon_strtime): New. To implement the + fixed-list-mode. + (colon_datestr_from_pk): New. + (colon_datestr_from_sk): New. + (colon_datestr_from_sig): New. + * keylist.c (list_keyblock_colon): Use these functions here. + * mainproc.c (list_node): Ditto. + +2001-04-18 Werner Koch <wk@gnupg.org> + + * openfile.c (open_sigfile): Fixed the handling of ".sign". + * mainproc.c (proc_tree): Use iobuf_get_real_fname. + Both are by Vincent Broman. + +2001-04-14 Werner Koch <wk@gnupg.org> + + * getkey.c (fixup_uidnode): Removed check for !sig which is + pointless here. Thanks to Jan Niehusmann. + +2001-04-10 Werner Koch <wk@gnupg.org> + + * sig-check.c (check_key_signature2): Use log_info instead of + log_error so that messed up keys do not let gpg return an error. + Suggested by Christian Kurz. + + * getkey.c (merge_selfsigs_main): Do a fixup_uidnode only if we + have both, uid and sig. Thanks to M Taylor. + +2001-04-05 Werner Koch <wk@gnupg.org> + + * armor.c (unarmor_pump_new,unarmor_pump_release): New. + (unarmor_pump): New. + * pipemode.c (pipemode_filter): Use the unarmor_pump to handle + armored or non-armored detached signatures. We can't use the + regular armor_filter becuase this does only chack for armored + signatures the very first time. In pipemode we may have a mix of + armored and binary detached signatures. + * mainproc.c (proc_tree): Do not print the "old style" notice when + this is a pipemode processes detached signature. + (proc_plaintext): Special handling of pipemode detached sigs. + + * packet.h (CTRLPKT_PLAINTEXT_MARK): New. + * parse-packet.c (create_gpg_control): New. + * kbnode.c (dump_kbnode): Support it here. + * mainproc.c (check_sig_and_print): Fixed the check for bad + sequences of multiple signatures. + (proc_plaintext): Add the marker packet. + (proc_tree): We can now check multiple detached signatures. + +2001-04-02 Werner Koch <wk@gnupg.org> + + The length of encrypted packets for blocksizes != 8 was not + correct encoded. I think this is a minor problem, because we + usually use partial length packets. Kudos to Kahil D. Jallad for + pointing this out. + * packet.h: Add extralen to PKT_encrypted. + * cipher.c (write_header): Set extralen. + * build-packet.c (do_encrypted): Use extralen instead of const 10. + (do_encrypted_mdc): Ditto. + * parse-packet.c (parse_encrypted): Set extralen to 0 because we + don't know it here. + +2001-03-30 Werner Koch <wk@gnupg.org> + + * getkey.c (premerge_public_with_secret): Changed wording an add + the keyID to the info message. + +2001-03-29 Werner Koch <wk@gnupg.org> + + * getkey.c (premerge_public_with_secret): Use log_info instead of + log_error when no secret key was found for a public one. + Fix the usage if the secret parts of a key are not available. + + * openfile.c (ask_outfile_name): Trim spaces. + (open_outfile): Allow to enter an alternate filename. Thanks to + Stefan Bellon. + * plaintext.c (handle_plaintext): Ditto. + +2001-03-28 Werner Koch <wk@gnupg.org> + + * mainproc.c (do_check_sig): Allow direct key and subkey + revocation signature. + * sig-check.c (check_key_signature2): Check direct key signatures. + Print the signature class along with an error. + +2001-03-27 Werner Koch <wk@gnupg.org> + + * packet.h: Add a missing typedef to an enum. Thanks to Stefan Bellon. + + * g10.c: New option --no-sig-create-check. + * sign.c (do_sign): Implement it here. + * g10.c: New option --no-sig-cache. + * sig-check.c (check_key_signature2): Implement it here. + (cache_selfsig_result): and here. + + * keylist.c (list_keyblock): Removed debugging stuff. + + * getkey.c (cache_public_key): Made global. + * keygen.c (write_selfsig, write_keybinding): Cache the new key. + + * getkey.c (key_byname): Add new arg secmode and changed all + callers to request explicitly the mode. Deriving this information + from the other supplied parameters does not work if neither pk nor + sk are supplied. + +2001-03-25 Werner Koch <wk@gnupg.org> + + * packet.h (ctrlpkttype_t): New. + * mainproc.c (add_gpg_control,proc_plaintext,proc_tree): Use the + new enum values. + * pipemode.c (make_control): Ditto. + * armor.c (armor_filter): Ditto. + +2001-03-24 Werner Koch <wk@gnupg.org> + + * sign.c (do_sign): Verify the signature right after creation. + +2001-03-23 Werner Koch <wk@gnupg.org> + + * status.c, status.h (STATUS_UNEXPECTED): New. + * mainproc.c (do_proc_packets): And emit it here. + +2001-03-21 Werner Koch <wk@gnupg.org> + + * status.c: Add sys/types.h so that it runs on Ultrix. Reported + by Georg Schwarz.x + + * build-packet.c (build_sig_subpkt): Fixed generaton of packet + length header in case where 2 bytes headers are needed. Thanks to + Piotr Krukowiecki. + +2001-03-19 Werner Koch <wk@gnupg.org> + + * g10.c (main): the default keyring is no always used unless + --no-default-keyring is given. + + * ringedit.c (add_keyblock_resource): invalidate cache after file + creation. + +2001-03-15 Werner Koch <wk@gnupg.org> + + * keygen.c (ask_algo): Changed the warning of the ElGamal S+E Algo. + + * keylist.c (print_capabilities): New. + (list_keyblock_colon): and use it here. + +2001-03-13 Werner Koch <wk@gnupg.org> + + * main.c, options.h: New option --fixed_list_mode. + * keylist.c (list_keyblock_colon): use it here. + + * getkey.c (merge_keys_and_selfsig): Divert merging of public keys + to the function used in key selection.. + * keylist.c (is_uid_valid): Removed. + (list_keyblock): Splitted into .. + (list_keyblock_print, list_keyblock_colon): .. these. + functions. Changed them to use the flags set in the key lookup code. + (reorder_keyblock): New, so that primary user IDs are listed first. + + * ringedit.c (keyring_copy): flush the new iobuf chaces before + rename or remove operations. This is mainly needed for W32. + + * hkp.c [HAVE_DOSISH_SYSTEM]: Removed the disabled code because we + have now W32 socket support in ../util/http.c + + * skclist.c (key_present_in_sk_list): New. + (is_duplicated_entry): New. + (build_sk_list): Check for duplicates and do that before unlocking. + +2001-03-12 Werner Koch <wk@gnupg.org> + + * armor.c (parse_header_line): Removed double empty line check. + (parse_header_line): Replaced trim_trailing_ws with a counting + function so that we can adjust for the next read. + + * options.skel: Fixed 3 typos. By Thomas Klausner. Replaced the + keyserver example by a better working server. + + * parse-packet.c (parse_symkeyenc): Return Invalid_Packet on error. + (parse_pubkeyenc): Ditto. + (parse_onepass_sig): Ditto. + (parse_plaintext): Ditto. + (parse_encrypted): Ditto. + (parse_signature): Return error at other places too. + (parse_key): Ditto. + * g10.c (main): Set opt.list_packets to another value when invoked + with the --list-packets command. + * mainproc.c (do_proc_packets): Don's stop processing when running + under --list-packets command. + + * signal.c (do_sigaction): Removed. + (init_one_signal): New to replace the above. Needed to support + systems without sigactions. Suggested by Dave Dykstra. + (got_fatal_signal,init_signals): Use the above here. + (do_block): Use sigset() if sigprocmask() is not available. + + * armor.c (parse_hash_header): Test on TIGER192, which is the + correct value as per rfc2440. By Edwin Woudt. + +2001-03-08 Werner Koch <wk@gnupg.org> + + * misc.c: Include time.h. By James Troup. + + * getkey.c: Re-enabled the unknown user Id and PK caches and + increased their sizes. + + * getkey.c (merge_selfsigs_main): Set expire date and continue + processing even if we found a revoked key. + (merge_selfsigs_subkeys): Ditto. + + * packet.h: Add an is_revoked flag to the user_id packet. + * getkey.c (fixup_uidnode): Set that flag here. + (merge_selfsigs_main): Fix so that the latest signature is used to + find the self-signature for an UID. + * parse-packet.c (parse_user_id): Zero out all fields. + * mainproc.c (check_sig_and_print): Print the primary user ID + according the the node flag and then all other non-revoked user IDs. + (is_uid_revoked): Removed; it is now handled by the key selection code. + + Changed the year list of all copyright notices. + +2001-03-07 Werner Koch <wk@gnupg.org> + + * getkey.c (finish_lookup): Print an info message only in verbose mode. + +2001-03-05 Werner Koch <wk@gnupg.org> + + * packet.h: Replaced sigsubpkt_t value 101 by PRIV_VERIFY_CACHE. + We have never used the old value, so we can do this without any harm. + * parse-packet.c (dump_sig_subpkt): Ditto. + (parse_one_sig_subpkt): Parse that new sub packet. + * build-packet.c (build_sig_subpkt): Removed the old one from the + hashed area. + (delete_sig_subpkt): New. + (build_sig_subpkt): Allow an update of that new subpkt. + * sig-check.c (check_key_signature2): Add verification caching + (cache_selfsig_result): New. + * export.c (do_export_stream): Delete that sig subpkt before exporting. + * import.c (remove_bad_stuff): New. + (import): Apply that function to all imported data + +2001-03-03 Werner Koch <wk@gnupg.org> + + * getkey.c: Introduced a new lookup context flag "exact" and used + it in all place where we once used primary. + (classify_user_id2): Replaced the old function and add an extra + argument to return whether an exact keyID has been requested. + (key_byname): Removed the unused ctx.primary flag + (get_seckey_byname2): Ditto. + (finish_lookup): Changed debugging output. + +2001-03-02 Werner Koch <wk@gnupg.org> + + * keylist.c (list_one): Remove the merge key calls. + +2001-03-01 Werner Koch <wk@gnupg.org> + + * getkey.c (finish_lookup): Don't use it if we no specific usage + has been requested. + (merge_selfsigs_main): fix UID only if we have an signature. + (lookup): Return UNU_PUBKEY etc. instead of NO_PUBKEY if we found + a key but the requested usage does not allow this key. + * import.c (import_one): Take UNU_PUBKEY into account. + * mainproc.c (list_node): Ditto. + * keylist.c (list_keyblock): Ditto. + * keyedit.c (print_and_check_one_sig): Ditto. + +2001-02-09 Werner Koch <wk@gnupg.org> + + * delkey.c (delete_key): Removed that silly assert which rendered + the whole new stuff meaningless. + +2001-02-08 Werner Koch <wk@gnupg.org> + + * getkey.c (key_byname): It can happen that we have both, sk and pk + NULL, fix for that. + + * parse-packet.c (parse_one_sig_subpkt): Add support for + primary_uid and key_flags. + (can_handle_critical): Ditto + + * parse-packet.c (parse_encrypted): Fixed listing of pktlen for + MDC packets. + + * getkey.c: Backported the version of this file from gpg 1.1. this + involved some changes in other files too. + * parse-packet.c (parse_key): Clear req_usage. + * skclist.c (build_sk_list): Use req_usage to pass the usage + information to the lookup function. + * pkclist.c (build_pk_list): Ditto. + * free-packet.c (copy_public_parts_to_secret_key): New. + * keydb.h: Add IS_* macros to check the sig_class. + * misc.c (openpgp_cipher_test_algo): New. + (openpgp_pk_test_algo): New. + (openpgp_pk_algo_usage): New. + (openpgp_md_test_algo): New. + * packet.h: Add a few fields to PKT_{public,secret}_key and + PKT_user_id. + * seckey-cert.c (do_check): Use the new main_keyid field. + +2001-02-04 Werner Koch <wk@gnupg.org> + + * encr-data.c (decrypt_data): Catch error when we had problems to + parse the encrypted packet. By Timo. + +2001-01-29 Werner Koch <wk@gnupg.org> + + * g10.c (main): --batch does now set nogreeting. + + * delkey.c (do_delete_key): Fixed delete-both functionality. + +2001-01-22 Werner Koch <wk@gnupg.org> + + * g10.c: New command --delete-secret-and-public-key. + * delkey.c (delete_key): Add new arg allow_both. + (do_delete_key): Move most stuff from above to this new function. + +2001-01-12 Werner Koch <wk@gnupg.org> + + * passphrase.c (passphrase_to_dek): Use MD5 when IDEA is installed + and we have no S2K. + * mainproc.c (proc_encrypted): Likewise + +2001-01-11 Werner Koch <wk@gnupg.org> + + * sig-check.c (do_check): Print the signature key expire message + only in verbose mode and added the keyID. + +2001-01-09 Werner Koch <wk@gnupg.org> + + * status.c, status.h: New status USERID_HINT. + (write_status_text): Replace LF and CR int text by C-escape sequence. + + * passphrase.c (passphrase_to_dek): Fixed the NEED_PASSPHRASE + output. It does now always print 2 keyIDs. Emit the new + USERID_HINT. + +2001-01-08 Werner Koch <wk@gnupg.org> + + * g10.c, options.h: New option --no-expensive-trust-checks. + * keylist.c (list_keyblock): Act on this option. + +2001-01-04 Werner Koch <wk@gnupg.org> + + * g10.c (main): Set homedir only in the pre-parsing phase and + replace backslashes in the W32 version. + +2001-01-03 Werner Koch <wk@gnupg.org> + + * status.c, status.h : New status KEY_CREATED + * keygen.c (do_generate_keypair,generate_subkeypair): Emit it. + +2000-12-28 Werner Koch <wk@gnupg.org> + + * signal.c (got_fatal_signal): Remove lockfiles here because the + atexit stuff does not work due to the use of raise. Suggested by + Peter Fales. + * gpgv.c (remove_lockfiles): New stub. + +2000-12-19 Werner Koch <wk@gnupg.org> + + * status.c, status.h (cpr_get_no_help): New. + * keyedit.c (keyedit_menu): Use it here because we have our own + help list here. + +2000-12-18 Werner Koch <wk@gnupg.org> + + * mainproc.c (print_failed_pkenc): Don't print the sometimes + confusing message about unavailabe secret key. Renamed ... + (print_pkenc_list): ... to this and introduced failed arg. + (proc_encrypted): Print the failed encryption keys and then + the one to be used. + (proc_pubkey_enc): Store also the key we are going to use. + + * mainproc.c (check_sig_and_print): Don't list revoked user IDs. + (is_uid_revoked): New. + +2000-12-08 Werner Koch <wk@gnupg.org> + + * pipemode.c: Made the command work. Currently only for + non-armored detached signatures. + * mainproc.c (release_list): Reset the new pipemode vars. + (add_gpg_control): Handle the control packets for pipemode + * status.c, status.h: New stati {BEGIN,END}_STREAM. + +2000-12-07 Werner Koch <wk@gnupg.org> + + * g10.c: New option --allow-secret-key-import. + * import.c (import_keys,import_keys_stream): Honor this option. + (import): New arg allow_secret and pass that arg down to ... + (import_secret_one): to this and print a warning if secret key + importing is not allowed. + +2000-12-05 Werner Koch <wk@gnupg.org> + + * cipher.c (cipher_filter): Moved the end_encryption status ... + * encode.c (encode_simple,encode_crypt): to here + * sign.c (sign_file): and here. + + * status.c (mywrite): Removed. + (get_status_string): Removed the LFs from the strings. + (set_status_fd,is_status_enabed,write_status_text, + write_status_buffer): Replaced all mywrite by stdio calls and use + fdopen to create a strem. This is needed to make things smoother + in the W32 version. + +2000-12-04 Werner Koch <wk@gnupg.org> + + * import.c (merge_blocks): Increment n_sigs for revocations. + +2000-11-30 Werner Koch <wk@gnupg.org> + + * g10.c (main): Use iobuf_translate_file_handle for all options + with filehandles as arguments. This is function does some magic + for the W32 API. + + * verify.c (verify_signatures): Add a comment rant about the + detached signature problem. + * mainproc.c (proc_tree): Issue an error if a detached signature + is assumed but a standard one was found. + * plaintext.c (hash_datafiles): Don't fall back to read signature + from stdin. + * openfile.c (open_sigfile): Print verbose message only if the + file could be accessed. + +2000-11-24 Werner Koch <wk@gnupg.org> + + * passphrase.c [HAVE_DOSISH_SYSTEM]: Disabled all the agent stuff. + +2000-11-16 Werner Koch <wk@gnupg.org> + + * g10.c: New option --use-agent + * passphrase.c (agent_open,agent_close): New. + (agent_get_passphrase,agent_clear_passphrase): New. + (passphrase_clear_cache): New. + (passphrase_to_dek): Use the agent here. + * seckey-cert.c (do_check): Clear cached passphrases. + +2000-11-15 Werner Koch <wk@gnupg.org> + + * status.c (write_status_text): Moved the big switch to ... + (get_status_string): ... new function. + (write_status_buffer): New. + + * status.c (mywrite): New and replaced all write() by this. + + * status.c, status.h: Add 3 status lcodes for notaions and policy. + * mainproc.c (print_notation_data): Do status output of notations. + +2000-11-13 Werner Koch <wk@gnupg.org> + + * sign.c (clearsign_file): Use LF macro to print linefeed. + +2000-11-11 Paul Eggert <eggert@twinsun.com> + + Clean up the places in the code that incorrectly use "long" or + "unsigned long" for file offsets. The correct type to use is + "off_t". The difference is important on large-file hosts, + where "off_t" is longer than "long". + + * keydb.h (struct keyblock_pos_struct.offset): + Use off_t, not ulong, for file offsets. + * packet.h (dbg_search_packet, dbg_copy_some_packets, + search_packet, copy_some_packets): Likewise. + * parse-packet.c (parse, dbg_search_packet, search_packet, + dbg_copy_some_packets, copy_some_packets): Likewise. + * ringedit.c (keyring_search): Likewise. + + * parse-packet.c (parse): Do not use %lu to report file + offsets in error diagnostics; it's not portable. + * ringedit.c (keyring_search): Likewise. + +2000-11-09 Werner Koch <wk@gnupg.org> + + * g10.c (main): New option --enable-special-filenames. + +2000-11-07 Werner Koch <wk@gnupg.org> + + * g10.c (main): New command --pipemode. + * pipemode.c: New. + +2000-10-23 Werner Koch <wk@gnupg.org> + + * armor.c (armor_filter): Changed output of hdrlines, so that a CR + is emitted for DOS systems. + + * keygen.c (read_parameter_file): Add a cast for isspace(). + + * status.c (myread): Use SIGINT instead of SIGHUP for DOS. + +2000-10-19 Werner Koch <wk@gnupg.org> + + * g10.c: New option --ignore-crc-error + * armor.c (invalid_crc): New. + (radix64_read): Act on new option. + + * openfile.c (try_make_homedir): Klaus Singvogel fixed a stupid + error introduced on Sep 6th. + +2000-10-18 Werner Koch <wk@gnupg.org> + + * misc.c (print_cipher_algo_note): Don't print the note for AES. + Changed wording. + +2000-10-16 Werner Koch <wk@gnupg.org> + + * mainproc.c (do_proc_packets): Hack to fix the problem that + signatures are not detected when there is a MDC packet but no + compression packet. + + * g10.c (print_hashline): New. + (print_mds): Use above func with --with-colons. + + * mainproc.c (check_sig_and_print): Detect multiple signatures + and don't verify them. + +2000-10-14 Werner Koch <wk@gnupg.org> + + * mainproc.c (add_onepass_sig): There is an easier solution to the + error fixed yesterday; just check that we only have onepass + packets. However, the other solution provides an cleaner + interface and opens the path to get access to other information + from the armore headers. + (release_list): Reset some more variables. + +2000-10-13 Werner Koch <wk@gnupg.org> + + * mainproc.c (add_gpg_control): New. + (do_proc_packets): use it. + (proc_plaintext): Changed logic to detect clearsigns. + (proc_tree): Check the cleartext sig with some new code. + + * packet.h: New packet PKT_GPG_CONTROL. + * parse-packet.c (parse_gpg_control): New. + * misc.c (get_session_marker): New. + * armor.c (armor_filter): Replaced the faked 1-pass packet by the + new control packet. + + * keyedit.c (keyedit_menu): Allow batchmode with a command_fd. + * status.c (my_read): New. + (do_get_from_fd): use it. + +2000-10-12 Werner Koch <wk@gnupg.org> + + * keygen.c (keygen_add_std_prefs): Add Rijndael to the prefs. + +2000-10-07 Werner Koch <wk@gnupg.org> + + * gpgv.c: Add more stubs for ununsed code to make the binary smaller. + +Wed Oct 4 15:50:18 CEST 2000 Werner Koch <wk@openit.de> + + * sign.c (hash_for): New arg to take packet version in account, changed + call callers. + + * gpgv.c: New. + * Makefile.am: Rearranged source files so that gpgv can be build with + at least files as possible. + +Mon Sep 18 12:13:52 CEST 2000 Werner Koch <wk@openit.de> + + * hkp.c (not_implemented): Print a notice for W32 + +Fri Sep 15 18:40:36 CEST 2000 Werner Koch <wk@openit.de> + + * keygen.c (keygen_add_std_prefs): Changed order of preferences to + twofish, cast5, blowfish. + + * pkclist.c (algo_available): Removed hack to disable Twofish. + +Thu Sep 14 17:45:11 CEST 2000 Werner Koch <wk@openit.de> + + * parse-packet.c (dump_sig_subpkt): Dump key flags. Print special + warning in case of faked ARRs. + + * getkey.c (finsih_lookup): Hack so that for v4 RSA keys the subkey + is used for encryption. + +Thu Sep 14 14:20:38 CEST 2000 Werner Koch <wk@openit.de> + + * g10.c (main): Default S2K algorithms are now SHA1 and CAST5 - this + should solve a lot of compatibility problems with other OpenPGP + apps because those algorithms are SHOULD and not optional. The old + way to force it was by using the --openpgp option whith the drawback + that this would disable a couple of workarounds for PGP. + + * g10.c (main): Don't set --quite along with --no-tty. By Frank Tobin. + + * misc.c (disable_core_dump): Don't display a warning here but a return + a status value and ... + * g10.c (main): ...print warnining here. Suggested by Sam Roberts. + +Wed Sep 13 18:12:34 CEST 2000 Werner Koch <wk@openit.de> + + * keyedit.c (keyedit_menu): Allow to use "debug" on the secret key. + + * ringedit.c (cmp_seckey): Fix for v4 RSA keys. + * seckey-cert.c (do_check): Workaround for PGP 7 bug. + +Wed Sep 6 17:55:47 CEST 2000 Werner Koch <wk@openit.de> + + * misc.c (print_pubkey_algo_note): Do not print the RSA notice. + * sig-check.c (do_signature_check): Do not emit the RSA status message. + * pubkey-enc.c (get_session_key): Ditto. + + * encode.c (encode_simple, encode_crypt): Fix for large files. + * sign.c (sign_file): Ditto. + +Wed Sep 6 14:59:09 CEST 2000 Werner Koch <wk@openit.de> + + * passphrase.c (hash_passphrase): Removed funny assert. Reported by + David Mathog. + + * openfile.c (try_make_homedir): Changes for non-Posix systems. + * g10.c (main): Take the default homedir from macro. + + * g10.c: The --trusted-key option is back. + * trustdb.c (verify_own_key): Handle this option. + (add_ultimate_key): Moved stuff from verify_own_key to this new func. + (register_trusted_key): New. + +Fri Aug 25 16:05:38 CEST 2000 Werner Koch <wk@openit.de> + + * parse-packet.c (dump_sig_subpkt): Print info about the ARR. + + * openfile.c (overwrite_filep): Always return okay if the file is + called /dev/null. + (make_outfile_name): Add ".sign" to the list of know extensions. + (open_sigfile): Ditto. + +Wed Aug 23 19:52:51 CEST 2000 Werner Koch <wk@openit.de> + + * g10.c: New option --allow-freeform-uid. By Jeroen C. van Gelderen. + * keygen.c (ask_user_id): Implemented here. + +Fri Aug 4 14:23:05 CEST 2000 Werner Koch <wk@openit.de> + + * status.c (do_get_from_fd): Ooops, we used fd instead of opt.command_fd. + Thanks to Michael Tokarev. + +Tue Aug 1 20:06:23 CEST 2000 Werner Koch <wk@openit.de> + + * g10.c: New opttion --try-all-secrets on suggestion from Matthias Urlichs. + * pubkey-enc.c (get_session_key): Quite easy to implement here. + +Thu Jul 27 17:33:04 CEST 2000 Werner Koch <wk@openit.de> + + * g10.c: New option --merge-only. Suggested by Brendan O'Dea. + * import.c (import_one): Implemented it here + (import_secret_one): Ditto. + (print_stats): and give some stats. + +Thu Jul 27 12:01:00 CEST 2000 Werner Koch <wk@openit.de> + + * g10.c: New options --show-session-key and --override-session-key + * pubkey-enc.c (hextobyte): New. + (get_override_session_key): New. + * mainproc.c (proc_pubkey_enc): Add session-key stuff. + * status.h, status.c (STATUS_SESSION_KEY): New. + +Thu Jul 27 10:02:38 CEST 2000 Werner Koch <wk@openit.de> + + * g10.c (main): Use setmode(O_BINARY) for MSDOS while generating random bytes + (print_mds): Likewise for stdin. + * plaintext.c (handle_plaintext): Likewise for stdout. + +Mon Jul 24 10:30:17 CEST 2000 Werner Koch <wk@openit.de> + + * keyedit.c (menu_expire): expire date for primary key can be set again. + +Wed Jul 19 11:26:43 CEST 2000 Werner Koch <wk@openit.de> + + * keylist.c (is_uid_valid): New. + (list_keyblock): Print validity information for all user IDs. Note, this + has to be done at other places too; for now we have only minimal support. + +Wed Jul 12 13:32:06 CEST 2000 Werner Koch <wk@openit.de> + + * helptext.c, pkclist.c: s/superseeded/superseded/ + +Mon Jul 10 16:08:57 CEST 2000 Werner Koch <wk@openit.de> + + * parse-packet.c (enum_sig_subpkt): Fixed testing on crtitical bit in case + of a NULL buffer. Reported by Peter Marschall. + +Wed Jul 5 13:28:45 CEST 2000 Werner Koch <wk@openit.de> + + * keyedit.c, keyid.c: Add some _() + + * argparse.c: Changed the flag to suppress --version handling to also + suppress --help. + +Wed Jun 28 11:54:44 CEST 2000 Werner Koch <wk@openit.de> + + * armor.c (armor_filter): Set sigclass to 0 in case of non-dash-escaped + clearsig. This makes this mode work again. + + * mainproc.c (proc_tree): Fixed handling of one-pass-sig packets in textmode. + Disabled the ugly workaround for PGP 5 - let's see whether thi breaks less + cases. Found by Ted Cabeen. + + * options.h (DBG_HASHING): New. All commented md_start_debug are now + controlled by this debug option. + + * sign.c (print_status_sig_created): New and called from 2 places. + + * keygen.c (gen_rsa): New, but commented. + (ask_algo): Commented support for RSA. + + * seckey-cert.c (protect_secret_key): Started to fix the code for v4 RSA + keys - it is not solved yet. However, we have time until, Sep 20th ;) + +Wed Jun 14 12:27:09 CEST 2000 Werner Koch <wk@openit.de> + + * status.c (init_shm_coprocessing): Changed the sequence of the get,attach + to cope with the changes in newer Linux kernels. This bug has been found + by <dmitri@advantrix.com> who also proposed this solution. Hopefully + this does not break gpg on to many systems. + + * cipher.c (write_header): Protect the IV with the MDC too. + * encr-data.c (decrypt_data): Likewise. + +Fri Jun 9 10:09:52 CEST 2000 Werner Koch <wk@openit.de> + + * g10.c: New options --no-auto-key-retrieve + * options.h (auto_key_retrieve): New. + * mainproc.c (check_sig_and_print): Implemented that. + +Wed Jun 7 19:19:09 CEST 2000 Werner Koch <wk@openit.de> + + * sig-check.c (do_check): Use EMULATE_MDENCODE also on v4 packets. + +Wed Jun 7 17:25:38 CEST 2000 Werner Koch <wk@openit.de> + + * cipher.c (write_header): Use plain CFB mode for MDC encrypted packets. + * encr-data.c (decrypt_data): Ditto. + +Mon Jun 5 23:41:54 CEST 2000 Werner Koch <wk@openit.de> + + * seskey.c (do_encode_md, encode_md_value): Add new arg v3compathack to work + around a bug in old versions. + * sig-check.c (do_check): use the aboved workaround when enabled. + * g10.c: New option --emulate-md-decode-bug + +Mon Jun 5 12:37:43 CEST 2000 Werner Koch <wk@openit.de> + + * build-packet.c (do_mdc): New. + (do_encrypted_mdc): Changed for the new proposal. + * parse-packet.c (parse_mdc): New. + (parse_encrypted): Fixed for the new proposal. + * packet.h (PKT_MDC): New. + * cipher.c (cipher_filter): Build the MDC packet here. + * g10.c (main): Enable --force-mdc. + * encr-data.c (mdc_decode_filter): Fixed for new MDC method + + * options.h(rfc2440): New. + * g10.c (main): Changed the selected values for --openpgp to not include + optional algorithms. + +Thu May 18 11:38:54 CEST 2000 Werner Koch <wk@openit.de> + + * keyedit.c (keyedit_menu): Add a keyword arg to the prompt. + + * status.c, status.h: Added 3 new status tokens. + * status.c (do_get_from_fd): New. + (cpr_enabled,cpr_get,cpr_get_hidden,cpr_kill_prompt, + cpr_get_answer_is_yes,cpr_get_answer_yes_no_quit): Modified to work + with the new function. + * g10.c: Add new option --command-fd. + + * status.c (progress_cb): New. + (set_status_fd): Register progress functions + +Fri May 12 14:01:20 CEST 2000 Werner Koch <wk@openit.de> + + * delkey.c (delete_key): Add 2 new status messages + * status.c, status.h (STATUS_DELETE_PROBLEM): New. + + Fixed years of copyright in all source files. + +Mon May 1 17:08:14 CEST 2000 Werner Koch <wk@openit.de> + + * trustdb.c (propagate_validity): Fixed the bug that only one uid + gets fully trusted even when all are signed by an ultimate key. + +Mon May 1 15:38:04 CEST 2000 Werner Koch <wk@openit.de> + + * getkey.c (key_byname): Always returned a defined context. Fixed + a segv for invalid user id specifications. Reported by Walter Koch. + + * getkey.c (get_user_id): I18ned "no user id" string. By Walter. + + * pkclist.c (do_show_revocation_reason): Typo fixes. + * helptext.c: Ditto. + + * armor.c (armor_filter): Fixed some CRLF issues. By Mike McEwan. + +Fri Apr 14 19:37:08 CEST 2000 Werner Koch <wk@openit.de> + + * pkclist.c (do_show_revocation_reason): New. + (show_revocation_reason): New and called at various places. + + * g10.c (main): Fixed small typo. + + * pkclist.c (do_we_trust): Act on always_trust but not for revoked + keys. Suggested by Chip Salzenberg. + + * g10.c: New option --lock-never. + + * ringedit.c (get_writable_keyblock_file): New. + * keygen.c (do_generate_keypair): Use this instead of the hardwired one. + + * keygen.c (ask_user_id): Check that the email address is in the + correct field. Suggested by Christian Kurz. + +Mon Apr 10 13:34:19 CEST 2000 Werner Koch <wk@openit.de> + + * keyedit.c (show_key_with_all_names): s/sbb/ssb/ + +Tue Mar 28 14:26:58 CEST 2000 Werner Koch <wk@openit.de> + + * trustdb.c (verify_own_keys): Do not print warning about unprotected + key when in quiet mode. + +Wed Mar 22 13:50:24 CET 2000 Werner Koch <wk@openit.de> + + * mainproc.c (print_userid): Do UTF8 conversion before printing. + * import.c (import_one): Ditto. + (import_secret_one): Ditto. + (delete_inv_parts): Ditto. + +Thu Mar 16 16:20:23 CET 2000 Werner Koch <wk@openit.de> + + * keylist.c (print_key_data): Handle a NULL pk gracefully. + + * getkey.c (merge_one_pk_and_selfsig): Fixed silly code for + getting the primary keys keyID but kept using the one from the + subkey. + * pubkey-enc.c (get_it): Print a note for expired subkeys. + + * getkey.c (has_expired): New. + (subkeys_expiretime): New. + (finish_lookup): Check for expired subkeys needed for encryption. + (merge_keys_and_selfsig): Fixed expiration date merging for subkeys. + + * keylist.c (list_keyblock): Print expiration time for "sub". + (list_one): Add missing merging for public keys. + * mainproc.c (list_node): Ditto. + +2000-03-14 13:49:38 Werner Koch (wk@habibti.openit.de) + + * keygen.c (keyedit_menu): Do not allow to use certain commands + while the secret key is selected. + +2000-03-09 12:53:09 Werner Koch (wk@habibti.openit.de) + + * keygen.c (ask_expire_interval): Movede parsig to ... + (parse_expire_string): ... this new function. And some new control + commands. + (proc_parameter_file): Add expire date parsing. + (do_generate_keypair): Allow the use of specified output files. + +2000-03-08 10:38:38 Werner Koch (wk@habibti.openit.de) + + * keygen.c (ask_algo): Removed is_v4 return value and the commented + code to create Elg keys in a v3 packet. Removed the rounding + of key sizes here. + (do_create): Likewise removed arg v4_packet. + (gen_elg): Likewise removed arg version. Now rounding keysizes here. + (gen_dsa): Rounding keysize now here. + (release_parameter_list): New + (get_parameter*): New. + (proc_parameter_file): New. + (read_parameter_file): New. + (generate_keypair): Splitted. Now uses read_parameter_file when in + batch mode. Additional argument to specify a parameter file. + (do_generate_keypair): Main bulk of above fucntion and uses the + parameter list. + (do_create): Don't print long notice in batch mode. + * g10.c (main): Allow batched key generation. + +Thu Mar 2 15:37:46 CET 2000 Werner Koch <wk@gnupg.de> + + * pubkey-enc.c (get_it): Print a note about unknown cipher algos. + + * g10.c (opts): Add a note to the help listing about the man page + and removed some options from the help listing. + + * keyedit.c (print_and_check_one_sig): Use a new function to truncate + the output of the user ID. Suggested by Jan-Benedict Glaw. + +Wed Feb 23 10:07:57 CET 2000 Werner Koch <wk@gnupg.de> + + * helptext.c: typo fix. + +Thu Feb 17 13:39:32 CET 2000 Werner Koch <wk@gnupg.de> + + * revoke.c: Removed a bunch of commented code. + + * packet.h (SIGSUBPKT_REVOC_REASON): New. + * build-packet.c (build_sig_subpkt): Support new sub packet. + * parse-packet.c (parse_one_sig_subpkt): Ditto. + (dump_sig_subpkt): Ditto. + * revoke.c (ask_revocation_reason): New. + (release_revocation_reason_info): New. + (revocation_reason_build_cb): New. + (gen_revoke): Ask for reason. + * main.h (struct revocation_reason_info): Add declaration. + * keyedit.c (menu_revsig): Add support for revocation reason. + (menu_revkey): Ditto. + (sign_uid_mk_attrib): Renamed to ... + (sign_mk_attrib): ... this, made static and add support for reasons. + +Tue Feb 15 08:48:13 CET 2000 Werner Koch <wk@gnupg.de> + + * build-packet.c (build_packet): Fixed fixing of old comment packets. + + * import.c (import_keys): Fixed importing from stdin when called with + nnames set to zero as it normally happens. + +Mon Feb 14 14:30:20 CET 2000 Werner Koch <wk@gnupg.de> + + * sig-check.c (check_key_signature2): Add new arg r_expired. + (do_signature_check): New arg to pass it down to ... + (do_check): New arg r-expire which is set when the signature + has expired. + * trustdb.c (check_sig_record): Set SIGF_EXPIRED flag and set + the expiretime to zero so that thi signature will not be checked + anymore. + +Fri Feb 11 17:44:40 CET 2000 Werner Koch <wk@gnupg.de> + + * g10.c (g10_exit): Update the random seed_file. + (main): Set the random seed file. New option --no-random-seed-file. + +Thu Feb 10 17:39:44 CET 2000 Werner Koch <wk@gnupg.de> + + * keyedit.c (menu_expire): Fixed segv due to unitialized sub_pk. + By Rémi. + +Thu Feb 10 11:39:41 CET 2000 Werner Koch <wk@gnupg.de> + + * keylist.c (list_keyblock): Don't print warnings in the middle of + regulat output lines. By Rémi. + + * sig-check.c: Include options.h + +Wed Feb 9 15:33:44 CET 2000 Werner Koch <wk@gnupg.de> + + * gpg.c: New option --ignore-time-conflict + * sig-check.c (do_check): Implemented this option. + * trustdb.c (check_trust): Ditto. + * sign.c (do_sign): Ditto. + * keygen.c (generate_subkeypair): Ditto. + + * encode.c (encode_simple): use iobuf_cancel after open failure. + Reported by Huy Le. + +Fri Jan 14 18:32:01 CET 2000 Werner Koch <wk@gnupg.de> + + * packet.h (STRING2KEY): Changed mode from byte to int. + * parse-packet.c (parse_key): Add the special GNU protection stuff + * build-packet.c (so_secret_key): Ditto. + * seckey-cert.c (do_check): Ditto. + * keyedit.c (change_passphrase): Ditto. + * export.c (export_secsubkeys): New. + (do_export_stream): Hack to export the primary key using mode 1001. + * g10.c: New command --export-secret-subkeys + +Thu Jan 13 19:31:58 CET 2000 Werner Koch <wk@gnupg.de> + + * armor.c (is_armored): Check for 1-pass-sig packets. Reported by + David Hallinan <hallinan@rtd.com>. + (armor_filter): Replaced one LF by the LF macro. Reported by + Wolfgang Redtenbacher. + +Wed Jan 5 11:51:17 CET 2000 Werner Koch <wk@gnupg.de> + + * g10.c (main): Reset new global flag opt.pgp2_workarounds + when --openpgp is used. + * mainproc.c (proc_plaintext): Do the PGP2,5 workarounds only + when the global flag is set. + (proc_tree): Ditto. + * textfilter.c (copy_clearsig_text): Ditto. + * armor.c (armor_filter): Ditto. + + * g10.c: New option --list-only + * mainproc.c (proc_tree): Don't do it if opt.list_only is active. + (proc_pubkey_enc): Implement option. + + * status.h, status.c ({BEGIN,END}_{EN,DE}CRYPTION): New. + * cipher.c (cipher_filter): New status outputs. + * mainproc.c (proc_encrypted): New status outputs. + +Fri Dec 31 14:08:15 CET 1999 Werner Koch <wk@gnupg.de> + + * armor.c (armor_filter): Made the "Comment:" header translatable. + + * hkp.c (hkp_import): Make sure that the program does not return + success when there is a connection problem. Reported by Phillip Jones. + +Sun Dec 19 15:22:26 CET 1999 Werner Koch <wk@gnupg.de> + + * armor.c (LF): Use this new macro at all places where a line LF + is needed. This way DOSish textfiles should be created when the + input data is also in dos mode. + * sign.c (LF): Ditto. + * textfilter.c (LF): Ditto. + (copy_clearsig_text): Disabled the forcing of CR,LF sequences + for DOS systems. + + * plaintext.c (handle_plaintext): Fixes for line endings on DOS. + and react on a LF in cleartext. + * armor.c (fake_packet): Restore the original line ending after + removing trailing spaces. + + * signal.c (got_fatal_signal): DOS fix. + +Thu Dec 16 10:07:58 CET 1999 Werner Koch <wk@gnupg.de> + + * mainproc.c (print_failed_pkenc): Fix for unknown algorithm. + Found by fygrave@epr0.org. + +Thu Dec 9 10:31:05 CET 1999 Werner Koch <wk@gnupg.de> + + * hkp.c: i18n the strings. + +Sat Dec 4 15:32:20 CET 1999 Werner Koch <wk@gnupg.de> + + * trustdb.c (verify_key): Shortcut for ultimately trusted keys. + +Sat Dec 4 12:30:28 CET 1999 Werner Koch <wk@gnupg.de> + + * pkclist.c (build_pk_list): Validate the trust using the namehash + if this one has been set by the key lookup. + + * g10.c: Add --delete-secret-key to the help page. + + * openfile.c (copy_options_file): Made static. + (try_make_homedir): New. + * ringedit.c (add_keyblock_resource): Use the try_make_hoemdir logic. + * tdbio.c (tdbio_set_dbname): Likewise. + + * keygen.c (generate_user_id): Use m_alloc_clear() here. We should + better use an allocation function specific to the user_id packet. + + * keygen.c (keygen_add_std_prefs): Changed symmetric preferences + to include Blowfish again. This is due to it's better speed compared + to CAST5. + + * g10.c (strusage): Print the home directory. + + * armor.c (armor_filter): Take action on the cancel control msg. + * filter.h (armor_filter_context_t): Add cancel flag. + +Mon Nov 29 21:52:11 CET 1999 Werner Koch <wk@gnupg.de> + + * g10.c: New option --fast-list-mode .. + * keylist.c (list_keyblock): .. and implemented. + * mainproc.c (list_node): Ditto. + + * import.c (mark_non_selfsigned_uids_valid): Fixed the case that there + is a uid without any packet following. + +Mon Nov 22 11:14:53 CET 1999 Werner Koch <wk@gnupg.de> + + * mainproc.c (proc_plaintext): Never enable the hash processing + when skip_verify is active. + + * armor.c (parse_header_line): Stop parsing on a WS line too. + Suggested by Aric Cyr. + + * tdbdump.c (HEXTOBIN): Changed the name of the argument, so that + traditional cpp don't mess up the macros. Suggested by Jos Backus. + + * mainproc.c (list_node): Print the PK algo in the --with-colon mode. + * keylist.c (list_keyblock): Ditto. + + * signal.c (got_fatal_signal): Found the reason why exit(8) did not + work - it is better to set the disposition back to default before + raising the signal. Print the notice on stderr always. + +Fri Nov 12 20:33:19 CET 1999 Werner Koch <wk@gnupg.de> + + * g10.c (make_username): Swapped the logic. + * keylist.c (public_key_list): Now takes a STRLIST as arg and moved + the creation ot this list to the caller, so that he can copy with + UTF-conversion of user IDs. Changed all callers. + (secret_key_list): Likewise. + + * getkey.c (get_user_id_string_native): New and ... + * encode.c (write_pubkey_enc_from_list): ... use it here. + + * pubring.asc: Updated. + + * packet.h (PKT_PHOTO_ID): New. + * parse-packet.c (parse_photo_id): New. + * build-packet.c (do_user_id: Handle photo IDs. + (build_packet): Change CTB for photo IDs + * free-packet.c (free_user_id): Release memory used for photo IDs + * sig-check.c (hash_uid_node): Handle photo IDs too. + * trustdb.c (print_uid_from_keyblock): Hash photo ID. + (make_uid_records): Ditto. + * getkey.c (find_by_name): Ditto. + * keyedit.c (show_prefs): Ditto. + * keylist.c (list_keyblock): Ditto. + +Thu Oct 28 16:08:20 CEST 1999 Werner Koch <wk@gnupg.de> + + * keygen.c (ask_expire_interval): Print a warning for systems + with a signed 32 time_t if the exiration time is beyoind 2038. + +Fri Oct 8 20:40:50 CEST 1999 Werner Koch <wk@gnupg.de> + + * ringedit.c (enum_keyblocks): The last fix way really stupid; + reverted and set rt to Unknown. + +Fri Oct 8 20:32:01 CEST 1999 Werner Koch <wk@gnupg.de> + + * ringedit.c (enum_keyblocks): Zero the entire kbpos out on open. + + * g10.c (oEntropyDLL): Removed option. + (main): Made the warning on development versions more verbose. + + * g10.c (oHonorHttpProxy): New option. + * hkp.c (hkp_ask_import,hkp_export): Implement this option. + * options.skel: Enable this option for new installations + +Mon Oct 4 21:23:04 CEST 1999 Werner Koch <wk@gnupg.de> + + * import.c (import_keys): Changed calling interface, adjusted caller. + (import): Moved printing of stats out ... + (print_stats): New. ... to here. + (import_keys_stream): Call stats print here. + (import_keys): Print stats as totals for all files. + + * tdbio.h (DIRF_NEWKEYS): New + * tdbio.c (tdbio_dump_record): Print the new flag. + * trustdb.c (check_trust_record): New arg sigs_only. Adapted all + callers. + (do_update_trust_record): Removed recheck arg and add a new sigs_only + do we can later improve on the performance. Changed all callers too. + (check_trustdb): Evalutate the new flag and add a status output. + Do a check when the dir record has not been checked. + (build_cert_tree): Evaluate the new flag. + (check_trust): Ditto. Do a trust_record check, when the dir record + is not marked as checked. + (mark_fresh_keys): New. + (clear_lid_table): New. + (sync_trustdb): New. + * import.c (import_keys): Call sync_trustdb() after processing. + (import_keys_stream): Ditto. + * tdbdump.c (import_ownertrust): Ditto. + + * import.c (import_revoke_cert): Notify the trust DB. + (do_update_trust_record): Use |= to set the REVOKED bit and not &=; + shame on me for this bad copy+paste introduced bug. + (do_we_trust): Add trustmask to allow revoked key override to work. + Chnaged are to allow return of a mofified trustlevel. Adapted the + one caller. + + * g10.c: New options --emulate-3des-s2k-bug + * passphrase.c (hash_passphrase): Implemented above. + + * mainproc.c (proc_tree): Check for standalone signatures. + (do_check_sig): Print a notice for a standalone revocation + (check_sig_and_print): Do not print an error for unchecked standalone + revocations. + +Tue Sep 28 20:54:37 CEST 1999 Werner Koch <wk@gnupg.de> + + * encode.c (encode_simple): Use new CTB when we don't have the + length of the file. This is somewhat strange as the comment above + indicates that this part is actually fixed for PGP 5 - maybe I simply + lost the source line, tsss. + + * armor.c (armor_filter): Set a flag if no OpenPGP data has been found. + * verify.c (verify_signatures): Add an error helptext. + +Thu Sep 23 19:24:30 CEST 1999 Werner Koch <wk@isil.d.shuttle.de> + + * openfile.c (open_outfile): Fixed the 8dot3 handling. + + * passphrase.c (passphrase_to_dek): Print uid using utf8 func. + * delkey.c (delete_key): Ditto. + * pkclist.c (show_paths,do_edit_ownertrust,do_we_trust): Ditto + (do_we_trust_pre): Ditto. + * trustdb.c (print_user_id,check_uidsigs): Ditto. + * revoke.c (gen_revoke,ask_revoke_sig): Ditto. + +Thu Sep 23 09:52:58 CEST 1999 Werner Koch <wk@isil.d.shuttle.de> + + * verify.c (print_file_status): New. + (verify_one_file): Moved status print to th new fnc. Add error status. + * status.c, status.h (STATUS_FILE_ERROR): New + +Wed Sep 22 10:14:17 CEST 1999 Werner Koch <wk@isil.d.shuttle.de> + + * openfile.c (make_outfile_name): Use case-insenstive compare for + DOS systems. Add ".pgp" to the list of know extensions. + (open_outfile): For DOS systems try to replace the suffiy instead of + appending it. + + * status.c, status.h: Add STATUS_FILE_{START,DONE}. + * verify.c (verify_one_file): Emit these new stati. + + * sign.c (clearsign_file): Avoid duplicated Entries in the "Hash:" + line. Those headers are now only _not_ printed when there are + only old-style keys _and_ all hashs are MD5. + +Mon Sep 20 12:24:41 CEST 1999 Werner Koch <wk@isil.d.shuttle.de> + + + * verify.c (verify_files, ferify_one_file): New. + * g10.c: New command --verify-files + +Fri Sep 17 12:56:42 CEST 1999 Werner Koch <wk@isil.d.shuttle.de> + + * g10.c: Add UK spelling as alias for armor options ;-) + + * import.c (append_uid): Fixed a SEGV when there is no selfsig and + no subkey. + (merge_sigs): Ditto. Removed the assertion. + +Wed Sep 15 16:22:17 CEST 1999 Werner Koch <wk@isil.d.shuttle.de> + + * g10.c: New option --entropy-dll-name + +Mon Sep 13 10:51:29 CEST 1999 Werner Koch <wk@isil.d.shuttle.de> + + * signal.c (got_fatal_signal): Print message using write(2) and + only for development versions. + +Mon Sep 6 19:59:08 CEST 1999 Werner Koch <wk@isil.d.shuttle.de> + + * tdbio.c (tdbio_set_dbname): Use mkdir macro + * ringedit.c (add_keyblock_resource): Ditto. + +Fri Sep 3 10:04:45 CEST 1999 Werner Koch <wk@isil.d.shuttle.de> + + * pkclist.c (build_pk_list): Skip keys set with --encrypt-to also + when asking for a key. + + * plaintext.c (handle_plaintext): Make sure that we don't read a + second EOF in the read loop for partial length packets. + + * mainproc.c (check_sig_and_print): print user ID as utf-8. + +Thu Sep 2 16:40:55 CEST 1999 Werner Koch <wk@isil.d.shuttle.de> + + * import.c (merge_blocks): First add new subkeys, then merge subkey + certificates. + (merge_sigs): Don't merge subkey signatures here. + +Wed Sep 1 15:30:44 CEST 1999 Werner Koch <wk@isil.d.shuttle.de> + + * keygen.c (ask_expire_interval): Fixed bug related to cpr_xx (tnx + Francis J. Lacoste). + +Tue Aug 31 17:20:44 CEST 1999 Werner Koch <wk@isil.d.shuttle.de> + + * plaintext.c (do_hash): Hash CR,LF for a single CR. + (ask_for_detached_datafile): Changed arguments to be closer to + those of hash_datafiles and cleanup the code a bit. + * mainproc.c (proc_tree): Workaround for pgp5 textmode detached + signatures. Changed behavior of asking for data file to be the same + as with provided data files. + + * keylist.c (list_keyblock): Use UTF8 print functions. + +Mon Aug 30 20:38:33 CEST 1999 Werner Koch <wk@isil.d.shuttle.de> + + * import.c (chk_self_sigs): some s/log_error/log_info/ so that gpg + does not return an error if a key has some invalid packets. + + * helptext.c: Fixed some typos and changed the way the + translation works. The english text is now the keyword for gettext + and not anymore the keyword supplied to the function. Done after + some discussion with Walter who thinks this is much easier for the + translators. + + * misc.c (disable_core_dumps): Don't do it for DOSish systems. + + * signal.c (signal_name): Bounds check on signum. + +Wed Aug 4 10:34:18 CEST 1999 Werner Koch <wk@isil.d.shuttle.de> + + * pubring.asc: Updated. + + * pkclist.c (do_we_trust_pre,check_signatures_trust): Do not print + the warning about --always_trust when --quiet is used. + + * pkclist.c (fpr_info): New and called at several places. + + * parse-packet.c (dump_sig_subpkt): List revocation key contents. + +Mon Jul 26 09:34:46 CEST 1999 Werner Koch <wk@isil.d.shuttle.de> + + * pkclist.c (build_pk_list): Fixed typo in format string. + + * trustdb.c (create_shadow_dir): Don't translate the error string. + + * g10.c (main): Fixed spelling of user-id. + * getkey.c (find_by_name_pk,find_by_name_sk, + find_by_keyid,find_by_keyid_sk): Ditto and translate it. + * import.c (mark_non_selfsigned_uids_valid,delete_inv_parts): Ditto. + + +Mon Jul 26 01:01:39 CEST 1999 Michael Roth <mroth@nessie.de> + + * g10.c, options.h: New options --no-literal and --set-filesize + + * encode.c (encode_simple, encode_crypt): Support for the options + --no-literal and --set-filesize. + + * sign.c (sign_file): ditto. + +Fri Jul 23 13:53:03 CEST 1999 Werner Koch <wk@isil.d.shuttle.de> + + + * ringedit.c (enum_keyblocks): Removed annoying error message in cases + when we have no keyring at all to enum. + + * getkey.c (classify_user_id): Rewrote to relax the recognition of + keyIDs and fingerprints (Michael). + + * mainproc.c (check_sig_and_print): Print status NO_PUBKEY. + (print_failed_pkenc): Print status NO_SECKEY. + + * import.c (mark_non_selfsigned_uids_valid): New. + * g10.c: New option --allow-non-selfsigned-uid. + + * pkclist.c (print_fpr): New. + (do_we_trust_pre): Print the fpr before asking whether to use the key + anyway. + (do_edit_ownertrust): Likewise. + +Thu Jul 22 20:03:03 CEST 1999 Werner Koch <wk@isil.d.shuttle.de> + + + * ringedit.c (enum_keyblocks): Removed annoying error message in cases + when we have no keyring at all to enum. + + * getkey.c (classify_user_id): Rewrote to relax the recognition of + keyIDs and fingerprints (Michael). + + * mainproc.c (check_sig_and_print): Print status NO_PUBKEY. + (print_failed_pkenc): Print status NO_SECKEY. + + * import.c (mark_non_selfsigned_uids_valid): New. + * g10.c: New option --allow-non-selfsigned-uid. + +Thu Jul 15 10:15:35 CEST 1999 Werner Koch <wk@isil.d.shuttle.de> + + * g10.c: New options --disable-{cipher,pubkey}-algo. + +Wed Jul 14 19:42:08 CEST 1999 Werner Koch <wk@isil.d.shuttle.de> + + * status.h (STATUS_IMPORTED): New. + * import.c (import): Print some status information (Holger Schurig). + + * g10.c (main): Make --no-greeting work again. Add a warning when + --force-mds is used. + +Tue Jul 13 17:39:25 CEST 1999 Werner Koch <wk@isil.d.shuttle.de> + + * pkclist.c (do_edit_ownertrust): Changed the way help works. + (build_pk_list): Implemented default recipient stuff. + * g10.c: New options --default-recipient[-self] + (main): Suppress greeting in most cases, entering a passphrase or + a missing value is not considered to be interactive use. + Merged --print-md and --print-mds; the latter is now obsolete. + Changed the way --gen-random works and documented it. + Changed the way --gen-prime works and add a man entry. + * g10.c (MAINTAINER_OPTIONS): Removed. + +Mon Jul 12 18:45:57 CEST 1999 Werner Koch <wk@isil.d.shuttle.de> + + * keyedit.c (keyedit_menu): Add arg sign_mode and changed callers + * g10.c (main): New command --lsign-key. + +Mon Jul 12 14:55:34 CEST 1999 Werner Koch <wk@isil.d.shuttle.de> + + * mainproc.c (kidlist_item): New. + (release_list): Release failed pk-enc-list. + (print_failed_pkenc): New + (proc_encrypted): Print info about failed PK enc. + + * openfile.c (make_outfile_name): s/error/info/ + + * passphrase.c (passphrase_to_dek): Return an empty passphrase when + in batch mode and don't make the warning message fatal + * seckey-cert.c (check_secret_key): Try only once when in batch mode. + + * g10.c (make_username): New. + +Thu Jul 8 16:21:27 CEST 1999 Werner Koch <wk@isil.d.shuttle.de> + + + * packet.h (PKT_ring_trust): New + * parse-packet.c (parse_trust): Store trust value + * build-packet (build_packet): Ignore ring trust packets. + * mainproc.c (add_ring_trust): New. + (list_node): Print "rtv" records. + * g10.c: New option --with-fingerprint. + + * trustdb.c (verify_own_keys): Don't insert if we are dry running + (check_trust): Ditto. + +Wed Jul 7 13:08:40 CEST 1999 Werner Koch <wk@isil.d.shuttle.de> + + * Makefile.am: Support for libtool. + + * keygen.c (ask_expire_interval): Hack to allow for an expire date. + + * trustdb.c (do_update_trust_record,update_trust_record): Splitted. + (check_trust_record): New. + (check_trust,build_cert_tree): Check the dir record as needed. + (upd_pref_record): Removed. + (make_pref_record): New. + (propagate_validity): Stop as soon as we have enough validity. + + * tbdio.c (MAX_CACHE_ENTRIES_HARD): Increased the limit. + + +Fri Jul 2 11:45:54 CEST 1999 Werner Koch <wk@isil.d.shuttle.de> + + * g10.c (g10_exit): Dump random stats. + + * sig-check.c (check_key_signature,check_key_signature2): Enhanced + version and wrapper for old function. + (do_signature_check,signature_check): Ditto. + +Thu Jul 1 12:47:31 CEST 1999 Werner Koch <wk@isil.d.shuttle.de> + + + * keyedit.c (show_key_with_all_names): Print a notice for disabled keys. + (enable_disable_keys): Add functionality + * pkclist.c (edit_ownertrust): preserve disabled state. + (build_pk_list): Skip disabled keys. + * trustdb.c (upd_one_ownertrust): Ditto. + (build_cert_tree): Mask the ownertrust. + (trust_letter): Mask the value. + (do_check): Take disabled flag into account. + + * passphrase.c (passphrase_to_dek): Add a pubkey_algo arg and changed + all callers. + + * g10.c (utf8_strings): 2 new options. + + * trustdb.c (insert_trust_record_by_pk): New, replaces the next one. + (insert_trust_record): Now takes a keyblock as arg. Changed all + callers to use the appropritae function. + + * openfile.c (ask_outfile_name): New. + * plaintext.c (handle_plaintext): Ask for filename if there is + no valid syntax. Don't use fname varbatim but filter it. + +Tue Jun 29 21:44:25 CEST 1999 Werner Koch <wk@isil.d.shuttle.de> + + + * trustdb.h (TRUST_FLAG_DISABLED): New. + + * status.c (USE_CAPABILITIES): Capabilities support (Remi). + + * tdbio.c : Added new fields to the DIR record. + (tdbio_write_record): Fixed the update of the hash tables. + (tdbio_delete_record): Drop the record from the hash tables. + (drop_from_hashtbl): New. + + * status.c (cpr_get): Special online help mode. + * helptext.c ("keyedit.cmd"): Removed. + * keyedit.c (keyedit_menu): Use only help system. + (enable_disable_key): New bit doies not yet work. + +Sat Jun 26 12:15:59 CEST 1999 Werner Koch <wk@isil.d.shuttle.de> + + + * dearmor.c (enarmor_file): Fixed comment string. + * tdbdump.c (export_ownertrust): Text fix. + * tbio.c (tdbio_invalid): Ditto. + + * parse-packet.c (parse_key): Made temp buffer larger. + + * Makefile.am (install-data-local): Add missing backslashes + +Tue Jun 15 12:21:08 CEST 1999 Werner Koch <wk@isil.d.shuttle.de> + + * g10.c (main): Made iterated+salted the default S2K method. + + * Makefile.am (install-data-local): Use DESTDIR. + + * passphrase.c (passphrase_to_dek): Emit missing-passphrase while in + batchmode. + + * parse-packet.c (parse_pubkeyenc): Fixed a SEGV. + +Mon Jun 14 21:18:54 CEST 1999 Michael Roth <mroth@nessie.de> + + * g10.c: New options --openpgp, --no-tty, --emit-version, + --default-comment and --lock-multiple + +Thu Jun 10 14:18:23 CEST 1999 Werner Koch <wk@isil.d.shuttle.de> + + * free-packet.c (free_encrypted): Fixed EOF case (Remi). + (free_plaintext): Ditto. + + * helptext.c (keyedit.delsig.unknown): New (Remi). + * keyedit.c (print_and_check_one_sig): Add arg print_without_key and + changed all callers to make use of it (Remi): + +Tue Jun 8 13:36:25 CEST 1999 Werner Koch <wk@isil.d.shuttle.de> + + * keylist.c (print_key_data): New and called elsewhere. + * g10.c: New option --with-key-data + +Wed Jun 2 14:17:19 CEST 1999 Werner Koch <wk@isil.d.shuttle.de> + + * mainproc.c (proc_tree): Yet another bad hack to cope with + broken pgp2 created detached messages in textmode. + +Tue Jun 1 16:01:46 CEST 1999 Werner Koch <wk@isil.d.shuttle.de> + + * openfile.c (make_outfile_name): New. + * plaintext.c (handle_plaintext): Outputfile is now the inputfile + without the suffix. + * g10.c: New option --use-embedded-filename + +Mon May 31 19:41:10 CEST 1999 Werner Koch <wk@isil.d.shuttle.de> + + * g10.c (main): Fix for SHM init (Michael). + + * compress.c, encr-data.c, mdfilter.c, + plaintext.c, free-packet.c: Speed patches (Rémi). + +Thu May 27 09:40:55 CEST 1999 Werner Koch <wk@isil.d.shuttle.de> + + * status.c (cpr_get_answer_yes_no_quit): New. + * keyedit.c (menu_delsig): New. + (check_all_keysigs): Splitted. + (print_and_check_one_sig): New. + +Wed May 26 14:36:29 CEST 1999 Werner Koch <wk@isil.d.shuttle.de> + + * build-packet.c (build_sig_subpkt): Support large packets. + * parse-packet.c (enum_sig_subpkt): Replaces parse_sig_subpkt. + * mainproc.c (print_notation_data): Print all notation packets. + * g10.c (add_notation_data): Add a way to specify the critical flag. + (main): Add option --set-policy-url. + (check_policy_url): Basic checks. + * sign.c (mk_notation_and_policy): Replaces mk_notation. + + * parse-packet.c (can_handle_critical): Moved decision whether we can + handle critical subpacket to an extra function. + +Tue May 25 19:50:32 CEST 1999 Werner Koch <wk@isil.d.shuttle.de> + + * sign.c (sign_file): Always use compression algo 1 for signed + onyl file becuase we can´ be sure the the verifier supports other + algorithms. + + * build-packet.c (build_sig_subpkt): Support for notation data. + * sign.c (sign_file,clearsign_file,make_keysig_packet): Ditto. + (mk_notation): New. + * g10.c (add_notation_data): New and add option -N + * mainproc.c (print_notation_data): New. + (check_sig_and_print): Print any notation data of the signed text. + +Sun May 23 14:20:22 CEST 1999 Werner Koch <wk@isil.d.shuttle.de> + + * pkclist.c (check_signatures_trust): Print a warning and return + immediateley if opt.always_trust is true. + + * g10.c (main): Corrected handling of no-default-keyring + + * pkclist.c (algo_available): Disable Twofish until we have settled + how to do the MDC. + + * hkp.c: Disable everything for mingw32 + +Sat May 22 22:47:26 CEST 1999 Werner Koch <wk@isil.d.shuttle.de> + + * mainproc.c (check_sig_and_print): Add sig creation time to the + VALIDSIG status output. Add more info to the ERRSIG output. + * sig-check.c (signature_check): Add sig time after epoch to SIG_ID. + + * import.c (import_one): Merge duplicate user IDs. + (collapse_uids): New. + * kbnode.c (move_kbnode): New. + (remove_kbnode): New. + * keyedit.c (keyedit_menu): Call collapse_uids. + + * g10.c: new option --logger-fd. + + * import.c: s/log_*_f/log_*/ + +Thu May 20 14:04:08 CEST 1999 Werner Koch <wk@isil.d.shuttle.de> + + * misc.c (pull_in_libs): do the volatile only for gcc + + * sig-check (signature_check): Emit SIG_iD only for classes 0 and 1. + + * armor.c (armor_filter): Add detection of PGP2 created clearsigs. + (fake_packet): A tab is not a WS for pgp2 - handle this. + * textfilter.c (len_without_trailing_chars): New. + (copy_clearsig_text): Add pgp2mode arg. + * sign.c (clearsign_file): pass old_style to the above fnc. + + +Wed May 19 16:04:30 CEST 1999 Werner Koch <wk@isil.d.shuttle.de> + + * g10.c: New option --interactive. + + * mainproc.c (proc_plaintext): Add workaround for pgp2 bug + (do_check_sig): Ditto. + (proc_tree): Ditto. + * plaintext.c (do_hash): Ditto. + (hash_datafiles): Ditto, add an arg, changed all callers. + * mdfilter.c (md_filter): Add support for the alternate hash context. + +Mon May 17 21:54:43 CEST 1999 Werner Koch <wk@isil.d.shuttle.de> + + * parse-packet.c (parse_encrypted): Support for PKT_ENCRYPTED_MDC. + * build-packet.c (do_encrypted_mdc): Ditto. + * cipher.c (write_header): Add mdc hashing. + (cipher_filter): write out the hash. + * mainproc.c (do_proc_packets): Add PKT_ENCRYPTED_MDC. + * encr-data.c (decrypt_data): Add mdc hashing. + (mdc_decode_filter): New. + + * parse-packet.c (parse_sig_subpkt): Fixed stupid bug for subpkt + length calculation + (parse_signature): Fixed even more stupid bug. + +Sat May 8 19:28:08 CEST 1999 Werner Koch <wk@isil.d.shuttle.de> + + * build-packet.c (do_signature): Removed MDC hack. + * encode.c (encode_crypt_mdc): Removed. + * mainproc.c (do_check_sig): Removed MDC hack. + (check_sig_and_print): Ditto. + * parse-packet.c (parse_signature): Ditto. + * sig-check.c (mdc_kludge_check): Ditto. + * free-packte.c (copy_signature, free_seckey_enc): Ditto. + + * parse-packet.c (parse_signature,parse_key): Store data of + unknown algorithms with mpi_set_opaque inseatd of the old + faked data stuff. + (read_rest): Removed. + (read_rest2): Renamed to read_rest + * build-packet.c (write_fake_data): Use mpi_get_opaque. + * free-packet.c (cp_fake_data): Removed and cahnged all callers + to use mpi_copy. + (free_pubkey_enc,free_seckey_enc,release_public_key_parts, + release_secret_key_parts): Use mpi_free for opaque data. + +Thu May 6 14:18:17 CEST 1999 Werner Koch <wk@isil.d.shuttle.de> + + * trustdb.c (check_trust): Check for revoked subkeys. + * pkclist.c (do_we_trust): Handled revoked subkeys. + (do_we_trust_pre): Ditto. + (check_signatures_trust): Ditto. + + * build-packet.c (hash_public_key): Fix for ancient g10 keys. + + * mainproc.c (do_proc_packets): Return EOF if no data has been read. + * g10.c (main): Catch errors for default operation. + +Thu Apr 29 12:29:22 CEST 1999 Werner Koch <wk@isil.d.shuttle.de> + + * sign.c (sign_file): Fixed hashing in case of no subpackets. + (clearsign_file): Ditto. + (make_keysig_packet): Ditto. + +Wed Apr 28 13:03:03 CEST 1999 Werner Koch <wk@isil.d.shuttle.de> + + * keyedit.c (keyedit_menu): Add new command revkey. + * (menu_revkey): New. + + +Mon Apr 26 17:48:15 CEST 1999 Werner Koch <wk@isil.d.shuttle.de> + + * parse-packet.c (parse_signature): Add the MDC hack. + * build-packet.c (do_signature): Ditto. + * free-packet.c (free_seckey_enc,copy_signature,cmp_signatures): Ditto. + * mainproc.c (do_check_sig): Ditto. + * sig-check.c (mdc_kludge_check): New. + * encode.c (encrypt_mdc_file): New. + + * keyedit.c (check_all_keysigs): List revocations. + * (menu_revsig): New. + * sign (make_keysig_packet): Support for class 0x30. + +Sun Apr 18 20:48:15 CEST 1999 Werner Koch <wk@isil.d.shuttle.de> + + * pkclist.c (select_algo_from_prefs): Fixed the case that one key + has no preferences (Remi Guyomarch). + + keylist.c (list_keyblock): ulti_hack to propagate trust to all uids. + +Sun Apr 18 10:11:28 CEST 1999 Werner Koch <wk@isil.d.shuttle.de> + + * seckey-cert.c (do_check): Use real IV instead of a 0 one, so that + it works even if the length of the IV doesn't match the blocksize. + Removed the save_iv stuff. + (protect_secret_key): Likewise. Create the IV here. + * packet.h (PKT_secret_key): Increased size of IV field and add a + ivlen field. + * parse-packet.c (parse_key): Use the len protect.ivlen. + * build-packet.c (do_secret_key). Ditto. + + * getkey.c (key_byname): Close keyblocks. + + * Makefile.am (gpgm): Removed this + * g10.c: Merged gpg and gpgm + + * import.c (import): Utilize option quiet. + * tdbio.c (tdbio_set_dbname): Ditto. + * ringedit.c (add_keyblock_resource,keyring_copy): Ditto. + + * keyedit.c (sign_uids): Add some batch support. + + * g10.c (main): add call to tty_batchmode. + +Fri Apr 9 12:26:25 CEST 1999 Werner Koch <wk@isil.d.shuttle.de> + + * status.c (write_status_text): Some more status codes. + * passphrase_to_dek (passphrase_to_dek): add a status code. + * seckey_cert.c (check_secret_key): Likewise. + + * encr-data.c (decrypt_data): Reverse the last changes + * cipher.c (write_header): Ditto. + + * parse-packet.c (parse_key): Dropped kludge for ancient blowfish mode. + +Thu Apr 8 09:35:53 CEST 1999 Werner Koch <wk@isil.d.shuttle.de> + + * mainproc.c (proc_encrypted): Add a new status output + * passphrase.c (passphrase_to_dek): Ditto. + * status.h status.c: Add new status tokens. + +Wed Apr 7 20:51:39 CEST 1999 Werner Koch <wk@isil.d.shuttle.de> + + * encr-data.c (decrypt_data): Fixes for 128 bit blocksize + * cipher.c (write_header): Ditto. + * seckey-cert.c (do_check): Ditto. + (protect_secret_key). Ditto. + * misc.c (print_cipher_algo_note): Twofish is now a standard algo. + + * keygen.c (do_create): Fixed spelling (Gaël Quéri) + (ask_keysize): Only allow keysizes up to 4096 + + * ringedit.c (add_keyblock_resource): chmod newly created secrings. + + * import.c (delete_inv_parts): Fixed accidently deleted subkeys. + +Tue Apr 6 19:58:12 CEST 1999 Werner Koch <wk@isil.d.shuttle.de> + + * armor.c: Removed duped include (John Bley) + * mainproc.c: Ditto. + + * build-packet.c (hash_public_key): Fixed hashing of the header. + + * import.c (delete_inv_parts): Allow import of own non-exportable sigs. + +Sat Mar 20 13:59:47 CET 1999 Werner Koch <wk@isil.d.shuttle.de> + + * armor.c (fake_packet): Fix for not not-dash-escaped + +Sat Mar 20 11:44:21 CET 1999 Werner Koch <wk@isil.d.shuttle.de> + + * g10.c (main): Added command --recv-keys + * hkp.c (hkp_import): New. + +Wed Mar 17 13:09:03 CET 1999 Werner Koch <wk@isil.d.shuttle.de> + + * trustdb.c (check_trust): add new arg add_fnc and changed all callers. + (do_check): Ditto. + (verify_key): Ditto. + (propagate_validity): Use the new add_fnc arg. + (print_user_id): Add the FILE arg. + (propagate_ownertrust): New. + * pkclist.c (add_ownertrust_cb): New and changed the add_ownertrust + logic. + + * getkey.c (get_keyblock_bylid): New. + * trustdb.c (print_uid_from_keyblock): New. + (dump_tn_tree_with_colons): New. + (list_trust_path): Add colon print mode. + + * trustdb.c (insert_trust_record): Always use the primary key. + + * encode.c (encode_simple): Added text_mode filter (Rémi Guyomarch) + (encode_crypt): Ditto. + + * mainproc.c (proc_pubkey_enc): Added status ENC_TO. + * armor.c (armor_filter): Added status NODATA. + * passphrase.c (passphrase_to_dek): Always print NEED_PASSPHRASE + * seckey_cert.c (check_secret_key): Added BAD_PASS status. + + * g10.c (main): Set g10_opt_homedir. + +Sun Mar 14 19:34:36 CET 1999 Werner Koch <wk@isil.d.shuttle.de> + + * keygen.c (do_create): Changed wording of the note (Hugh Daniel) + +Thu Mar 11 16:39:46 CET 1999 Werner Koch <wk@isil.d.shuttle.de> + + * tdbdump.c: New + + * trustdb.c (walk_sigrecs,do_list_sigs,list_sigs, + list_records,list_trustdb,export_ownertrust,import_ownertrust): Moved + to tdbdump.c + (init_trustdb): renamed to setup_trustdb. Changed all callers. + (do_init_trustdb): renamed to init_trustdb(). + * trustdb.c (die_invalid_db): replaced by tdbio_invalid. + * tdbio.c (tdbio_invalid): New. + + * import.c (delete_inv_parts): Skip non exportable signatures. + * keyedit.c (sign_uid_mk_attrib): New. + (sign_uids): Add the local argument. + (keyedit_menu): New "lsign" command. + * trustdb.c (register_trusted_key): Removed this and all related stuff. + * g10.c (oTrustedKey): Removed option. + + * tdbio.h (dir.valcheck): New trustdb field. + * tdbio.c: Add support for this field + (tdbio_read_modify_stamp): New. + (tdbio_write_modify_stamp): New. + * trustdb.c (do_check): Check against this field. Removed cache update. + (verify_key): Add cache update. + (upd_uid_record): Some functional changes. + (upd_cert_record): Ditto + +Wed Mar 10 11:26:18 CET 1999 Werner Koch <wk@isil.d.shuttle.de> + + * keylist.c (list_keyblock): Fixed segv in uid. Print 'u' as + validity of sks. + +Mon Mar 8 20:47:17 CET 1999 Werner Koch <wk@isil.d.shuttle.de> + + * getkey.c (classify_user_id): Add new mode 12 (#<lid>). + + * seckey-cert.c (check_secret_key): replaced error by info. + + * trustdb.c (query_trust_info): Add another arg, changed all callers. + (check_trust): Ditto. + (do_check): Ditto. + (verify_key): Handle namehash. + * keylist.c (list_keyblock): print trust info for user ids. + + * sig-check.c (signature_check): Add sig-created to status output. + +Tue Mar 2 16:44:57 CET 1999 Werner Koch <wk@isil.d.shuttle.de> + + * textfilter.c (copy_clearsig_text): New. + (clearsign): Removed. + * sign.c (clearsign_file): does not use textfiler anymore. + + * keygen.c (ask_user_id): print a note about the used charset. + +Tue Mar 2 10:38:42 CET 1999 Werner Koch <wk@isil.d.shuttle.de> + + * sig-check.c (signature_check): sig-id now works for all algos. + + * armor.c (armor_filter): Fixed armor bypassing. + +Sun Feb 28 19:11:00 CET 1999 Werner Koch <wk@isil.d.shuttle.de> + + * keygen.c (ask_user_id): Don't change the case of email addresses. + (has_invalid_email_chars): Adjusted. + + * keylist.c (list_one): Really list serect keys (Remi Guyomarch) + + * keyedit.c (menu_select_uid): Add some braces to make egcs happy. + (menu_select_key): Ditto. + + * mainproc.c (do_proc_packets): List sym-enc packets (Remi Guyomarch) + +Fri Feb 26 17:55:41 CET 1999 Werner Koch <wk@isil.d.shuttle.de> + + * pkclist.c (build_pk_list): Return error if there are no recipients. + + * sig-check.c (signature_check): New signature id feature. + * armor.c (make_radic64_string): New. + + * mainproc.c (proc_pubkey_enc): early check for seckey availability. + + * pkclist.c (do_we_trust_pre): print user id before asking. + + * ringedit.c (add_keyblock_resource,get_keyblock_handle): Cleaner + handling of default resource. + + +Thu Feb 25 18:47:39 CET 1999 Werner Koch <wk@isil.d.shuttle.de> + + * pkclist.c (algo_available): New. + (select_algo_from_prefs): Check whether algo is available. + + * ringedit.c (keyring_copy): Take care of opt.dry_run. + (do_gdbm_store): Ditto. + * openfile.c (open_outfile). Ditto. + (copy_options_file): Ditto. + * trustdb.c (update_trustdb): Ditto. + (clear_trust_checked_flag): Ditto. + (update_trust_record): Ditto. + (insert_trust_record): Ditto. + +Wed Feb 24 11:07:27 CET 1999 Werner Koch <wk@isil.d.shuttle.de> + + * keylist.c (secret_key_list): Now really list the secret key. + + * trustdb.c (do_init_trustdb): New. Init is now deferred. + +Mon Feb 22 20:04:00 CET 1999 Werner Koch <wk@isil.d.shuttle.de> + + * getkey.c (lookup_sk): Return G10ERR_NO_SECKEY and not x_PUBKEY. + +Fri Feb 19 15:49:15 CET 1999 Werner Koch <wk@isil.d.shuttle.de> + + * pkclist.c (select_algo_from_prefs): retrieve LID if not there. + + * armor.c (fake_packet): Replaced ugly lineending handling. + + * g10.c (oNoEncryptTo): New. + * pkclist.c (build_pk_list): Implemented this option. + + * g10.c (main): Greeting is now printed to stderr and not to tty. + Use add_to_strlist() instead of direct coding. + + * import.c (import): Use iobuf_push_filter2. + + * mainproc.c (check_sig_and_print): Print all user ids + for good signatures. + * getkey.c (get_pubkeyblock): New. + + * import.c (chk_self_sigs): Fixed SEGV for unbounded class 0x18 keys. + (delete_inv_parts): Delete special marked packets. + +Tue Feb 16 14:10:02 CET 1999 Werner Koch <wk@isil.d.shuttle.de> + + * g10.c (main): New option --encrypt-to + + * pkclist.c (build_pk_list): Implemented encrypt-to. + + * parse-packet.c (parse_user_id): Removed the hack to work with + utf-8 strings. + + * g10.c (main): Install lockfile cleanup handler. + * tdbio.c (cleanup): Removed: this is now handled by dotlock. + +Sat Feb 13 14:13:04 CET 1999 Werner Koch <wk@isil.d.shuttle.de> + + * tdbio.c (tdbio_set_dbname): Init lockhandle for a new trustdb + +Wed Feb 10 17:15:39 CET 1999 Werner Koch <wk@isil.d.shuttle.de> + + * g10.c (main): check for development version now in configure + + * tdbio.c (tdbio_write_record): Add uid.validity + (tdbio_read_record) : Ditto. + (tdbio_dump_record) : Ditto. + + * keygen.c (keygen_add_std_prefs): Replaced Blowfish by Twofish, + removed MD5 and Tiger. + * pubkey-enc.c (get_it): Suppress warning about missing Blowfish + in preferences in certain cases. + + * ringedit.c (lock_rentry,unlock_rentry): New. + + * getkey.c (key_byname): Pass ret_kb down to lookup_xx. + + * armor.c (armor_filter): No output of of empty comment lines. + Add option --no-version to suppress the output of the version string. + + * getkey.c: Release the getkey context for auto context variables. + +Sun Jan 24 18:16:26 CET 1999 Werner Koch <wk@isil.d.shuttle.de> + + * getkey.c: Changed the internal design to allow simultaneous + lookup of multible user ids + (get_pubkey_bynames): New. + (get_seckey_bynames): New. + (get_seckey_next): New. + (get_seckey_end): New. + * keylist.c (list_one): Use the new functions. + + * keylist.c (list_keyblock): add a newline for normal listings. + + * g10.c (--recipient): New option name to replace --remote-user + + +Wed Jan 20 18:59:49 CET 1999 Werner Koch <wk@isil.d.shuttle.de> + + * textfilter.c: Mostly rewritten + * plaintext.c (handle_plaintext): Use now text_filter semantics. + +Tue Jan 19 19:34:58 CET 1999 Werner Koch <wk@isil.d.shuttle.de> + + * export.c (export_pubkeys_stream): New. + (do_export_stream): New. + * g10.c (aSendKeys): New command. + * hkp.c (hkp_export): New. + + * compress.c (do_uncompress): Hack for algo 1 and 1.1.3 + +Sun Jan 17 11:04:33 CET 1999 Werner Koch <wk@isil.d.shuttle.de> + + * textfilter.c (text_filter): Now uses iobuf_read_line(). + (read_line): Removed. + + * armor.c (trim_trailing_spaces): Removed and replaced + by trim_trailing_ws from libutil + +Sat Jan 16 12:03:27 CET 1999 Werner Koch <wk@isil.d.shuttle.de> + + * hkp.c (hkp_ask_import): Use only the short keyid + +Sat Jan 16 09:27:30 CET 1999 Werner Koch <wk@isil.d.shuttle.de> + + * import.c (import_key_stream): New + (import): New, moved most of import_keys here. + * g10.c: New option --keyserver + * mainproc.c (check_sig_and_print): Hook to import a pubkey. + + * pref.c pref.h : Removed + + * hkp.c hkp.h: New + +Wed Jan 13 14:10:15 CET 1999 Werner Koch <wk@isil.d.shuttle.de> + + * armor.c (radix64_read): Print an error if a bad armor was detected. + +Wed Jan 13 12:49:36 CET 1999 Werner Koch <wk@isil.d.shuttle.de> + + * armor.c (radix64_read): Now handles malformed armors produced + by some buggy MUAs. + +Tue Jan 12 11:17:18 CET 1999 Werner Koch <wk@isil.d.shuttle.de> + + * ringedit.c (find_keyblock_bysk): New. + + * skc_list.c (is_insecure): New. + (build_sk_list): usage check for insecure keys. + + * import.c (chk_self_sigs): Add handling for subkeys. + (delete_inv_parts): Skip unsigned subkeys + + * sig-check.c (do_check): Print info if the signature is older + than the key. + * keygen.c (generate_subkeypair): Fail on time warp. + * sign.c (do_sign): Ditto. + +Sun Jan 10 15:10:02 CET 1999 Werner Koch <wk@isil.d.shuttle.de> + + * armor.c (fake_packet): Fixed not-dash-escaped bug. + +Sat Jan 9 16:02:23 CET 1999 Werner Koch <wk@isil.d.shuttle.de> + + * sig-check.c (do_check): Output time diff on error + + * status.c (STATUS_VALIDSIG): New. + (is_status_enabled): New. + * mainproc.c (check_sig_and_print): Issue that status message. + + * plaintext.c (special_md_putc): Removed + + * armor.c (armor_filter): print error for truncated lines. + + * free-packet.c (free_encrypted): Revomed call to set_block_mode. + (free_plaintext): Ditto. + +Thu Jan 7 18:00:58 CET 1999 Werner Koch <wk@isil.d.shuttle.de> + + * pkclist.c (add_ownertrust): Fixed return value. + + * encr-data.c (decrypt_data): Disabled iobuf_set_limit and + iobuf_pop_filter stuff. + * compress.c (handle_compressed): Disabled iobuf_pop_filter. + + * packet.h (PKT_secret_key): Add is_primary flag. + * parse-packet.c (parse_key): Set this flag. + * passphrase.c (passphrase_to_dek): Kludge to print the primary + keyid - changed the API: keyid must now hold 2 keyids. + * getkey.c (get_primary_seckey): New. + * seckey-cert.c (do_check): pass primary keyid to passphrase query + + * tbdio.c (open_db): removed the atexit + (tdbio_set_dbname): and moved it to here. + + * armor.c: Rewrote large parts. + +Tue Dec 29 19:55:38 CET 1998 Werner Koch <wk@isil.d.shuttle.de> + + * revoke.c (gen_revoke): Removed compression. + + * pkclist.c (do_we_trust_pre): special check for revoked keys + + * trustdb.c (update_trust_record): Fixed revoke flag. + +Tue Dec 29 14:41:47 CET 1998 Werner Koch <wk@isil.d.shuttle.de> + + * misc.c (disable_core_dumps): Check for EINVAL (Atari) + + * getkey (merge_one_pk_and_selfsig): Fixed search of expiredate. + (merge_keys_and_selfsig): Ditto. + + * free-packet.c (cmp_public_keys): cmp expire only for v3 packets + (cmp_secret_keys): Ditto. + (cmp_public_secret_key): Ditto. + +Wed Dec 23 17:12:24 CET 1998 Werner Koch <wk@isil.d.shuttle.de> + + * armor.c (find_header): Reset not_dashed at every header + +Wed Dec 23 13:18:14 CET 1998 Werner Koch <wk@isil.d.shuttle.de> + + * pkclist.c (add_ownertrust): Refresh validity values. + + * trustdb.c (enum_cert_paths_print): New arg refresh. + + * ringedit.c: Fixed problems fix keyrings + * parse-packet.c (dbg_parse_packet): New debug functions. + + * getkey.c (getkey_disable_caches): New. + * import.c (import_keys): Disable caches. + +Thu Dec 17 18:31:15 CET 1998 Werner Koch <wk@isil.d.shuttle.de> + + * misc.c (trap_unaligned): Only for glibc 1 + + * sign.c (write_dash_escaped): Now escapes "From " lines + * g10.c: New option --escape-from-lines + + * trustdb.c (sort_tsl_list): New + (list_trust_path): Now prints sorted list. + (enum_cert_paths): Likewise. + (enum_cert_paths_print): New. + (print_paths): New printing format. + * pkclist.c (add_ownertrust): New arg quit. + (edit_ownertrust): New quit selection and does not query + the recipients ownertrust anymore. + (add_ownertrust): Print the ceritficate path. + + +Mon Dec 14 21:18:49 CET 1998 Werner Koch <wk@isil.d.shuttle.de> + + * parse-packet.c (parse_signature): Now checks for critical bit + (parse_sig_subpkt): Splitted. + (parse_one_sig_subpkt): New. + * sig-check.c (do_check): handle critical bit. + +Sun Dec 13 14:10:56 CET 1998 Werner Koch <wk@isil.d.shuttle.de> + + * pcklist.c (select_algo_from_prefs): Preferences should + now work (lost the != ? ) + +Thu Dec 10 20:15:36 CET 1998 Werner Koch <wk@isil.d.shuttle.de> + + * ringedit.c (gdbm_store): Fix for inserts + + * g10.c (main): New option --export-all + * export.c (export_pubkeys): New arg. + (do_export): Now may skip old keys. + + * status.c: Minor patches for Sun's cc + + * keygen.c (ask_algo): Disabled v3 ElGamal choice, rearranged + the numbers. Add a warning question when a sign+encrypt key + is selected. + + * g10.c (do_not_use_RSA): Removed. + * misc.c (print_pubkey_algo_note): New as replacement for the + do_not_use_RSA() and chnaged all callers. + (print_cipher_algo_note): New. + (print_hash_algo_note): New. + + * cipher.c (write_header): Add a call to print_cipher_algo_note. + * seckey-cert.c (protect_secret_key): Ditto + * sign.c (do_sign): Add a call to print_digest_algo_note. + + * getkey.c (get_long_user_id_string): New. + * mainproc.c (check_sig_and_print): Changed the format of the + status output. + + * encrypt.c (write_pubkey_enc_from_list): print used symmetric cipher. + + * pkclist.c (do_we_trust): Changed a message. + +Wed Dec 9 13:41:06 CET 1998 Werner Koch <wk@isil.d.shuttle.de> + + * misc.c (trap_unaligned) [ALPHA]: Only if UAC_SIGBUS is defined. + + * sign.c (write_dash_escaped): Add the forgotten patch by Brian Moore. + + * compress.c (do_uncompress): Fixed the inflating bug. + + +Tue Dec 8 13:15:16 CET 1998 Werner Koch <wk@isil.d.shuttle.de> + + * trustdb.c (upd_uid_record): Now uses the newest self-signature + (insert_trust_record): Now calls update with recheck set to true. + (register_trusted_key): New. + (verify_own_keys): Enhanced by list of trusted keys. + + * g10.c (main): Print a warning when a devel version is used. + (main): New option --trusted-key + + * import.c (merge_blocks): Fixed merging of new user ids and + added merging of subkeys. + (append_uid): Ditto. + (merge_keysig): New. + (append_key): New. + * getkey.c (merge_one_pk_and_selfsig): Get the expiration time + from the newest self-signature. + (merge_keys_and_selfsig): Ditto. + + * free-packet.c (cmp_secret_key): New. + + +Fri Nov 27 21:37:41 CET 1998 Werner Koch <wk@isil.d.shuttle.de> + + * g10.c: New option --lock-once + * tdbio.c (open_db): Add an atexit + (cleanup): New. + (tdbio_sync): Add locking. + (tdbio_end_transaction): Ditto. + (put_record_into_cache): Ditto. + * ringedit.c (keyring_copy): Ditto. + (cleanup): New. + (add_keyblock_resource): Add an atexit. + +Fri Nov 27 15:30:24 CET 1998 Werner Koch <wk@isil.d.shuttle.de> + + * armor.c (find_header): Another fix for clearsigs. + +Fri Nov 27 12:39:29 CET 1998 Werner Koch <wk@isil.d.shuttle.de> + + + * status.c (display_help): Removed. + * helptext.c: New and removed the N_() from all cpr_gets. + + +Fri Nov 20 16:54:52 1998 Werner Koch (wk@isil.d.shuttle.de) + + * g10.c (main): New option --not-dash-escaped + * sign.c (write_dashed_escaped): Ditto. + * armor.c (find_header): Support for NotDashEscaped header. + + * getkey.c: print "disabled cache.." only if verbose is used. + +Thu Nov 19 07:17:31 1998 Werner Koch <werner.koch@guug.de> + + * parse-packet.c (dump_sig_subpkt): Fixed expire listing + * getkey.c (merge_keys_and_selfsig): Fixed expire calculation. + (merge_one_pk_and_selfsig): Ditto. + * keyedit.c (menu_expire). Ditto. + * keygen.c (keygen_add_key_expire): Ditto. + (ask_expire_interval): New and changed all local function to use + this instead. + (keygen_add_key_expire): Opaque should now be a public key; + changed all callers. + + * parse.packet.c (parse): use skip_rest to skip packets. + + * keyedit.c (keyedit_menu): New arg for cmdline cmds. + +Wed Nov 18 20:33:50 1998 Werner Koch (wk@isil.d.shuttle.de) + + * trustdb.c (check_trustdb): Now rechecks all gived userids. + (collect_paths): Some fixes. + (upd_pref_records): Skips empty items, evaluate all items. + + * parse-packet.c (dump_sig_subpkt): Better listing of prefs. + (skip_packet): Now knows about marker packet + + * g10.c: removed cmd "--edit-sig". + + * pubring.asc: Updated. + +Sat Nov 14 14:01:29 1998 Werner Koch (wk@isil.d.shuttle.de) + + * g10.c (main): Changed syntax of --list-trust-path + * trustdb.c (list_trust_path): Replaced max_depth by + opt.max_cert_depth + +Fri Nov 13 07:39:58 1998 Werner Koch <werner.koch@guug.de> + + * trustdb.c (collect_paths): Removed a warning message. + (enum_trust_web): Removed. + (enum_cert_paths): New. + * pkclist.c (add_ownertrust): Changed to use enum_cert_paths. + (edit_ownertrust): Now list ceritficates on request. + (show_paths): New. + +Wed Nov 11 18:05:44 1998 Werner Koch <werner.koch@guug.de> + + * g10.c (main): New option --max-cert-depth + * tdbio.h: add new fields to ver and dir record. + * tdbio.c: read/write/dump of these fields. + (tdbio_db_matches_options): New. + * trustdb.c: replaced MAC_CERT_DEPTH by opt.max_cert_depth. + (do_check): cache validity and changed other functions + to reset the cached value. + + * keylist.c (list_one): Now lists the ownertrust. + * mainproc.c (list_node): Ditto. + +Tue Nov 10 10:08:59 1998 Werner Koch (wk@isil.d.shuttle.de) + + * g10.c (g10_exit): Now looks at the new g10_errors_seen. + * mainproc.c (check_sig_and_print): Sets g10_errors_seen. + + * *.c : i18n many more strings. + + * ringedit.c (locate_keyblock_by_keyid): Add HAVE_LIBGDBM + (locate_keyblock_by_fpr): Ditto. + + * g10.c (main): removed unsused "int errors". + (main): Add new option --charset. + + * g10.c (main): special message for the unix newbie. + +Mon Nov 9 07:17:42 1998 Werner Koch <werner.koch@guug.de> + + * getkey.c (finish_lookup): Kludge to prefere algo 16. + + * trustdb.c (new_lid_table): Clear cached item. + + * status.c (cpr_get_utf8): New. + * pkclist.c (build_pk_list): Uses this. + +Sun Nov 8 17:20:39 1998 Werner Koch (wk@isil.d.shuttle.de) + + * mainproc.c (check_sig_and_print): Why did I use strlen()-1 + in the printf? - This truncated the TZ. + +Sat Nov 7 15:57:28 1998 me,,, (wk@tobold) + + * getkey.c (lookup): Changes to support a read_next. + (get_pubkey): Fixed a memory leak. + + * keylist.c (list_one): Now lists all matching user IDs. + +Tue Nov 3 16:19:21 1998 Werner Koch (wk@isil.d.shuttle.de) + + * keygen.c (ask_user_id): Now converted to UTF-8 + + * g10.c (main): Kludge for pgp clearsigs and textmode. + +Fri Oct 30 16:40:39 1998 me,,, (wk@tobold) + + * signal.c (block_all_signals): New. + (unblock_all_signals): New + * tdbio.c (tdbio_end_transaction): Now blocks all signals. + + * trustdb.c (new_lid_table): Changed the representation of the + former local_lid_info stuff. + + * trustdb.c (update_trust_record): Reorganized the whole thing. + * sig-check.c (check_key_signature): Now handles class 0x28 + + +Wed Oct 28 18:56:33 1998 me,,, (wk@tobold) + + * export.c (do_export): Takes care of the exportable sig flag. + +Tue Oct 27 14:53:04 1998 Werner Koch (wk@isil.d.shuttle.de) + + * trustdb.c (update_trust_record): New "fast" parameter. + +Sun Oct 25 19:32:05 1998 Werner Koch (wk@isil.d.shuttle.de) + + * openfile.c (copy_options_File): New. + * ringedit.c (add_keyblock_resource): Creates options file + * tdbio.c (tdbio_set_dbname): Ditto. + +Sat Oct 24 14:10:53 1998 brian moore <bem@cmc.net> + + * mainproc.c (proc_pubkey_enc): Don't release the DEK + (do_proc_packets): Ditto. + +Fri Oct 23 06:49:38 1998 me,,, (wk@tobold) + + * keyedit.c (keyedit_menu): Comments are now allowed + + * trustdb.c: Rewrote large parts. + + +Thu Oct 22 15:56:45 1998 Michael Roth (mroth@nessie.de) + + * encode.c: (encode_simple): Only the plain filename without + a given directory is stored in generated packets. + (encode_crypt): Ditto. + + * sign.c: (sign_file) Ditto. + + +Thu Oct 22 10:53:41 1998 Werner Koch (wk@isil.d.shuttle.de) + + * trustdb.c (update_trust_record): Add new optional arg. + + * import.c (import_keys): Add statistics output + * trustdb.c (update_trustdb): Ditto. + (insert_trustdb): Ditto. + + * tdbio.c (tdbio_begin_transaction): New. + (tdbio_end_transaction): New. + (tdbio_cancel_transaction): New. + + * g10.c (main): New option --quit. + + * trustdb.c (check_hint_sig): No tests for user-id w/o sig. + This caused an assert while checking the sigs. + + * trustdb.c (upd_sig_record): Splitted into several functions. + + * import.c (import_keys): New arg "fast". + * g10.c (main): New command --fast-import. + +Wed Oct 21 18:19:36 1998 Michael Roth <mroth@nessie.de> + + * ringedit.c (add_keyblock_resource): Directory is now created. + * tdbio.c (tdbio_set_dbname): New info message. + +Wed Oct 21 11:52:04 1998 Werner Koch (wk@isil.d.shuttle.de) + + * trustdb.c (update_trustdb): released keyblock in loop. + + * keylist.c (list_block): New. + (list_all): Changed to use list_block. + + * trustdb.c: Completed support for GDBM + + * sign.c (only_old_style): Changed the way force_v3 is handled + (sign_file): Ditto. + (clearsign_file): Ditto. + + * keygen.c (has_invalid_email_chars): Splitted into mailbox and + host part. + + * keylist.c (list_one): Add a merge_keys_and_selfsig. + * mainproc.c (proc_tree): Ditto. + +Sun Oct 18 11:49:03 1998 Werner Koch (wk@isil.d.shuttle.de) + + * sign.c (only_old_style): Add option force_v3_sigs + (sign_file): Fixed a bug in sig->version + (clearsign_file): Ditto. + + * parse-packet.c (dump_sig_subpkt): New + + * keyedit.c (menu_expire): New. + * free-packet.c (cmp_signatures): New + + +Sat Oct 17 10:22:39 1998 Werner Koch (wk@isil.d.shuttle.de) + + * armor.c: changed output line length from 72 to 64. + + * keyedit.c (fix_keyblock): New. + +Fri Oct 16 10:24:47 1998 Werner Koch (wk@isil.d.shuttle.de) + + * trustdb.c: Rewrote most. + * tdbio.c: Add cache and generalized hash tables. + + * options.h (ENABLE_COMMENT_PACKETS): New but undef'ed. + * encode.c, sign.c, keygen.c: Disabled comment packets. + * export.c (do_export): Comment packets are never exported, + except for those in the secret keyring. + + * g10.c (main): Removed option do-no-export-rsa; should be + be replaced by a secpial tool. + * export.c (do_export): Removed the code for the above option. + + * armor.c (find_header): Support for new only_keyblocks. + * import.c (import_keys): Only looks for keyblock armors. + + * packet.h: replaced valid_days by expiredate and changed all users. + * build-packet.c (do_public_key): calculates valid-days + (do_secret_key): Ditto. + * parse-packet.c (parse_key): expiredate is calucated from the + valid_period in v3 packets. + * keyid.c (do_fingerprint_md): calculates valid_dates. + + * keygen.c (add_key_expire): fixed key expiration time for v4 packets. + + * armor.c (find_header): A LF in the first 28 bytes + was skipped for non-armored data. + +Thu Oct 8 11:35:51 1998 Werner Koch (wk@isil.d.shuttle.de) + + * armor.c (is_armored): Add test on old comment packets. + + * tdbio.c (tdbio_search_dir_bypk): fixed memory leak. + + * getkey.c: Changed the caching algorithms. + +Wed Oct 7 19:33:28 1998 Werner Koch (wk@isil.d.shuttle.de) + + * kbnodes.c (unused_nodes): New. + +Wed Oct 7 11:15:36 1998 Werner Koch (wk@isil.d.shuttle.de) + + * keyedit.c (sign_uids): Fixed a problem with SK which could caused + a save of an unprotected key. + (menu_adduid): Ditto. + + * keyedit.c (keyedit_menu): Prefs are now correctly listed for + new user ids. + + * trustdb.c (update_trust_record): New. + (insert_trust_record): Now makes use of update_trust_record. + +Tue Oct 6 16:18:03 1998 Werner Koch (wk@isil.d.shuttle.de) + + * trustdb.c (read_record): replaces most of the tdbio_read_records. + (write_record): Ditto. + +Sat Oct 3 11:01:21 1998 Werner Koch (wk@isil.d.shuttle.de) + + * keygen.c (ask_alogo): enable ElGamal enc-only only for addmode. + +Wed Sep 30 10:15:33 1998 Werner Koch (wk@isil.d.shuttle.de) + + * import.c (import_one): Fixed update of wrong keyblock. + +Tue Sep 29 08:32:08 1998 me,,, (wk@tobold) + + * mainproc.c (proc_plaintext): Display note for special filename. + * plaintext.c (handle_plaintext): Suppress output of special file. + +Mon Sep 28 12:57:12 1998 Werner Koch (wk@isil.d.shuttle.de) + + * g10.c (verify_own_keys): Add warning if a key is not protected. + + * passphrase (hash_passphrase): Fixed iterated+salted mode and + setup for keysizes > hashsize. + + * g10.c (main): New options: --s2k-{cipher,digest,mode}. + +Fri Sep 25 09:34:23 1998 Werner Koch (wk@isil.d.shuttle.de) + + * g10.c: Chnaged some help texts. + +Tue Sep 22 19:34:39 1998 Werner Koch (wk@isil.d.shuttle.de) + + * passphrase.c (read_passphrase_from_fd): fixed bug for long + passphrases. + +Mon Sep 21 11:28:05 1998 Werner Koch (wk@(none)) + + * getkey.c (lookup): Add code to use the sub key if the primary one + does not match the usage. + + * armor.c (armor_filter): New error message: no valid data found. + (radix64_read): Changes to support multiple messages. + (i18n.h): New. + * mainproc.c (add_onepass_sig): bug fix. + +Mon Sep 21 08:03:16 1998 Werner Koch (wk@isil.d.shuttle.de) + + * pkclist.c (do_we_trust): Add keyid to most messages. + + * passphrase.c (read_passphrase_from_fd): New. + (have_static_passphrase): New + (get_passphrase_fd): Removed. + (set_passphrase_fd): Removed. + * g10.c (main): passphrase is now read here. + + * keyedit.c (keyedit_menu): "help" texts should now translate fine. + +Mon Sep 21 06:40:02 1998 Werner Koch (wk@isil.d.shuttle.de) + + * encode.c (encode_simple): Now disables compression + when --rfc1991 is used. + (encode_crypt): Ditto. + +Fri Sep 18 16:50:32 1998 Werner Koch (wk@isil.d.shuttle.de) + + * getkey.c (merge_key_and_selfsig): New. + +Fri Sep 18 10:20:11 1998 Werner Koch (wk@isil.d.shuttle.de) + + * pkclist.c (select_algo_from_prefs): Removed 3DES kludge. + + * seskey.c (make_session_key): Fixed SERIOUS bug introduced + by adding the weak key detection code. + + * sign.c (sign_file): Changed aremor header in certain cases. + +Tue Sep 15 17:52:55 1998 Werner Koch (wk@isil.d.shuttle.de) + + * mainproc.c (check_sig_and_print): Replaced ascime by asctimestamp. + +Mon Sep 14 11:40:52 1998 Werner Koch (wk@isil.d.shuttle.de) + + * seskey.c (make_session_key): Now detects weak keys. + + * trustdb (clear_trust_checked_flag): New. + + * plaintext.c (handle_plaintext): Does no anymore suppress CR from + cleartext signed messages. + +Sun Sep 13 12:54:29 1998 Werner Koch (wk@isil.d.shuttle.de) + + * trustdb.c (insert_trust_record): Fixed a stupid bug in the free + liunked list loops. + +Sat Sep 12 15:49:16 1998 Werner Koch (wk@isil.d.shuttle.de) + + * status.c (remove_shmid): New. + (init_shm_comprocess): Now sets permission to the real uid. + +Wed Sep 9 11:15:03 1998 Werner Koch (wk@isil.d.shuttle.de) + + * packet.h (PKT_pubkey_enc): New flah throw_keyid, and add logic to + implement it. + * g10.c (main): New Option --throw-keyid + + * getkey.c (enum_secret_keys): Add new ar and changed all callers. + +Tue Sep 8 20:04:09 1998 Werner Koch (wk@isil.d.shuttle.de) + + * delkey.c (delete_key): Moved from keyedit.c. + +Mon Sep 7 16:37:52 1998 Werner Koch (wk@isil.d.shuttle.de) + + * build-packet.c (calc_length_header): New arg new_ctb to correctly + calculate the length of new style packets. + + * armor.c (is_armored): Checks for symkey_enc packets. + + * pkclist.c (select_algo_from_prefs): 3DEs substitute is now CAST5. + +Tue Aug 11 17:54:50 1998 Werner Koch (wk@isil.d.shuttle.de) + + * build-packet.c (do_secret_key): Fixed handling of old keys. + + * getkey.c (compare_name): Fixed exact and email matching + + * openfile.c (open_outfile): Changed arguments and all callers. + +Tue Aug 11 09:14:35 1998 Werner Koch (wk@isil.d.shuttle.de) + + * encode.c (encode_simple): Applied option set-filename and comment. + (encode_crypt): Ditto. + * sign.c (sign_file): Ditto. + * armor.c (armor_filter): Applied option comment. + + * encode.c (encode_crypt): Moved init_packet to the begin. + (encode_simple): add an init_packet(). + + * comment (write_comment): Now enforces a hash sign as the 1st byte. + + * import.c (import_one): Add explanation for "no user ids". + + * compress.c (do_uncompress): Applied Brian Warner's patch to support + zlib 1.1.3 etc. + + * trustdb.c (check_trust): Fixed a problem after inserting new keys. + + * getkey (lookup): do not return the primary key if usage is given + (lookup_sk): Ditto and take usage into account. + + * status.c (cpr_get_answer_is_yes): add display_help. + +Mon Aug 10 10:11:28 1998 Werner Koch (wk@isil.d.shuttle.de) + + * getkey.c (lookup_sk): Now always returns the primary if arg + primary is true. + (lookup): Likewise. + (get_pubkey_byname): Now returns the primary key + (get_seckey_byname): Ditto. + + +Mon Aug 10 08:34:03 1998 Werner Koch (wk@isil.d.shuttle.de) + + * keyid.c (pubkey_letter): ELG_E is now a small g. + +Sat Aug 8 17:26:12 1998 Werner Koch (wk@isil.d.shuttle.de) + + * openfile (overwrite_filep): Changed semantics and all callers. + +Sat Aug 8 12:17:07 1998 Werner Koch (wk@isil.d.shuttle.de) + + * status.c (display_help): New. + +Thu Aug 6 16:30:41 1998 Werner Koch,mobil,,, (wk@tobold) + + * seskey.c (encode_session_key): Now uses get_random_bits(). + +Thu Aug 6 07:34:56 1998 Werner Koch,mobil,,, (wk@tobold) + + * ringedit.c (keyring_copy): No more backupfiles for + secret keyrings and add additional warning in case of + a failed secret keyring operation. + +Wed Aug 5 11:54:37 1998 Werner Koch (wk@isil.d.shuttle.de) + + * g10.c (check_opts): Moved to main. Changed def_cipher_algo + semantics and chnaged all users. + + * pubkey-enc.c (get_sssion_key): New informational output + about preferences. + + * parse-packet.c (parse_symkeyenc): Fixed salted+iterated S2K + (parse_key): Ditto. + * build-packet.c (do_secret_key): Ditto. + (do_symkey_enc): Ditto. + +Tue Aug 4 08:59:10 1998 Werner Koch (wk@isil.d.shuttle.de) + + * getkey.c (enum_secret_keys): Now returns only primary keys. + + * getkey (lookup): Now sets the new namehash field. + + * parse-packet.c (parse_sig_subpkt2): New. + + * sign.c (sign_file): one-pass sigs are now emiited reverse. + Preference data is considered when selecting the compress algo. + +Wed Jul 29 12:53:03 1998 Werner Koch (wk@isil.d.shuttle.de) + + * free-packet.c (copy_signature): New. + + * keygen.c (generate_subkeypair): rewritten + * g10.c (aKeyadd): Removed option --add-key + +Mon Jul 27 10:37:28 1998 Werner Koch (wk@isil.d.shuttle.de) + + * seckey-cert.c (do_check): Additional check on cipher blocksize. + (protect_secret_key): Ditto. + * encr-data.c: Support for other blocksizes. + * cipher.c (write_header): Ditto. + +Fri Jul 24 16:47:59 1998 Werner Koch (wk@isil.d.shuttle.de) + + * kbnode.c (insert_kbnode): Changed semantics and all callers. + * keyedit.c : More or less a complete rewrite + +Wed Jul 22 17:10:04 1998 Werner Koch (wk@isil.d.shuttle.de) + + * build-packet.c (write_sign_packet_header): New. + +Tue Jul 21 14:37:09 1998 Werner Koch (wk@isil.d.shuttle.de) + + * import.c (import_one): Now creates a trustdb record. + + * g10.c (main): New command --check-trustdb + +Mon Jul 20 11:15:07 1998 Werner Koch (wk@isil.d.shuttle.de) + + * genkey.c (generate_keypair): Default key is now DSA with + encryption only ElGamal subkey. + +Thu Jul 16 10:58:33 1998 Werner Koch (wk@isil.d.shuttle.de) + + * keyid.c (keyid_from_fingerprint): New. + * getkey.c (get_pubkey_byfprint): New. + +Tue Jul 14 18:09:51 1998 Werner Koch (wk@isil.d.shuttle.de) + + * keyid.c (fingerprint_from_pk): Add argument and changed all callers. + (fingerprint_from_sk): Ditto. + +Tue Jul 14 10:10:03 1998 Werner Koch (wk@isil.d.shuttle.de) + + * plaintext.c (handle_plaintext): Now returns create error if + the file could not be created or the user responded not to overwrite + the file. + * mainproc.c (proc_plaintext): Tries again if the file could not + be created to check the signature without output. + + * misc.c (disable_core_dumps): New. + * g10.c (main): disable coredumps for gpg + + * g10.c (MAINTAINER_OPTIONS): New to disable some options + +Mon Jul 13 16:47:54 1998 Werner Koch (wk@isil.d.shuttle.de) + + * plaintext.c (hash_datafiles): New arg for better support of + detached sigs. Changed all callers. + * mainproc.c (proc_signature_packets): Ditto. + + * g10.c (main): New option "compress-sigs" + * sig.c (sign_file): detached signatures are not anymore compressed + unless the option --compress-sigs is used. + +Thu Jul 9 19:54:54 1998 Werner Koch (wk@isil.d.shuttle.de) + + * armor.c: Fixes to allow zero length cleartext signatures + +Thu Jul 9 14:52:47 1998 Werner Koch (wk@isil.d.shuttle.de) + + * g10.c (build_list): Now drops setuid. + (main): Changed the way keyrings and algorithms are registered . + +Wed Jul 8 14:17:30 1998 Werner Koch (wk@isil.d.shuttle.de) + + * packet.h (PKT_public_key): Add field keyid. + * parse-packet.c (parse_key): Reset the above field. + * keyid.c (keyid_from_pk): Use above field as cache. + + * tdbio.c, tdbio.h: New + * trustdb.c: Moved some functions to tdbio.c. + (print_keyid): New. + + * pkclist.c (check_signatures_trust): New. + +Wed Jul 8 10:45:28 1998 Werner Koch (wk@isil.d.shuttle.de) + + * plaintext.c (special_md_putc): New. + (handle_plaintext): add clearsig argument + * mainproc.c (proc_plaintext): detection of clearsig + * sign.c (write_dased_escaped): Changed clearsig format + +Tue Jul 7 18:56:19 1998 Werner Koch (wk@isil.d.shuttle.de) + + * armor.c (find_header): Now makes sure that there is only one + empty line for clearsigs, as this is what OP now says. + +Mon Jul 6 13:09:07 1998 Werner Koch (wk@isil.d.shuttle.de) + + * g10.c (main): New option default-secret-key + * getkey.c (get_seckey_byname): support for this option. + +Mon Jul 6 09:03:49 1998 Werner Koch (wk@isil.d.shuttle.de) + + * getkey.c (add_keyring): Keyrings are now added to end of the + list of keyrings. The first added keyringwill be created. + (add_secret_keyring): Likewise. + + * ringedit.c (add_keyblock_resource): Files are created here. + + * g10.c (aNOP): Removed + + * getkey.c (lookup): Add checking of usage for name lookups + * packet.h (pubkey_usage): Add a field which may be used to store + usage capabilities. + * pkclist.c (build_pk_list): getkey now called with usage arg. + * skclist.c (build_sk_list): Ditto. + + * sign.c (clearsign_file): Fixed "Hash:" headers + +Sat Jul 4 13:33:31 1998 Werner Koch (wk@isil.d.shuttle.de) + + * trustdb.c (list_ownertrust): New. + * g10.c (aListOwnerTrust): New. + + * g10.c (def_pubkey_algo): Removed. + + * trustdb.c (verify_private_data): Removed and also the call to it. + (sign_private_data): Removed. + +Fri Jul 3 13:26:10 1998 Werner Koch (wk@isil.d.shuttle.de) + + * g10.c (aEditKey): was aEditSig. Changed usage msg. + + * keyedit.c: Done some i18n stuff. + + * g10.c (do_not_use_RSA): New. + * sign.c (do_sign): Add call to above function. + * encode.c (write_pubkey_enc_from_list): Ditto. + +Thu Jul 2 21:01:25 1998 Werner Koch (wk@isil.d.shuttle.de) + + * parse-packet.c: Now is able sto store data of unknown + algorithms. + * free-packet.c: Support for this. + * build-packet.c: Can write data of packet with unknown algos. + +Thu Jul 2 11:46:36 1998 Werner Koch (wk@isil.d.shuttle.de) + + * parse-packet.c (parse): fixed 4 byte length header + +Wed Jul 1 12:36:55 1998 Werner Koch (wk@isil.d.shuttle.de) + + * packet.h (new_ctb): New field for some packets + * build-packet.c (build_packet): Support for new_ctb + * parse-packet.c (parse): Ditto. + +Mon Jun 29 12:54:45 1998 Werner Koch (wk@isil.d.shuttle.de) + + * packet.h: changed all "_cert" to "_key", "subcert" to "subkey". + + * free-packet.c (free_packet): Removed memory leak for subkeys. + +Sun Jun 28 18:32:27 1998 Werner Koch (wk@isil.d.shuttle.de) + + * import.c (import_keys): Renamed from import_pubkeys. + (import_secret_one): New. + + * g10.c (aExportSecret): New. + + * export.c (export_seckeys): New. + + * parse-packet.c (parse_certificate): Cleaned up. + (parse_packet): Trust packets are now considered as unknown. + (parse_pubkey_warning): New. + +Fri Jun 26 10:37:35 1998 Werner Koch (wk@isil.d.shuttle.de) + + * keygen.c (has_invalid_email_chars): New. + +Wed Jun 24 16:40:22 1998 Werner Koch (wk@isil.d.shuttle.de) + + * armor.c (armor_filter): Now creates valid onepass_sig packets + with all detected hash algorithms. + * mainproc.c (proc_plaintext): Now uses the hash algos as specified + in the onepass_sig packets (if there are any) + +Mon Jun 22 11:54:08 1998 Werner Koch (wk@isil.d.shuttle.de) + + * plaintext.c (handle_plaintext): add arg to disable outout + * mainproc.c (proc_plaintext): disable output when in sigs_only mode. + +Thu Jun 18 13:17:27 1998 Werner Koch (wk@isil.d.shuttle.de) + + * keygen.c: Removed all rsa packet stuff, chnaged defaults + for key generation. + +Sun Jun 14 21:28:31 1998 Werner Koch (wk@isil.d.shuttle.de) + + * misc.c (checksum_u16): Fixed a stupid bug which caused a + wrong checksum calculation for the secret key protection and + add a backward compatibility option. + * g10.c (main): Add option --emulate-checksum-bug. + +Thu Jun 11 13:26:44 1998 Werner Koch (wk@isil.d.shuttle.de) + + * packet.h: Major changes to the structure of public key material + which is now stored in an array and not anaymore in a union of + algorithm specific structures. These is needed to make the system + more extendable and makes a lot of stuff much simpler. Changed + all over the system. + + * dsa.c, rsa.c, elg.c: Removed. + +Wed Jun 10 07:22:02 1998 Werner Koch,mobil,,, (wk@tobold) + + * g10.c ("load-extension"): New option. + +Mon Jun 8 22:23:37 1998 Werner Koch (wk@isil.d.shuttle.de) + + * seckey-cert.c (do_check): Removed cipher constants + (protect_secret_key): Ditto. + +Fri May 29 10:00:28 1998 Werner Koch (wk@isil.d.shuttle.de) + + * trustdb.c (query_trust_info): New. + * keylist.c (list_one): Add output of trust info + * mainproc (list_node): ditto. + * g10.c (main): full trustdb init if -with-colons and any of the + key list modes. + +Thu May 28 10:34:42 1998 Werner Koch (wk@isil.d.shuttle.de) + + * status.c (STATUS_RSA_OR_IDEA): New. + * sig-check.c (check_signature): Output special status message. + * pubkey-enc.c (get_session_key): Ditto. + + * mainproc.c (check_sig_and_print): Changed format of output. + * passpharse.c (passphrase_to_dek): Likewise. + +Wed May 27 13:46:48 1998 Werner Koch (wk@isil.d.shuttle.de) + + * g10.c (aListSecretKeys): New option --list-secret-keys + * keylist.c (std_key_list): Renamed to public_key_list. + (secret_key_list): New + (list_one, list_all): Add support for secret keys. + * getkey.c (get_secret_keyring): New. + * mainproc.c (list_node): Add option --with-colons for secret keys + + * sig-check.c (check_key_signature): detection of selfsigs + * mainproc.c (list_node): fixed listing. + + * g10.c (aListSecretKeys): New option --always-trust + * pkclist.c (do_we_trust): Override per option added + + * status.c (write_status_text): Add a prefix to every output line. + +Wed May 27 07:49:21 1998 Werner Koch (wk@isil.d.shuttle.de) + + * g10 (--compress-keys): New. + * options.h (compress_keys): New. + * export.c (export_pubkeys): Only compresses with the new option. + +Tue May 26 11:24:33 1998 Werner Koch (wk@isil.d.shuttle.de) + + * passphrase.c (get_last_passphrase): New + (set_next_passphrase): New. + (passphrase_to_dek): add support for the above functions. + * keyedit.c (make_keysig_packet): Add sigclass 0x18, + changed all callers due to a new argument. + * keygen.c (write_keybinding): New + (generate_subkeypair): Add functionality + (ask_algo, ask_keysize, ask_valid_days): Broke out of generate_keypair + (ask_user_id, ask_passphrase): Ditto. + +Thu May 21 11:26:13 1998 Werner Koch (wk@isil.d.shuttle.de) + + * g10.c,gpgd.c (main): Does now return an int, so that egcs does + not complain. + + * armor.c (fake_packet): Removed erro message and add a noticed + that this part should be fixed. + + * sign.c (sign_file): Compression now comes in front of encryption. + * encode.c (encode_simple): Ditto. + (encode_crypt): Ditto. + +Tue May 19 16:18:19 1998 Werner Koch (wk@isil.d.shuttle.de) + + * armor.c (fake_packet): Changed assertion to log_error + +Sat May 16 16:02:06 1998 Werner Koch (wk@isil.d.shuttle.de) + + * build-packet.c (build_packet): Add SUBKEY packets. + +Fri May 15 17:57:23 1998 Werner Koch (wk@isil.d.shuttle.de) + + * sign.c (hash_for): New and used in all places here. + * main.h (DEFAULT_): new macros. + * g10.c (opt.def_digest_algo): Now set to 0 + + * compress.c (init_compress): Add support for algo 1 + * options.h (def_compress_algo): New + * g10.c (main): New option --compress-algo + +Fri May 15 13:23:59 1998 Werner Koch (wk@isil.d.shuttle.de) + + * g10.c (print_mds): New feature to print only one hash, + chnaged formatting. + +Thu May 14 15:36:24 1998 Werner Koch (wk@isil.d.shuttle.de) + + * misc.c (trap_unaligned) [__alpha__]: New + * g10.c (trap_unaligned): Add call to this to track down SIGBUS + on Alphas (to avoid the slow emulation code). + +Wed May 13 11:48:27 1998 Werner Koch (wk@isil.d.shuttle.de) + + * build-packet.c (do_signature): Support for v4 pakets. + * keyedit.c (make_keysig_packet): Ditto. + * build-packet.c (build_sig_subpkt_from_sig): New. + (build_sig_subpkt): New. + + * elg.c (g10_elg_sign): removed keyid_from_skc. + * dsa.c (g10_dsa_sign): Ditto. + * rsa.c (g10_rsa_sign): Ditto. + * keyedit.c (make_keysig_packet): Add call to keyid_from_skc + + * sign.c (clearsign_file): Support for v4 signatures. + (sign_file): Ditto. + +Wed May 6 09:31:24 1998 Werner Koch (wk@isil.d.shuttle.de) + + * parse-packet.c (do_parse): add support for 5 byte length leader. + (parse_subpkt): Ditto. + * build-packet.c (write_new_header): Ditto. + + * packet.h (SIGSUBPKT_): New constants. + * parse-packet.c (parse_sig_subpkt): Changed name, made global, + and arg to return packet length, chnaged all callers + + +Tue May 5 22:11:59 1998 Werner Koch (wk@isil.d.shuttle.de) + + * keygen.c (gen_dsa): New. + * build_packet.c (do_secret_cert): Support for DSA + +Mon May 4 19:01:25 1998 Werner Koch (wk@isil.d.shuttle.de) + + * compress.c: doubled buffer sizes + * parse-packet.c (do_plaintext): now uses iobuf_read/write. + +Mon May 4 09:35:53 1998 Werner Koch (wk@isil.d.shuttle.de) + + * seskey.c (encode_md_value): Add optional argument hash_algo, + changed all callers. + + * passphrase.c (make_dek_from_passphrase): Removed + * (get_passhrase_hash): Changed name to passphrase_to_dek, add arg, + changed all callers. + + * all: Introduced the new ELG identifier and added support for the + encryption only one (which is okay to use by GNUPG for signatures). + +Sun May 3 17:50:26 1998 Werner Koch (wk@isil.d.shuttle.de) + + * packet.h (PKT_OLD_COMMENT): New name for type 16. + * parse-packet.c (parse_comment): Now uses type 61 + +Fri May 1 12:44:39 1998 Werner Koch,mobil,,, (wk@tobold) + + * packet.h (count): Chnaged s2k count from byte to u32. + * seckey-cert.c (do_check): Changed s2k algo 3 to 4, changed + reading of count. + * build-packet.c (do_secret_cert): ditto. + * parse-packet.c (parse_certificate): ditto. + + * parse-packet.c (parse_symkeyenc): New. + * build-packet.c (do_symkey_enc): New. + +Thu Apr 30 16:33:34 1998 Werner Koch (wk@isil.d.shuttle.de) + + * sign.c (clearsign_file): Fixed "Hash: " armor line. + +Tue Apr 28 14:27:42 1998 Werner Koch (wk@isil.d.shuttle.de) + + * parse-packet.c (parse_subpkt): Some new types. + +Mon Apr 27 12:53:59 1998 Werner Koch (wk@isil.d.shuttle.de) + + * g10.c (main): Add option --skip-verify. + * mainproc.c (check_sig_and_print): Ditto. + + * g10.c (print_mds): Add output for Tiger. + + * sign.c (sign_file): Now uses partial length headers if used + in canonical textmode (kludge to fix a bug). + + * parse-packet.c (parse_certificate): Changed BLOWFISH id. + * pubkey-enc.c (get_session_key): Ditto. + * seskey.c (make_session_key): Ditto. + * seckey-cert.c (protect_secret_key,do_check): Add BLOWFISH160. + +Fri Apr 24 17:38:48 1998 Werner Koch,mobil,,, (wk@tobold) + + * sig-check.c (check_key_signature): Add sig-class 0x14..0x17 + * keyedit.c (sign-key): Some changes to start with support of + the above new sig-classes. + +Wed Apr 22 09:01:57 1998 Werner Koch,mobil,,, (wk@tobold) + + * getkey.c (compare_name): add email matching + +Tue Apr 21 16:17:12 1998 Werner Koch,mobil,,, (wk@tobold) + + * armor.c (armor_filter): fixed missing last LF before CSUM. + +Thu Apr 9 11:35:22 1998 Werner Koch (wk@isil.d.shuttle.de) + + * seckey-cert.c (do_check): New; combines all the check functions + into one. + + * sign.c: removed all key management functions + * keyedit.c: New. + +Thu Apr 9 09:49:36 1998 Werner Koch (wk@isil.d.shuttle.de) + + * import.c (chk_self_sigs): Changed an error message. + +Wed Apr 8 16:19:39 1998 Werner Koch (wk@isil.d.shuttle.de) + + * packet.h: packet structs now uses structs from the pubkey, + removed all copy operations from packet to pubkey structs. + +Wed Apr 8 13:40:33 1998 Werner Koch (wk@isil.d.shuttle.de) + + * trustdb.c (verify_own_certs): Fixed "public key not found". + + * getkey.c (key_byname): New, combines public and secret key search. + + * pkclist.c (build_pkc_list): Add new arg usage, changed all callers. + * skclist.c (build_skc_list): Likewise. + + * ringedit.c (find_keyblock, keyring_search2): Removed. + +Wed Apr 8 09:47:21 1998 Werner Koch (wk@isil.d.shuttle.de) + + * sig-check.c (do_check): Applied small fix from Ulf Möller. + +Tue Apr 7 19:28:07 1998 Werner Koch (wk@isil.d.shuttle.de) + + * cipher.c, encr-data.c, seckey-cert.c: Now uses cipher_xxxx + functions instead of blowfish_xxx or cast_xxx + +Tue Apr 7 11:04:02 1998 Werner Koch (wk@isil.d.shuttle.de) + + * Makefile.am (g10maint.o): Changed the way it is created. + +Mon Apr 6 11:17:08 1998 Werner Koch (wk@isil.d.shuttle.de) + + * misc.c: New. + * keygen.c (checksum,checksum_u16,checksum_mpi): Moved to misc.c + * seckey-cert.c: Kludge for wrong ELG checksum implementation. + +Sat Apr 4 20:07:01 1998 Werner Koch (wk@isil.d.shuttle.de) + + * cipher.c (cipher_filter): Support for CAST5 + * encr-data.c (decode_filter): Ditto. + (decrypt_data): Ditto. + * seskey.c (make_session_key): Ditto. + * seckey-cert.c (check_elg, check_dsa): Ditto, + (protect_secret_key): Ditto. + * pubkey-enc.c (get_session_key): Ditto. + * passphrase.c (hash_passphrase): Ditto. + +Thu Apr 2 20:22:35 1998 Werner Koch (wk@isil.d.shuttle.de) + + * gpgd.c: New + +Thu Apr 2 10:38:16 1998 Werner Koch (wk@isil.d.shuttle.de) + + * keygen.c (generate_keypair): Add valid_days stuff. + * trustdb.c (check_trust): Add check for valid_days. + +Wed Apr 1 16:15:58 1998 Werner Koch (wk@isil.d.shuttle.de) + + * keygen.c (generate_keypair): Addional question whether the + selected large keysize is really needed. + +Wed Apr 1 15:56:33 1998 Werner Koch (wk@isil.d.shuttle.de) + + * seckey-cert.c (protect_secret_key): merged protect_xxx to here. + +Wed Apr 1 10:34:46 1998 Werner Koch (wk@isil.d.shuttle.de) + + * Makefile.am (g10maint.c): Changed creation rule, so that it works + on FreeBSD (missing CFLAGS). + + * parse-packet.c (parse_subkey): Removed. + +Thu Mar 19 15:22:36 1998 Werner Koch (wk@isil.d.shuttle.de) + + * ringedit.c (keyring_enum): Fixed problem with reading too + many packets. Add support to read secret keyrings. + + * getkey.c (scan_keyring): Removed + (lookup): New to replace scan_keyring. + (scan_secret_keyring): Removed. + (lookup_skc): New. + +Wed Mar 18 11:47:34 1998 Werner Koch (wk@isil.d.shuttle.de) + + * ringedit.c (enum_keyblocks): New read mode 11. + + * keyid.c (elg_fingerprint_md): New and changed all other functions + to call this if the packet version is 4 or above. + +Tue Mar 17 20:46:16 1998 Werner Koch (wk@isil.d.shuttle.de) + + * parse-packet.c (parse_certificate): Add listing support for subkeys. + +Tue Mar 17 20:32:22 1998 Werner Koch (wk@isil.d.shuttle.de) + + * armor.c (is_armored): Allow marker packet. + +Thu Mar 12 13:36:49 1998 Werner Koch (wk@isil.d.shuttle.de) + + * trustdb.c (check_trust): Checks timestamp of pubkey. + * sig-check. (do_check): Compares timestamps. + +Tue Mar 10 17:01:56 1998 Werner Koch (wk@isil.d.shuttle.de) + + * g10.c (main): Add call to init_signals. + * signal.c: New. + +Mon Mar 9 12:43:42 1998 Werner Koch (wk@isil.d.shuttle.de) + + * dsa.c: New + * packet.h, free-packet.c, parse-packet.c : Add support for DSA + * sig-check.c, getkey.c, keyid.c, ringedit.c: Ditto. + * seckey-cert.c: Ditto. + + * packet.h : Moved .digest_algo of signature packets to outer + structure. Changed all references + +Sun Mar 8 13:06:42 1998 Werner Koch (wk@isil.d.shuttle.de) + + * openfile.c : Support for stdout filename "-". + + * mainproc.c (check_sig_and_print): Enhanced status output: + * status.c (write_status_text): New. + +Fri Mar 6 16:10:54 1998 Werner Koch (wk@isil.d.shuttle.de) + + * kbnode.c (clone_kbnode): Fixed private_flag. + + * mainproc.c (list_node): Output of string "Revoked" as user-id. + +Fri Mar 6 14:26:39 1998 Werner Koch (wk@isil.d.shuttle.de) + + * g10.c (main): Add userids to "-kv" and cleaned up this stuff. + +Fri Mar 6 12:45:58 1998 Werner Koch (wk@isil.d.shuttle.de) + + * g10.c (main): Changed semantics of the list-... commands + and added a new one. Removed option "-d" + + * decrypt.c: New. + + * trustdb.c (init_trustdb): Autocreate directory only if it ends + in "/.gnupg". + +Thu Mar 5 12:12:11 1998 Werner Koch (wk@isil.d.shuttle.de) + + * mainproc.c (do_proc_packets): New. Common part of proc_packet. + (proc_signature_packets): special version to handle signature data. + * verify.c: New. + * g10.c (aVerify): New. + * plaintext.c (hash_datafiles): New. + * compress.c (handle_compressed): Add callback arg, changed caller. + +Thu Mar 5 10:20:06 1998 Werner Koch (wk@isil.d.shuttle.de) + + * g10.c: Is nom the common source for gpg and gpgm + * g10maint.c: Removed + * Makefile.am: Add rule to build g10maint.c + +Thu Mar 5 08:43:59 1998 Werner Koch (wk@isil.d.shuttle.de) + + * g10.c (main): Changed the way clear text sigs are faked. + +Wed Mar 4 19:47:37 1998 Werner Koch (wk@isil.d.shuttle.de) + + * g10maint.c (aMuttKeyList): New + * keylist.c: New. + +Wed Mar 4 17:20:33 1998 Werner Koch (wk@isil.d.shuttle.de) + + * getkey.c (get_pubkey_byname): Kludge to allow 0x prefix. + +Tue Mar 3 13:46:55 1998 Werner Koch (wk@isil.d.shuttle.de) + + * g10maint.c (main): New option --gen-random. + +Tue Mar 3 09:50:08 1998 Werner Koch (wk@isil.d.shuttle.de) + + * g10.c (aDeleteSecretKey): New. + (aEditSig): Add option "--edit-key" as synonym for "--edit-sig". + (aDeleteSecretKey): New. + * getkey.c (seckey_available): New. + * sign.c (delete_key): Enhanced to delete secret keys, changed all + callers. + +Mon Mar 2 21:23:48 1998 Werner Koch (wk@isil.d.shuttle.de) + + * pkc_list.c (build_pkc_list): Add interactive input of user ID. + +Mon Mar 2 20:54:05 1998 Werner Koch (wk@isil.d.shuttle.de) + + * pkclist.c (do_we_trust_pre): New. + (add_ownertrust): Add message. + * trustdb.c (enum_trust_web): Quick fix. + +Mon Mar 2 13:50:53 1998 Werner Koch (wk@isil.d.shuttle.de) + + * g10.c (main): New action aDeleteKey + * sign.c (delete_key): New. + +Sun Mar 1 16:38:58 1998 Werner Koch (wk@isil.d.shuttle.de) + + * trustdb.c (do_check): No returns TRUST_UNDEFINED instead of + eof error. + +Fri Feb 27 18:14:03 1998 Werner Koch (wk@isil.d.shuttle.de) + + * armor.c (find_header): Removed trailing CR on headers. + +Fri Feb 27 18:02:48 1998 Werner Koch (wk@isil.d.shuttle.de) + + * ringedit.c (keyring_search) [MINGW32]: Open and close file here + because rename does not work on open files. Chnaged callers. + +Fri Feb 27 16:43:11 1998 Werner Koch (wk@isil.d.shuttle.de) + + * sig-check.c (do_check): Add an md_enable. + * mainproc.c (do_check_sig): Use md_open in case of detached sig + (proc_tree): Take detached sigs into account. + +Fri Feb 27 15:22:46 1998 Werner Koch (wk@isil.d.shuttle.de) + + * g10.c (main): Make use of GNUPGHOME envvar. + * g10main.c (main): Ditto. + +Wed Feb 25 11:40:04 1998 Werner Koch (wk@isil.d.shuttle.de) + + * plaintext.c (ask_for_detached_datafile): add opt.verbose to + info output. + + * openfile.c (open_sigfile): Try also name ending in ".asc" + +Wed Feb 25 08:41:00 1998 Werner Koch (wk@isil.d.shuttle.de) + + * keygen.c (generate_keypair): Fixed memory overflow. + +Tue Feb 24 15:51:55 1998 Werner Koch (wk@isil.d.shuttle.de) + + * parse-packet.c (parse_certificate): Support for S2K. + * build-packet.c (do_secret_cert): Ditto. + * keygen.c (gen_elg): Ditto. + * seckey-cert.c (check_elg): Ditto + (protect_elg): Ditto. + * sign.c (chnage_passphrase): Ditto. + * passphrase.c (get_passphrase_hash): Support for a salt and + changed all callers. + (make_dek_from_passphrase): Ditto. + +Tue Feb 24 12:30:56 1998 Werner Koch (wk@isil.d.shuttle.de) + + * build-packet.c (hash_public_cert): Disabled debug output. + +Fri Feb 20 17:22:28 1998 Werner Koch (wk@isil.d.shuttle.de) + + * trustdb.c (init_trustdb) [MINGW32]: Removed 2nd mkdir arg. + (keyring_copy) [MINGW32]: Add a remove prior to the renames. + +Wed Feb 18 18:39:02 1998 Werner Koch (wk@isil.d.shuttle.de) + + * Makefile.am (OMIT_DEPENDENCIES): New. + + * rsa.c: Replaced log_bug by BUG. + +Wed Feb 18 13:35:58 1998 Werner Koch (wk@isil.d.shuttle.de) + + * mainproc.c (do_check_sig): Now uses hash_public_cert. + * parse-packet.c (parse_certificate): Removed hashing. + * packet.h (public_cert): Removed hash variable. + * free-packet.c (copy_public_cert, free_public_cert): Likewise. + + * sig-check.c (check_key_signatures): Changed semantics. + +Wed Feb 18 12:11:28 1998 Werner Koch (wk@isil.d.shuttle.de) + + * trustdb.c (do_check): Add handling for revocation certificates. + (build_sigrecs): Ditto. + (check_sigs): Ditto. + +Wed Feb 18 09:31:04 1998 Werner Koch (wk@isil.d.shuttle.de) + + * armor.c (armor_filter): Add afx->hdrlines. + * revoke.c (gen_revoke): Add comment line. + * dearmor.c (enarmor_file): Ditto. + + * sig-check.c (check_key_signature): Add handling for class 0x20. + * mainproc.c : Ditto. + +Tue Feb 17 21:24:17 1998 Werner Koch (wk@isil.d.shuttle.de) + + * armor.c : Add header lines "...ARMORED FILE .." + * dearmor.c (enarmor_file): New. + * g10maint.c (main): New option "--enarmor" + +Tue Feb 17 19:03:33 1998 Werner Koch (wk@isil.d.shuttle.de) + + * mainproc.c : Changed a lot, because the packets are now stored + a simple linlked list and not anymore in a complicatd tree structure. + +Tue Feb 17 10:14:48 1998 Werner Koch (wk@isil.d.shuttle.de) + + * free_packet.c (cmp_public_certs): New. + (cmp_user_ids): New. + + * kbnode.c (clone_kbnode): New. + (release_kbnode): Add clone support. + + * ringedit.c (find_keyblock_bypkc): New. + + * sign.c (remove_keysigs): Self signatures are now skipped, + changed arguments and all callers. + + * import.c : Add functionality. + +Tue Feb 17 09:31:40 1998 Werner Koch (wk@isil.d.shuttle.de) + + * options.h (homedir): New option. + * g10.c, g10maint.c, getkey.c, keygen.c, trustdb.c (opt.homedir): New. + + * trustdb.c (init_trustdb): mkdir for hoem directory + (sign_private_data): Renamed "sig" to "g10.sig" + +Mon Feb 16 20:02:03 1998 Werner Koch (wk@isil.d.shuttle.de) + + * kbnode.c (commit_kbnode): New. + (delete_kbnode): removed unused first arg. Changed all Callers. + + * ringedit.c (keyblock_resource_name): New. + (get_keyblock_handle): NULL for filename returns default resource. + +Mon Feb 16 19:38:48 1998 Werner Koch (wk@isil.d.shuttle.de) + + * sig-check.s (check_key_signature): Now uses the supplied + public key to check the signature and not any more the one + from the getkey.c + (do_check): New. + (check_signature): Most work moved to do_check. + +Mon Feb 16 14:48:57 1998 Werner Koch (wk@isil.d.shuttle.de) + + * armor.c (find_header): Fixed another bug. + +Mon Feb 16 12:18:34 1998 Werner Koch (wk@isil.d.shuttle.de) + + * getkey.c (scan_keyring): Add handling of compressed keyrings. + +Mon Feb 16 10:44:51 1998 Werner Koch (wk@isil.d.shuttle.de) + + * g10.c, g10maint.c (strusage): Rewrote. + (build_list): New + +Mon Feb 16 08:58:41 1998 Werner Koch (wk@isil.d.shuttle.de) + + * armor.c (use_armor): New. + +Sat Feb 14 14:30:57 1998 Werner Koch (wk@isil.d.shuttle.de) + + * mainproc.c (proc_tree): Sigclass fix. + +Sat Feb 14 14:16:33 1998 Werner Koch (wk@isil.d.shuttle.de) + + * armor.c (armor_filter): Changed version and comment string. + * encode.c, sign.c, keygen.c: Changed all comment packet strings. + +Sat Feb 14 12:39:24 1998 Werner Koch (wk@isil.d.shuttle.de) + + * g10.c (aGenRevoke): New command. + * revoke.c: New. + * sign.c (make_keysig_packet): Add support for sigclass 0x20. + +Fri Feb 13 20:18:14 1998 Werner Koch (wk@isil.d.shuttle.de) + + * ringedit.c (enum_keyblocks, keyring_enum): New. + +Fri Feb 13 19:33:40 1998 Werner Koch (wk@isil.d.shuttle.de) + + * export.c: Add functionality. + + * keygen.c (generate_keypair): Moved the leading comment behind the + key packet. + * kbnode.c (walk_kbnode): Fixed. + + * g10.c (main): listing armored keys now work. + +Fri Feb 13 16:17:43 1998 Werner Koch (wk@isil.d.shuttle.de) + + * parse-packet.c (parse_publickey, parse_signature): Fixed calls + to mpi_read used for ELG b. + +Fri Feb 13 15:13:23 1998 Werner Koch (wk@isil.d.shuttle.de) + + * g10.c (main): changed formatting of help output. + +Thu Feb 12 22:24:42 1998 Werner Koch (wk@frodo) + + * pubkey-enc.c (get_session_key): rewritten + + + Copyright 1998, 1999, 2000, 2001, 2002, 2003, + 2004, 2005, 2006 Free Software Foundation, Inc. + + This file is free software; as a special exception the author gives + unlimited permission to copy and/or distribute it, with or without + modifications, as long as this notice is preserved. + + This file is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY, to the extent permitted by law; without even the + implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. diff --git a/g10/Makefile.am b/g10/Makefile.am new file mode 100644 index 0000000..fa4e0b5 --- /dev/null +++ b/g10/Makefile.am @@ -0,0 +1,156 @@ +# Copyright (C) 1998, 1999, 2000, 2001, 2002, 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 2 of the License, or +# (at your option) any later version. +# +# GnuPG is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +## Process this file with automake to produce Makefile.in + +AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_srcdir)/intl @LIBUSB_CPPFLAGS@ + +EXTRA_DIST = options.skel +# it seems that we can't use this with automake 1.5 +#OMIT_DEPENDENCIES = zlib.h zconf.h + +if ! HAVE_DOSISH_SYSTEM +AM_CPPFLAGS += -DGNUPG_LIBEXECDIR="\"$(libexecdir)/@PACKAGE@\"" +endif + +needed_libs = ../cipher/libcipher.a ../mpi/libmpi.a ../util/libutil.a +other_libs = $(LIBICONV) $(DNSLIBS) $(LIBINTL) $(CAPLIBS) + +bin_PROGRAMS = gpg gpgv + +if ENABLE_BZIP2_SUPPORT +bzip2_source = compress-bz2.c +else +bzip2_source = +endif + +common_source = \ + global.h \ + build-packet.c \ + compress.c \ + $(bzip2_source) \ + filter.h \ + free-packet.c \ + getkey.c \ + keydb.c keydb.h \ + keyring.c keyring.h \ + seskey.c \ + kbnode.c \ + main.h \ + mainproc.c \ + armor.c \ + mdfilter.c \ + textfilter.c \ + progress.c \ + misc.c \ + options.h \ + openfile.c \ + keyid.c \ + packet.h \ + parse-packet.c \ + status.c \ + status.h \ + plaintext.c \ + sig-check.c \ + keylist.c \ + signal.c + +if ENABLE_CARD_SUPPORT +card_support_source_gpg = card-util.c +card_support_source_scd = \ + app-common.h \ + app-openpgp.c \ + iso7816.c iso7816.h \ + apdu.c apdu.h \ + ccid-driver.c ccid-driver.h +card_support_source_local = cardglue.c cardglue.h tlv.c tlv.h +else +card_support_source_gpg = +card_support_source_scd = +card_support_source_local = +endif + +card_support_source = $(card_support_source_local) $(card_support_source_gpg) $(card_support_source_scd) + +gpg_SOURCES = gpg.c \ + $(common_source) \ + $(card_support_source) \ + pkclist.c \ + skclist.c \ + pubkey-enc.c \ + passphrase.c \ + seckey-cert.c \ + encr-data.c \ + cipher.c \ + encode.c \ + sign.c \ + verify.c \ + revoke.c \ + decrypt.c \ + keyedit.c \ + dearmor.c \ + import.c \ + export.c \ + trustdb.c \ + trustdb.h \ + tdbdump.c \ + tdbio.c \ + tdbio.h \ + delkey.c \ + keygen.c \ + pipemode.c \ + helptext.c \ + keyserver.c \ + keyserver-internal.h \ + photoid.c photoid.h \ + exec.c exec.h + +gpgv_SOURCES = gpgv.c \ + $(common_source) \ + verify.c + +LDADD = $(needed_libs) $(other_libs) @ZLIBS@ @W32LIBS@ @LIBREADLINE@ +gpg_LDADD = $(LDADD) @DLLIBS@ @NETLIBS@ @LIBUSB@ + +$(PROGRAMS): $(needed_libs) + +install-data-local: + $(mkinstalldirs) $(DESTDIR)$(pkgdatadir) + $(INSTALL_DATA) $(srcdir)/options.skel \ + $(DESTDIR)$(pkgdatadir)/options.skel + @set -e;\ + if test -f $(DESTDIR)$(bindir)/gpgm ; then \ + echo "removing obsolete gpgm binary" ; \ + rm $(DESTDIR)$(bindir)/gpgm ; \ + fi + +# Helper to update some source files. +update-source-from-gnupg-2: + @set -e; \ + if test -d ../../gnupg-1.9/scd; then dir="../../gnupg-1.9"; \ + elif test -d ../../gnupg/scd; then dir="../../gnupg"; \ + else exit 1; \ + fi; \ + for i in $(card_support_source_scd); do \ + cp $$dir/scd/$$i $$i; echo $$i; \ + done ;\ + for i in $(card_support_source_gpg); do \ + cp $$dir/g10/$$i $$i; echo $$i; \ + done ; \ + echo "Please remember to update the ChangeLog accordingly!" diff --git a/g10/Makefile.in b/g10/Makefile.in new file mode 100644 index 0000000..20d7899 --- /dev/null +++ b/g10/Makefile.in @@ -0,0 +1,801 @@ +# Makefile.in generated by automake 1.9.6 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, +# 2003, 2004, 2005 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) 1998, 1999, 2000, 2001, 2002, 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 2 of the License, or +# (at your option) any later version. +# +# GnuPG is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +srcdir = @srcdir@ +top_srcdir = @top_srcdir@ +VPATH = @srcdir@ +pkgdatadir = $(datadir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +top_builddir = .. +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +INSTALL = @INSTALL@ +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@ +# it seems that we can't use this with automake 1.5 +#OMIT_DEPENDENCIES = zlib.h zconf.h +@HAVE_DOSISH_SYSTEM_FALSE@am__append_1 = -DGNUPG_LIBEXECDIR="\"$(libexecdir)/@PACKAGE@\"" +bin_PROGRAMS = gpg$(EXEEXT) gpgv$(EXEEXT) +subdir = g10 +DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in ChangeLog +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/glibc21.m4 $(top_srcdir)/m4/iconv.m4 \ + $(top_srcdir)/m4/intdiv0.m4 $(top_srcdir)/m4/intmax.m4 \ + $(top_srcdir)/m4/inttypes-pri.m4 $(top_srcdir)/m4/inttypes.m4 \ + $(top_srcdir)/m4/inttypes_h.m4 $(top_srcdir)/m4/isc-posix.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/libcurl.m4 \ + $(top_srcdir)/m4/libusb.m4 $(top_srcdir)/m4/longdouble.m4 \ + $(top_srcdir)/m4/longlong.m4 $(top_srcdir)/m4/nls.m4 \ + $(top_srcdir)/m4/noexecstack.m4 $(top_srcdir)/m4/po.m4 \ + $(top_srcdir)/m4/printf-posix.m4 $(top_srcdir)/m4/progtest.m4 \ + $(top_srcdir)/m4/readline.m4 $(top_srcdir)/m4/signed.m4 \ + $(top_srcdir)/m4/size_max.m4 $(top_srcdir)/m4/stdint_h.m4 \ + $(top_srcdir)/m4/tar-ustar.m4 $(top_srcdir)/m4/uintmax_t.m4 \ + $(top_srcdir)/m4/ulonglong.m4 $(top_srcdir)/m4/wchar_t.m4 \ + $(top_srcdir)/m4/wint_t.m4 $(top_srcdir)/m4/xsize.m4 \ + $(top_srcdir)/acinclude.m4 $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +mkinstalldirs = $(SHELL) $(top_srcdir)/scripts/mkinstalldirs +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +am__installdirs = "$(DESTDIR)$(bindir)" +binPROGRAMS_INSTALL = $(INSTALL_PROGRAM) +PROGRAMS = $(bin_PROGRAMS) +am__gpg_SOURCES_DIST = gpg.c global.h build-packet.c compress.c \ + compress-bz2.c filter.h free-packet.c getkey.c keydb.c keydb.h \ + keyring.c keyring.h seskey.c kbnode.c main.h mainproc.c \ + armor.c mdfilter.c textfilter.c progress.c misc.c options.h \ + openfile.c keyid.c packet.h parse-packet.c status.c status.h \ + plaintext.c sig-check.c keylist.c signal.c cardglue.c \ + cardglue.h tlv.c tlv.h card-util.c app-common.h app-openpgp.c \ + iso7816.c iso7816.h apdu.c apdu.h ccid-driver.c ccid-driver.h \ + pkclist.c skclist.c pubkey-enc.c passphrase.c seckey-cert.c \ + encr-data.c cipher.c encode.c sign.c verify.c revoke.c \ + decrypt.c keyedit.c dearmor.c import.c export.c trustdb.c \ + trustdb.h tdbdump.c tdbio.c tdbio.h delkey.c keygen.c \ + pipemode.c helptext.c keyserver.c keyserver-internal.h \ + photoid.c photoid.h exec.c exec.h +@ENABLE_BZIP2_SUPPORT_TRUE@am__objects_1 = compress-bz2.$(OBJEXT) +am__objects_2 = build-packet.$(OBJEXT) compress.$(OBJEXT) \ + $(am__objects_1) free-packet.$(OBJEXT) getkey.$(OBJEXT) \ + keydb.$(OBJEXT) keyring.$(OBJEXT) seskey.$(OBJEXT) \ + kbnode.$(OBJEXT) mainproc.$(OBJEXT) armor.$(OBJEXT) \ + mdfilter.$(OBJEXT) textfilter.$(OBJEXT) progress.$(OBJEXT) \ + misc.$(OBJEXT) openfile.$(OBJEXT) keyid.$(OBJEXT) \ + parse-packet.$(OBJEXT) status.$(OBJEXT) plaintext.$(OBJEXT) \ + sig-check.$(OBJEXT) keylist.$(OBJEXT) signal.$(OBJEXT) +@ENABLE_CARD_SUPPORT_TRUE@am__objects_3 = cardglue.$(OBJEXT) \ +@ENABLE_CARD_SUPPORT_TRUE@ tlv.$(OBJEXT) +@ENABLE_CARD_SUPPORT_TRUE@am__objects_4 = card-util.$(OBJEXT) +@ENABLE_CARD_SUPPORT_TRUE@am__objects_5 = app-openpgp.$(OBJEXT) \ +@ENABLE_CARD_SUPPORT_TRUE@ iso7816.$(OBJEXT) apdu.$(OBJEXT) \ +@ENABLE_CARD_SUPPORT_TRUE@ ccid-driver.$(OBJEXT) +am__objects_6 = $(am__objects_3) $(am__objects_4) $(am__objects_5) +am_gpg_OBJECTS = gpg.$(OBJEXT) $(am__objects_2) $(am__objects_6) \ + pkclist.$(OBJEXT) skclist.$(OBJEXT) pubkey-enc.$(OBJEXT) \ + passphrase.$(OBJEXT) seckey-cert.$(OBJEXT) encr-data.$(OBJEXT) \ + cipher.$(OBJEXT) encode.$(OBJEXT) sign.$(OBJEXT) \ + verify.$(OBJEXT) revoke.$(OBJEXT) decrypt.$(OBJEXT) \ + keyedit.$(OBJEXT) dearmor.$(OBJEXT) import.$(OBJEXT) \ + export.$(OBJEXT) trustdb.$(OBJEXT) tdbdump.$(OBJEXT) \ + tdbio.$(OBJEXT) delkey.$(OBJEXT) keygen.$(OBJEXT) \ + pipemode.$(OBJEXT) helptext.$(OBJEXT) keyserver.$(OBJEXT) \ + photoid.$(OBJEXT) exec.$(OBJEXT) +gpg_OBJECTS = $(am_gpg_OBJECTS) +am__DEPENDENCIES_1 = ../cipher/libcipher.a ../mpi/libmpi.a \ + ../util/libutil.a +am__DEPENDENCIES_2 = +am__DEPENDENCIES_3 = $(am__DEPENDENCIES_2) $(am__DEPENDENCIES_2) \ + $(am__DEPENDENCIES_2) $(am__DEPENDENCIES_2) +am__DEPENDENCIES_4 = $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_3) +gpg_DEPENDENCIES = $(am__DEPENDENCIES_4) +am__gpgv_SOURCES_DIST = gpgv.c global.h build-packet.c compress.c \ + compress-bz2.c filter.h free-packet.c getkey.c keydb.c keydb.h \ + keyring.c keyring.h seskey.c kbnode.c main.h mainproc.c \ + armor.c mdfilter.c textfilter.c progress.c misc.c options.h \ + openfile.c keyid.c packet.h parse-packet.c status.c status.h \ + plaintext.c sig-check.c keylist.c signal.c verify.c +am_gpgv_OBJECTS = gpgv.$(OBJEXT) $(am__objects_2) verify.$(OBJEXT) +gpgv_OBJECTS = $(am_gpgv_OBJECTS) +gpgv_LDADD = $(LDADD) +gpgv_DEPENDENCIES = $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_3) +DEFAULT_INCLUDES = -I. -I$(srcdir) -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/scripts/depcomp +am__depfiles_maybe = depfiles +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +CCLD = $(CC) +LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ +SOURCES = $(gpg_SOURCES) $(gpgv_SOURCES) +DIST_SOURCES = $(am__gpg_SOURCES_DIST) $(am__gpgv_SOURCES_DIST) +ETAGS = etags +CTAGS = ctags +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +ALLOCA = @ALLOCA@ +AMDEP_FALSE = @AMDEP_FALSE@ +AMDEP_TRUE = @AMDEP_TRUE@ +AMTAR = @AMTAR@ +AR = @AR@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BUILD_INCLUDED_LIBINTL = @BUILD_INCLUDED_LIBINTL@ +CAPLIBS = @CAPLIBS@ +CATOBJEXT = @CATOBJEXT@ +CC = @CC@ +CCAS = @CCAS@ +CCASFLAGS = @CCASFLAGS@ +CCDEPMODE = @CCDEPMODE@ +CC_FOR_BUILD = @CC_FOR_BUILD@ +CFLAGS = @CFLAGS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CROSS_COMPILING_FALSE = @CROSS_COMPILING_FALSE@ +CROSS_COMPILING_TRUE = @CROSS_COMPILING_TRUE@ +CYGPATH_W = @CYGPATH_W@ +DATADIRNAME = @DATADIRNAME@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DLLIBS = @DLLIBS@ +DNSLIBS = @DNSLIBS@ +DOCBOOK_TO_MAN = @DOCBOOK_TO_MAN@ +DOCBOOK_TO_TEXI = @DOCBOOK_TO_TEXI@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +ENABLE_AGENT_SUPPORT_FALSE = @ENABLE_AGENT_SUPPORT_FALSE@ +ENABLE_AGENT_SUPPORT_TRUE = @ENABLE_AGENT_SUPPORT_TRUE@ +ENABLE_BZIP2_SUPPORT_FALSE = @ENABLE_BZIP2_SUPPORT_FALSE@ +ENABLE_BZIP2_SUPPORT_TRUE = @ENABLE_BZIP2_SUPPORT_TRUE@ +ENABLE_CARD_SUPPORT_FALSE = @ENABLE_CARD_SUPPORT_FALSE@ +ENABLE_CARD_SUPPORT_TRUE = @ENABLE_CARD_SUPPORT_TRUE@ +ENABLE_LOCAL_ZLIB_FALSE = @ENABLE_LOCAL_ZLIB_FALSE@ +ENABLE_LOCAL_ZLIB_TRUE = @ENABLE_LOCAL_ZLIB_TRUE@ +EXEEXT = @EXEEXT@ +FAKE_CURL_FALSE = @FAKE_CURL_FALSE@ +FAKE_CURL_TRUE = @FAKE_CURL_TRUE@ +FAQPROG = @FAQPROG@ +GENCAT = @GENCAT@ +GETOPT = @GETOPT@ +GLIBC21 = @GLIBC21@ +GMSGFMT = @GMSGFMT@ +GPGKEYS_CURL = @GPGKEYS_CURL@ +GPGKEYS_FINGER = @GPGKEYS_FINGER@ +GPGKEYS_HKP = @GPGKEYS_HKP@ +GPGKEYS_LDAP = @GPGKEYS_LDAP@ +GPGKEYS_MAILTO = @GPGKEYS_MAILTO@ +GREP = @GREP@ +HAVE_ASPRINTF = @HAVE_ASPRINTF@ +HAVE_DOCBOOK_TO_MAN_FALSE = @HAVE_DOCBOOK_TO_MAN_FALSE@ +HAVE_DOCBOOK_TO_MAN_TRUE = @HAVE_DOCBOOK_TO_MAN_TRUE@ +HAVE_DOCBOOK_TO_TEXI_FALSE = @HAVE_DOCBOOK_TO_TEXI_FALSE@ +HAVE_DOCBOOK_TO_TEXI_TRUE = @HAVE_DOCBOOK_TO_TEXI_TRUE@ +HAVE_DOSISH_SYSTEM_FALSE = @HAVE_DOSISH_SYSTEM_FALSE@ +HAVE_DOSISH_SYSTEM_TRUE = @HAVE_DOSISH_SYSTEM_TRUE@ +HAVE_POSIX_PRINTF = @HAVE_POSIX_PRINTF@ +HAVE_SNPRINTF = @HAVE_SNPRINTF@ +HAVE_USTAR_FALSE = @HAVE_USTAR_FALSE@ +HAVE_USTAR_TRUE = @HAVE_USTAR_TRUE@ +HAVE_W32_SYSTEM_FALSE = @HAVE_W32_SYSTEM_FALSE@ +HAVE_W32_SYSTEM_TRUE = @HAVE_W32_SYSTEM_TRUE@ +HAVE_WPRINTF = @HAVE_WPRINTF@ +IDEA_O = @IDEA_O@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +INSTOBJEXT = @INSTOBJEXT@ +INTLBISON = @INTLBISON@ +INTLLIBS = @INTLLIBS@ +INTLOBJS = @INTLOBJS@ +INTL_LIBTOOL_SUFFIX_PREFIX = @INTL_LIBTOOL_SUFFIX_PREFIX@ +LDAPLIBS = @LDAPLIBS@ +LDAP_CPPFLAGS = @LDAP_CPPFLAGS@ +LDFLAGS = @LDFLAGS@ +LIBCURL = @LIBCURL@ +LIBCURL_CPPFLAGS = @LIBCURL_CPPFLAGS@ +LIBICONV = @LIBICONV@ +LIBINTL = @LIBINTL@ +LIBOBJS = @LIBOBJS@ +LIBREADLINE = @LIBREADLINE@ +LIBS = @LIBS@ +LIBUSB = @LIBUSB@ +LIBUSB_CPPFLAGS = @LIBUSB_CPPFLAGS@ +LTLIBICONV = @LTLIBICONV@ +LTLIBINTL = @LTLIBINTL@ +LTLIBOBJS = @LTLIBOBJS@ +MAINT = @MAINT@ +MAINTAINER_MODE_FALSE = @MAINTAINER_MODE_FALSE@ +MAINTAINER_MODE_TRUE = @MAINTAINER_MODE_TRUE@ +MAKEINFO = @MAKEINFO@ +MKINSTALLDIRS = @MKINSTALLDIRS@ +MPI_EXTRA_ASM_OBJS = @MPI_EXTRA_ASM_OBJS@ +MPI_OPT_FLAGS = @MPI_OPT_FLAGS@ +MPI_SFLAGS = @MPI_SFLAGS@ +MSGFMT = @MSGFMT@ +MSGMERGE = @MSGMERGE@ +NETLIBS = @NETLIBS@ +NM = @NM@ +NOEXECSTACK_FLAGS = @NOEXECSTACK_FLAGS@ +OBJEXT = @OBJEXT@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PERL = @PERL@ +POSUB = @POSUB@ +RANLIB = @RANLIB@ +SENDMAIL = @SENDMAIL@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +TAR = @TAR@ +USE_DNS_SRV_FALSE = @USE_DNS_SRV_FALSE@ +USE_DNS_SRV_TRUE = @USE_DNS_SRV_TRUE@ +USE_INCLUDED_LIBINTL = @USE_INCLUDED_LIBINTL@ +USE_INTERNAL_REGEX_FALSE = @USE_INTERNAL_REGEX_FALSE@ +USE_INTERNAL_REGEX_TRUE = @USE_INTERNAL_REGEX_TRUE@ +USE_NLS = @USE_NLS@ +USE_RNDEGD_FALSE = @USE_RNDEGD_FALSE@ +USE_RNDEGD_TRUE = @USE_RNDEGD_TRUE@ +USE_RNDLINUX_FALSE = @USE_RNDLINUX_FALSE@ +USE_RNDLINUX_TRUE = @USE_RNDLINUX_TRUE@ +USE_RNDUNIX_FALSE = @USE_RNDUNIX_FALSE@ +USE_RNDUNIX_TRUE = @USE_RNDUNIX_TRUE@ +USE_RNDW32_FALSE = @USE_RNDW32_FALSE@ +USE_RNDW32_TRUE = @USE_RNDW32_TRUE@ +USE_SHA512_FALSE = @USE_SHA512_FALSE@ +USE_SHA512_TRUE = @USE_SHA512_TRUE@ +USE_SIMPLE_GETTEXT_FALSE = @USE_SIMPLE_GETTEXT_FALSE@ +USE_SIMPLE_GETTEXT_TRUE = @USE_SIMPLE_GETTEXT_TRUE@ +VERSION = @VERSION@ +W32LIBS = @W32LIBS@ +WORKING_FAQPROG_FALSE = @WORKING_FAQPROG_FALSE@ +WORKING_FAQPROG_TRUE = @WORKING_FAQPROG_TRUE@ +XGETTEXT = @XGETTEXT@ +ZLIBS = @ZLIBS@ +_libcurl_config = @_libcurl_config@ +_usb_config = @_usb_config@ +ac_ct_CC = @ac_ct_CC@ +am__fastdepCC_FALSE = @am__fastdepCC_FALSE@ +am__fastdepCC_TRUE = @am__fastdepCC_TRUE@ +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@ +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 = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_srcdir)/intl \ + @LIBUSB_CPPFLAGS@ $(am__append_1) +EXTRA_DIST = options.skel +needed_libs = ../cipher/libcipher.a ../mpi/libmpi.a ../util/libutil.a +other_libs = $(LIBICONV) $(DNSLIBS) $(LIBINTL) $(CAPLIBS) +@ENABLE_BZIP2_SUPPORT_FALSE@bzip2_source = +@ENABLE_BZIP2_SUPPORT_TRUE@bzip2_source = compress-bz2.c +common_source = \ + global.h \ + build-packet.c \ + compress.c \ + $(bzip2_source) \ + filter.h \ + free-packet.c \ + getkey.c \ + keydb.c keydb.h \ + keyring.c keyring.h \ + seskey.c \ + kbnode.c \ + main.h \ + mainproc.c \ + armor.c \ + mdfilter.c \ + textfilter.c \ + progress.c \ + misc.c \ + options.h \ + openfile.c \ + keyid.c \ + packet.h \ + parse-packet.c \ + status.c \ + status.h \ + plaintext.c \ + sig-check.c \ + keylist.c \ + signal.c + +@ENABLE_CARD_SUPPORT_FALSE@card_support_source_gpg = +@ENABLE_CARD_SUPPORT_TRUE@card_support_source_gpg = card-util.c +@ENABLE_CARD_SUPPORT_FALSE@card_support_source_scd = +@ENABLE_CARD_SUPPORT_TRUE@card_support_source_scd = \ +@ENABLE_CARD_SUPPORT_TRUE@ app-common.h \ +@ENABLE_CARD_SUPPORT_TRUE@ app-openpgp.c \ +@ENABLE_CARD_SUPPORT_TRUE@ iso7816.c iso7816.h \ +@ENABLE_CARD_SUPPORT_TRUE@ apdu.c apdu.h \ +@ENABLE_CARD_SUPPORT_TRUE@ ccid-driver.c ccid-driver.h + +@ENABLE_CARD_SUPPORT_FALSE@card_support_source_local = +@ENABLE_CARD_SUPPORT_TRUE@card_support_source_local = cardglue.c cardglue.h tlv.c tlv.h +card_support_source = $(card_support_source_local) $(card_support_source_gpg) $(card_support_source_scd) +gpg_SOURCES = gpg.c \ + $(common_source) \ + $(card_support_source) \ + pkclist.c \ + skclist.c \ + pubkey-enc.c \ + passphrase.c \ + seckey-cert.c \ + encr-data.c \ + cipher.c \ + encode.c \ + sign.c \ + verify.c \ + revoke.c \ + decrypt.c \ + keyedit.c \ + dearmor.c \ + import.c \ + export.c \ + trustdb.c \ + trustdb.h \ + tdbdump.c \ + tdbio.c \ + tdbio.h \ + delkey.c \ + keygen.c \ + pipemode.c \ + helptext.c \ + keyserver.c \ + keyserver-internal.h \ + photoid.c photoid.h \ + exec.c exec.h + +gpgv_SOURCES = gpgv.c \ + $(common_source) \ + verify.c + +LDADD = $(needed_libs) $(other_libs) @ZLIBS@ @W32LIBS@ @LIBREADLINE@ +gpg_LDADD = $(LDADD) @DLLIBS@ @NETLIBS@ @LIBUSB@ +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .o .obj +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh \ + && exit 0; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu g10/Makefile'; \ + cd $(top_srcdir) && \ + $(AUTOMAKE) --gnu g10/Makefile +.PRECIOUS: Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +install-binPROGRAMS: $(bin_PROGRAMS) + @$(NORMAL_INSTALL) + test -z "$(bindir)" || $(mkdir_p) "$(DESTDIR)$(bindir)" + @list='$(bin_PROGRAMS)'; for p in $$list; do \ + p1=`echo $$p|sed 's/$(EXEEXT)$$//'`; \ + if test -f $$p \ + ; then \ + f=`echo "$$p1" | sed 's,^.*/,,;$(transform);s/$$/$(EXEEXT)/'`; \ + echo " $(INSTALL_PROGRAM_ENV) $(binPROGRAMS_INSTALL) '$$p' '$(DESTDIR)$(bindir)/$$f'"; \ + $(INSTALL_PROGRAM_ENV) $(binPROGRAMS_INSTALL) "$$p" "$(DESTDIR)$(bindir)/$$f" || exit 1; \ + else :; fi; \ + done + +uninstall-binPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(bin_PROGRAMS)'; for p in $$list; do \ + f=`echo "$$p" | sed 's,^.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/'`; \ + echo " rm -f '$(DESTDIR)$(bindir)/$$f'"; \ + rm -f "$(DESTDIR)$(bindir)/$$f"; \ + done + +clean-binPROGRAMS: + -test -z "$(bin_PROGRAMS)" || rm -f $(bin_PROGRAMS) + +installcheck-binPROGRAMS: $(bin_PROGRAMS) + bad=0; pid=$$$$; list="$(bin_PROGRAMS)"; for p in $$list; do \ + case ' $(AM_INSTALLCHECK_STD_OPTIONS_EXEMPT) ' in \ + *" $$p "* | *" $(srcdir)/$$p "*) continue;; \ + esac; \ + f=`echo "$$p" | \ + sed 's,^.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/'`; \ + for opt in --help --version; do \ + if "$(DESTDIR)$(bindir)/$$f" $$opt >c$${pid}_.out \ + 2>c$${pid}_.err </dev/null \ + && test -n "`cat c$${pid}_.out`" \ + && test -z "`cat c$${pid}_.err`"; then :; \ + else echo "$$f does not support $$opt" 1>&2; bad=1; fi; \ + done; \ + done; rm -f c$${pid}_.???; exit $$bad +gpg$(EXEEXT): $(gpg_OBJECTS) $(gpg_DEPENDENCIES) + @rm -f gpg$(EXEEXT) + $(LINK) $(gpg_LDFLAGS) $(gpg_OBJECTS) $(gpg_LDADD) $(LIBS) +gpgv$(EXEEXT): $(gpgv_OBJECTS) $(gpgv_DEPENDENCIES) + @rm -f gpgv$(EXEEXT) + $(LINK) $(gpgv_LDFLAGS) $(gpgv_OBJECTS) $(gpgv_LDADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/apdu.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/app-openpgp.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/armor.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/build-packet.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/card-util.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cardglue.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ccid-driver.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cipher.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/compress-bz2.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/compress.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dearmor.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/decrypt.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/delkey.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/encode.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/encr-data.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/exec.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/export.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/free-packet.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/getkey.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gpg.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gpgv.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/helptext.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/import.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/iso7816.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/kbnode.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/keydb.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/keyedit.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/keygen.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/keyid.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/keylist.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/keyring.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/keyserver.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mainproc.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mdfilter.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/misc.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/openfile.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/parse-packet.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/passphrase.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/photoid.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pipemode.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pkclist.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/plaintext.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/progress.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pubkey-enc.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/revoke.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/seckey-cert.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/seskey.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sig-check.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sign.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/signal.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/skclist.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/status.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tdbdump.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tdbio.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/textfilter.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tlv.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/trustdb.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/verify.Po@am__quote@ + +.c.o: +@am__fastdepCC_TRUE@ if $(COMPILE) -MT $@ -MD -MP -MF "$(DEPDIR)/$*.Tpo" -c -o $@ $<; \ +@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/$*.Tpo" "$(DEPDIR)/$*.Po"; else rm -f "$(DEPDIR)/$*.Tpo"; exit 1; fi +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(COMPILE) -c $< + +.c.obj: +@am__fastdepCC_TRUE@ if $(COMPILE) -MT $@ -MD -MP -MF "$(DEPDIR)/$*.Tpo" -c -o $@ `$(CYGPATH_W) '$<'`; \ +@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/$*.Tpo" "$(DEPDIR)/$*.Po"; else rm -f "$(DEPDIR)/$*.Tpo"; exit 1; fi +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(COMPILE) -c `$(CYGPATH_W) '$<'` +uninstall-info-am: + +ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES) + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) ' { files[$$0] = 1; } \ + END { for (i in files) print i; }'`; \ + mkid -fID $$unique +tags: TAGS + +TAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + tags=; \ + here=`pwd`; \ + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) ' { files[$$0] = 1; } \ + END { for (i in files) print i; }'`; \ + if test -z "$(ETAGS_ARGS)$$tags$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$tags $$unique; \ + fi +ctags: CTAGS +CTAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + tags=; \ + here=`pwd`; \ + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) ' { files[$$0] = 1; } \ + END { for (i in files) print i; }'`; \ + test -z "$(CTAGS_ARGS)$$tags$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$tags $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && cd $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) $$here + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's|.|.|g'`; \ + list='$(DISTFILES)'; for file in $$list; do \ + case $$file in \ + $(srcdir)/*) file=`echo "$$file" | sed "s|^$$srcdirstrip/||"`;; \ + $(top_srcdir)/*) file=`echo "$$file" | sed "s|^$$topsrcdirstrip/|$(top_builddir)/|"`;; \ + esac; \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + dir=`echo "$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test "$$dir" != "$$file" && test "$$dir" != "."; then \ + dir="/$$dir"; \ + $(mkdir_p) "$(distdir)$$dir"; \ + else \ + dir=''; \ + fi; \ + if test -d $$d/$$file; then \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -pR $(srcdir)/$$file $(distdir)$$dir || exit 1; \ + fi; \ + cp -pR $$d/$$file $(distdir)$$dir || exit 1; \ + else \ + test -f $(distdir)/$$file \ + || cp -p $$d/$$file $(distdir)/$$file \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(PROGRAMS) +installdirs: + for dir in "$(DESTDIR)$(bindir)"; do \ + test -z "$$dir" || $(mkdir_p) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + `test -z '$(STRIP)' || \ + echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-binPROGRAMS clean-generic mostlyclean-am + +distclean: distclean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +info: info-am + +info-am: + +install-data-am: install-data-local + +install-exec-am: install-binPROGRAMS + +install-info: install-info-am + +install-man: + +installcheck-am: installcheck-binPROGRAMS + +maintainer-clean: maintainer-clean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-binPROGRAMS uninstall-info-am + +.PHONY: CTAGS GTAGS all all-am check check-am clean clean-binPROGRAMS \ + clean-generic ctags distclean distclean-compile \ + distclean-generic distclean-tags distdir dvi dvi-am html \ + html-am info info-am install install-am install-binPROGRAMS \ + install-data install-data-am install-data-local install-exec \ + install-exec-am install-info install-info-am install-man \ + install-strip installcheck installcheck-am \ + installcheck-binPROGRAMS installdirs maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-compile \ + mostlyclean-generic pdf pdf-am ps ps-am tags uninstall \ + uninstall-am uninstall-binPROGRAMS uninstall-info-am + + +$(PROGRAMS): $(needed_libs) + +install-data-local: + $(mkinstalldirs) $(DESTDIR)$(pkgdatadir) + $(INSTALL_DATA) $(srcdir)/options.skel \ + $(DESTDIR)$(pkgdatadir)/options.skel + @set -e;\ + if test -f $(DESTDIR)$(bindir)/gpgm ; then \ + echo "removing obsolete gpgm binary" ; \ + rm $(DESTDIR)$(bindir)/gpgm ; \ + fi + +# Helper to update some source files. +update-source-from-gnupg-2: + @set -e; \ + if test -d ../../gnupg-1.9/scd; then dir="../../gnupg-1.9"; \ + elif test -d ../../gnupg/scd; then dir="../../gnupg"; \ + else exit 1; \ + fi; \ + for i in $(card_support_source_scd); do \ + cp $$dir/scd/$$i $$i; echo $$i; \ + done ;\ + for i in $(card_support_source_gpg); do \ + cp $$dir/g10/$$i $$i; echo $$i; \ + done ; \ + echo "Please remember to update the ChangeLog accordingly!" +# 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/g10/apdu.c b/g10/apdu.c new file mode 100644 index 0000000..5df9ebf --- /dev/null +++ b/g10/apdu.c @@ -0,0 +1,3013 @@ +/* apdu.c - ISO 7816 APDU functions and low level I/O + * Copyright (C) 2003, 2004 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * $Id: apdu.c 4034 2006-03-05 15:13:18Z wk $ + */ + +/* NOTE: This module is also used by other software, thus the use of + the macro USE_GNU_PTH 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 <assert.h> +#include <signal.h> +#ifdef USE_GNU_PTH +# include <pth.h> +# include <unistd.h> +# include <fcntl.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 "options.h" +#include "errors.h" +#include "memory.h" +#include "util.h" +#include "i18n.h" +#include "cardglue.h" +#else /* GNUPG_MAJOR_VERSION != 1 */ +#include "scdaemon.h" +#endif /* GNUPG_MAJOR_VERSION != 1 */ + +#include "apdu.h" +#include "dynload.h" +#include "ccid-driver.h" + + +/* Due to conflicting use of threading libraries we usually can't link + against libpcsclite. Instead we use a wrapper program. */ +#ifdef USE_GNU_PTH +#if !defined(HAVE_W32_SYSTEM) && !defined(__CYGWIN__) +#define NEED_PCSC_WRAPPER 1 +#endif +#endif + + +#define MAX_READER 4 /* Number of readers we support concurrently. */ + + +#if defined(_WIN32) || defined(__CYGWIN__) +#define DLSTDCALL __stdcall +#else +#define DLSTDCALL +#endif + +#ifdef _POSIX_OPEN_MAX +#define MAX_OPEN_FDS _POSIX_OPEN_MAX +#else +#define MAX_OPEN_FDS 20 +#endif + +/* Helper to pass patrameters related to keypad based operations. */ +struct pininfo_s +{ + int mode; + int minlen; + int maxlen; + int padlen; +}; + +/* 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 intialized to the various backends. */ + int (*close_reader)(int); + int (*shutdown_reader)(int); + int (*reset_reader)(int); + int (*get_status_reader)(int, unsigned int *); + int (*send_apdu_reader)(int,unsigned char *,size_t, + unsigned char *, size_t *, struct pininfo_s *); + int (*check_keypad)(int, int, int, int, int, int); + void (*dump_status_reader)(int); + + struct { + ccid_driver_t handle; + } ccid; + struct { + unsigned long context; + unsigned long card; + unsigned long protocol; +#ifdef NEED_PCSC_WRAPPER + int req_fd; + int rsp_fd; + pid_t pid; +#endif /*NEED_PCSC_WRAPPER*/ + } 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. */ + int last_status; + int status; + int is_t0; /* True if we know that we are running T=0. */ + 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. */ + unsigned int change_counter; +#ifdef USE_GNU_PTH + int lock_initialized; + pth_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]; + + +/* ct API function pointer. */ +static char (* DLSTDCALL CT_init) (unsigned short ctn, unsigned short Pn); +static char (* DLSTDCALL CT_data) (unsigned short ctn, unsigned char *dad, + unsigned char *sad, unsigned short lc, + unsigned char *cmd, unsigned short *lr, + unsigned char *rsp); +static char (* DLSTDCALL CT_close) (unsigned short ctn); + +/* 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 +#define PCSC_PROTOCOL_RAW 4 + +#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 + +#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. */ + +#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. */ + +/* 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_W_REMOVED_CARD 0x80100069 + + +struct pcsc_io_request_s +{ + unsigned long protocol; + unsigned long pci_len; +}; + +typedef struct pcsc_io_request_s *pcsc_io_request_t; + +struct pcsc_readerstate_s +{ + const char *reader; + void *user_data; + unsigned long current_state; + unsigned long event_state; + unsigned long atrlen; + unsigned char atr[33]; +}; + +typedef struct pcsc_readerstate_s *pcsc_readerstate_t; + +long (* DLSTDCALL pcsc_establish_context) (unsigned long scope, + const void *reserved1, + const void *reserved2, + unsigned long *r_context); +long (* DLSTDCALL pcsc_release_context) (unsigned long context); +long (* DLSTDCALL pcsc_list_readers) (unsigned long context, + const char *groups, + char *readers, unsigned long*readerslen); +long (* DLSTDCALL pcsc_get_status_change) (unsigned long context, + unsigned long timeout, + pcsc_readerstate_t readerstates, + unsigned long nreaderstates); +long (* DLSTDCALL pcsc_connect) (unsigned long context, + const char *reader, + unsigned long share_mode, + unsigned long preferred_protocols, + unsigned long *r_card, + unsigned long *r_active_protocol); +long (* DLSTDCALL pcsc_reconnect) (unsigned long card, + unsigned long share_mode, + unsigned long preferred_protocols, + unsigned long initialization, + unsigned long *r_active_protocol); +long (* DLSTDCALL pcsc_disconnect) (unsigned long card, + unsigned long disposition); +long (* DLSTDCALL pcsc_status) (unsigned long card, + char *reader, unsigned long *readerlen, + unsigned long *r_state, + unsigned long *r_protocol, + unsigned char *atr, unsigned long *atrlen); +long (* DLSTDCALL pcsc_begin_transaction) (unsigned long card); +long (* DLSTDCALL pcsc_end_transaction) (unsigned long card); +long (* DLSTDCALL pcsc_transmit) (unsigned long card, + const pcsc_io_request_t send_pci, + const unsigned char *send_buffer, + unsigned long send_len, + pcsc_io_request_t recv_pci, + unsigned char *recv_buffer, + unsigned long *recv_len); +long (* DLSTDCALL pcsc_set_timeout) (unsigned long context, + unsigned long timeout); + + +/* Prototypes. */ +static int pcsc_get_status (int slot, unsigned int *status); + + + +/* + Helper + */ + + +/* 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. */ +static int +new_reader_slot (void) +{ + int i, reader = -1; + + for (i=0; i < MAX_READER; i++) + { + if (!reader_table[i].used && reader == -1) + reader = i; + } + if (reader == -1) + { + log_error ("new_reader_slot: out of slots\n"); + return -1; + } +#ifdef USE_GNU_PTH + if (!reader_table[reader].lock_initialized) + { + if (!pth_mutex_init (&reader_table[reader].lock)) + { + log_error ("error initializing mutex: %s\n", strerror (errno)); + return -1; + } + reader_table[reader].lock_initialized = 1; + } +#endif /*USE_GNU_PTH*/ + reader_table[reader].close_reader = NULL; + reader_table[reader].shutdown_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_keypad = NULL; + reader_table[reader].dump_status_reader = NULL; + + reader_table[reader].used = 1; + reader_table[reader].last_status = 0; + reader_table[reader].is_t0 = 1; +#ifdef NEED_PCSC_WRAPPER + reader_table[reader].pcsc.req_fd = -1; + reader_table[reader].pcsc.rsp_fd = -1; + reader_table[reader].pcsc.pid = (pid_t)(-1); +#endif + + 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].status != -1 + && 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_KEYPAD: return "no keypad"; + 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_CHV_WRONG : return "CHV wrong"; + case SW_CHV_BLOCKED : return "CHV blocked"; + case SW_USE_CONDITIONS : return "use conditions not satisfied"; + 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_BAD_P0_P1 : return "bad P0 or 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"; + } +} + + + +/* + ct API Interface + */ + +static const char * +ct_error_string (long err) +{ + switch (err) + { + case 0: return "okay"; + case -1: return "invalid data"; + case -8: return "ct error"; + case -10: return "transmission error"; + case -11: return "memory allocation error"; + case -128: return "HTSI error"; + default: return "unknown CT-API error"; + } +} + + +static void +ct_dump_reader_status (int slot) +{ + log_info ("reader slot %d: %s\n", slot, + reader_table[slot].status == 1? "Processor ICC present" : + reader_table[slot].status == 0? "Memory ICC present" : + "ICC not present" ); +} + + +/* Wait for the card in SLOT and activate it. Return a status word + error or 0 on success. */ +static int +ct_activate_card (int slot) +{ + int rc; + unsigned char dad[1], sad[1], cmd[11], buf[256]; + unsigned short buflen; + + /* Check whether card has been inserted. */ + dad[0] = 1; /* Destination address: CT. */ + sad[0] = 2; /* Source address: Host. */ + + cmd[0] = 0x20; /* Class byte. */ + cmd[1] = 0x13; /* Request status. */ + cmd[2] = 0x00; /* From kernel. */ + cmd[3] = 0x80; /* Return card's DO. */ + cmd[4] = 0x00; + + buflen = DIM(buf); + + rc = CT_data (slot, dad, sad, 5, cmd, &buflen, buf); + if (rc || buflen < 2 || buf[buflen-2] != 0x90) + { + log_error ("ct_activate_card: can't get status of reader %d: %s\n", + slot, ct_error_string (rc)); + return SW_HOST_CARD_IO_ERROR; + } + + /* Connected, now activate the card. */ + dad[0] = 1; /* Destination address: CT. */ + sad[0] = 2; /* Source address: Host. */ + + cmd[0] = 0x20; /* Class byte. */ + cmd[1] = 0x12; /* Request ICC. */ + cmd[2] = 0x01; /* From first interface. */ + cmd[3] = 0x01; /* Return card's ATR. */ + cmd[4] = 0x00; + + buflen = DIM(buf); + + rc = CT_data (slot, dad, sad, 5, cmd, &buflen, buf); + if (rc || buflen < 2 || buf[buflen-2] != 0x90) + { + log_error ("ct_activate_card(%d): activation failed: %s\n", + slot, ct_error_string (rc)); + if (!rc) + log_printhex (" received data:", buf, buflen); + return SW_HOST_CARD_IO_ERROR; + } + + /* Store the type and the ATR. */ + if (buflen - 2 > DIM (reader_table[0].atr)) + { + log_error ("ct_activate_card(%d): ATR too long\n", slot); + return SW_HOST_CARD_IO_ERROR; + } + + reader_table[slot].status = buf[buflen - 1]; + memcpy (reader_table[slot].atr, buf, buflen - 2); + reader_table[slot].atrlen = buflen - 2; + return 0; +} + + +static int +close_ct_reader (int slot) +{ + CT_close (slot); + reader_table[slot].used = 0; + return 0; +} + +static int +reset_ct_reader (int slot) +{ + /* FIXME: Check is this is sufficient do do a reset. */ + return ct_activate_card (slot); +} + + +static int +ct_get_status (int slot, unsigned int *status) +{ + *status = 1|2|4; /* FIXME */ + return 0; + + return SW_HOST_NOT_SUPPORTED; +} + +/* Actually send the APDU of length APDULEN to SLOT and return a + maximum of *BUFLEN data in BUFFER, the actual retruned size will be + set to BUFLEN. Returns: CT API error code. */ +static int +ct_send_apdu (int slot, unsigned char *apdu, size_t apdulen, + unsigned char *buffer, size_t *buflen, struct pininfo_s *pininfo) +{ + int rc; + unsigned char dad[1], sad[1]; + unsigned short ctbuflen; + + /* If we don't have an ATR, we need to reset the reader first. */ + if (!reader_table[slot].atrlen + && (rc = reset_ct_reader (slot))) + return rc; + + dad[0] = 0; /* Destination address: Card. */ + sad[0] = 2; /* Source address: Host. */ + ctbuflen = *buflen; + if (DBG_CARD_IO) + log_printhex (" CT_data:", apdu, apdulen); + rc = CT_data (slot, dad, sad, apdulen, apdu, &ctbuflen, buffer); + *buflen = ctbuflen; + + return rc? SW_HOST_CARD_IO_ERROR: 0; +} + + + +/* Open a reader and return an internal handle for it. PORT is a + non-negative value with the port number of the reader. USB readers + do have port numbers starting at 32769. */ +static int +open_ct_reader (int port) +{ + int rc, reader; + + if (port < 0 || port > 0xffff) + { + log_error ("open_ct_reader: invalid port %d requested\n", port); + return -1; + } + reader = new_reader_slot (); + if (reader == -1) + return reader; + reader_table[reader].port = port; + + rc = CT_init (reader, (unsigned short)port); + if (rc) + { + log_error ("apdu_open_ct_reader failed on port %d: %s\n", + port, ct_error_string (rc)); + reader_table[reader].used = 0; + return -1; + } + + /* Only try to activate the card. */ + rc = ct_activate_card (reader); + if (rc) + { + reader_table[reader].atrlen = 0; + rc = 0; + } + + reader_table[reader].close_reader = close_ct_reader; + reader_table[reader].reset_reader = reset_ct_reader; + reader_table[reader].get_status_reader = ct_get_status; + reader_table[reader].send_apdu_reader = ct_send_apdu; + reader_table[reader].check_keypad = NULL; + reader_table[reader].dump_status_reader = ct_dump_reader_status; + + dump_reader_status (reader); + return reader; +} + + +/* + PC/SC Interface + */ + +#ifdef NEED_PCSC_WRAPPER +static int +writen (int fd, const void *buf, size_t nbytes) +{ + size_t nleft = nbytes; + int nwritten; + +/* log_printhex (" writen:", buf, nbytes); */ + + while (nleft > 0) + { +#ifdef USE_GNU_PTH + nwritten = pth_write (fd, buf, nleft); +#else + nwritten = write (fd, buf, nleft); +#endif + if (nwritten < 0 && errno == EINTR) + continue; + if (nwritten < 0) + return -1; + nleft -= nwritten; + buf = (const char*)buf + nwritten; + } + return 0; +} + +/* Read up to BUFLEN bytes from FD and return the number of bytes + actually read in NREAD. Returns -1 on error or 0 on success. */ +static int +readn (int fd, void *buf, size_t buflen, size_t *nread) +{ + size_t nleft = buflen; + int n; +/* void *orig_buf = buf; */ + + while (nleft > 0) + { +#ifdef USE_GNU_PTH + n = pth_read (fd, buf, nleft); +#else + n = read (fd, buf, nleft); +#endif + if (n < 0 && errno == EINTR) + continue; + if (n < 0) + return -1; /* read error. */ + if (!n) + break; /* EOF */ + nleft -= n; + buf = (char*)buf + n; + } + if (nread) + *nread = buflen - nleft; + +/* log_printhex (" readn:", orig_buf, *nread); */ + + return 0; +} +#endif /*NEED_PCSC_WRAPPER*/ + +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; + 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 (ec) + { + case 0: rc = 0; break; + + case PCSC_E_CANCELLED: rc = SW_HOST_ABORTED; 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_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) +{ + 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"); +} + + +/* Send an PC/SC reset command and return a status word on error or 0 + on success. */ +static int +reset_pcsc_reader (int slot) +{ +#ifdef NEED_PCSC_WRAPPER + long err; + reader_table_t slotp; + size_t len; + int i, n; + unsigned char msgbuf[9]; + unsigned int dummy_status; + int sw = SW_HOST_CARD_IO_ERROR; + + slotp = reader_table + slot; + + if (slotp->pcsc.req_fd == -1 + || slotp->pcsc.rsp_fd == -1 + || slotp->pcsc.pid == (pid_t)(-1) ) + { + log_error ("pcsc_get_status: pcsc-wrapper not running\n"); + return sw; + } + + msgbuf[0] = 0x05; /* RESET command. */ + len = 0; + msgbuf[1] = (len >> 24); + msgbuf[2] = (len >> 16); + msgbuf[3] = (len >> 8); + msgbuf[4] = (len ); + if ( writen (slotp->pcsc.req_fd, msgbuf, 5) ) + { + log_error ("error sending PC/SC RESET request: %s\n", + strerror (errno)); + goto command_failed; + } + + /* Read the response. */ + if ((i=readn (slotp->pcsc.rsp_fd, msgbuf, 9, &len)) || len != 9) + { + log_error ("error receiving PC/SC RESET response: %s\n", + i? strerror (errno) : "premature EOF"); + goto command_failed; + } + len = (msgbuf[1] << 24) | (msgbuf[2] << 16) | (msgbuf[3] << 8 ) | msgbuf[4]; + if (msgbuf[0] != 0x81 || len < 4) + { + log_error ("invalid response header from PC/SC received\n"); + goto command_failed; + } + len -= 4; /* Already read the error code. */ + if (len > DIM (slotp->atr)) + { + log_error ("PC/SC returned a too large ATR (len=%lx)\n", + (unsigned long)len); + sw = SW_HOST_GENERAL_ERROR; + goto command_failed; + } + err = (msgbuf[5] << 24) | (msgbuf[6] << 16) | (msgbuf[7] << 8 ) | msgbuf[8]; + if (err) + { + log_error ("PC/SC RESET failed: %s (0x%lx)\n", + pcsc_error_string (err), err); + /* If the error code is no smart card, we should not considere + this a major error and close the wrapper. */ + sw = pcsc_error_to_sw (err); + if (err == PCSC_E_NO_SMARTCARD) + return sw; + goto command_failed; + } + + /* The open function may return a zero for the ATR length to + indicate that no card is present. */ + n = len; + if (n) + { + if ((i=readn (slotp->pcsc.rsp_fd, slotp->atr, n, &len)) || len != n) + { + log_error ("error receiving PC/SC RESET response: %s\n", + i? strerror (errno) : "premature EOF"); + goto command_failed; + } + } + slotp->atrlen = len; + + /* Read the status so that IS_T0 will be set. */ + pcsc_get_status (slot, &dummy_status); + + return 0; + + command_failed: + close (slotp->pcsc.req_fd); + close (slotp->pcsc.rsp_fd); + slotp->pcsc.req_fd = -1; + slotp->pcsc.rsp_fd = -1; + kill (slotp->pcsc.pid, SIGTERM); + slotp->pcsc.pid = (pid_t)(-1); + slotp->used = 0; + return sw; + +#else /* !NEED_PCSC_WRAPPER */ + long err; + char reader[250]; + unsigned long nreader, atrlen; + unsigned long card_state, card_protocol; + + if (reader_table[slot].pcsc.card) + { + 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; + } + + err = pcsc_connect (reader_table[slot].pcsc.context, + reader_table[slot].rdrname, + PCSC_SHARE_EXCLUSIVE, + PCSC_PROTOCOL_T0|PCSC_PROTOCOL_T1, + &reader_table[slot].pcsc.card, + &reader_table[slot].pcsc.protocol); + if (err) + { + log_error ("pcsc_connect failed: %s (0x%lx)\n", + pcsc_error_string (err), err); + reader_table[slot].pcsc.card = 0; + return pcsc_error_to_sw (err); + } + + + atrlen = 33; + nreader = sizeof reader - 1; + err = pcsc_status (reader_table[slot].pcsc.card, + reader, &nreader, + &card_state, &card_protocol, + reader_table[slot].atr, &atrlen); + if (err) + { + log_error ("pcsc_status failed: %s (0x%lx)\n", + pcsc_error_string (err), err); + reader_table[slot].atrlen = 0; + return pcsc_error_to_sw (err); + } + 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); + + return 0; +#endif /* !NEED_PCSC_WRAPPER */ +} + + +static int +pcsc_get_status (int slot, unsigned int *status) +{ +#ifdef NEED_PCSC_WRAPPER + long err; + reader_table_t slotp; + size_t len, full_len; + int i, n; + unsigned char msgbuf[9]; + unsigned char buffer[16]; + int sw = SW_HOST_CARD_IO_ERROR; + + slotp = reader_table + slot; + + if (slotp->pcsc.req_fd == -1 + || slotp->pcsc.rsp_fd == -1 + || slotp->pcsc.pid == (pid_t)(-1) ) + { + log_error ("pcsc_get_status: pcsc-wrapper not running\n"); + return sw; + } + + msgbuf[0] = 0x04; /* STATUS command. */ + len = 0; + msgbuf[1] = (len >> 24); + msgbuf[2] = (len >> 16); + msgbuf[3] = (len >> 8); + msgbuf[4] = (len ); + if ( writen (slotp->pcsc.req_fd, msgbuf, 5) ) + { + log_error ("error sending PC/SC STATUS request: %s\n", + strerror (errno)); + goto command_failed; + } + + /* Read the response. */ + if ((i=readn (slotp->pcsc.rsp_fd, msgbuf, 9, &len)) || len != 9) + { + log_error ("error receiving PC/SC STATUS response: %s\n", + i? strerror (errno) : "premature EOF"); + goto command_failed; + } + len = (msgbuf[1] << 24) | (msgbuf[2] << 16) | (msgbuf[3] << 8 ) | msgbuf[4]; + if (msgbuf[0] != 0x81 || len < 4) + { + log_error ("invalid response header from PC/SC received\n"); + goto command_failed; + } + len -= 4; /* Already read the error code. */ + err = (msgbuf[5] << 24) | (msgbuf[6] << 16) | (msgbuf[7] << 8 ) | msgbuf[8]; + if (err) + { + log_error ("pcsc_status failed: %s (0x%lx)\n", + pcsc_error_string (err), err); + /* This is a proper error code, so return immediately. */ + return pcsc_error_to_sw (err); + } + + full_len = len; + + /* The current version returns 3 words but we allow also for old + versions returning only 2 words. */ + n = 12 < len ? 12 : len; + if ((i=readn (slotp->pcsc.rsp_fd, buffer, n, &len)) + || (len != 8 && len != 12)) + { + log_error ("error receiving PC/SC STATUS response: %s\n", + i? strerror (errno) : "premature EOF"); + goto command_failed; + } + + slotp->is_t0 = (len == 12 && !!(buffer[11] & PCSC_PROTOCOL_T0)); + + + full_len -= len; + /* Newer versions of the wrapper might send more status bytes. + Read them. */ + while (full_len) + { + unsigned char dummybuf[128]; + + n = full_len < DIM (dummybuf) ? full_len : DIM (dummybuf); + if ((i=readn (slotp->pcsc.rsp_fd, dummybuf, n, &len)) || len != n) + { + log_error ("error receiving PC/SC TRANSMIT response: %s\n", + i? strerror (errno) : "premature EOF"); + goto command_failed; + } + full_len -= n; + } + + /* We are lucky: The wrapper already returns the data in the + required format. */ + *status = buffer[3]; + + return 0; + + command_failed: + close (slotp->pcsc.req_fd); + close (slotp->pcsc.rsp_fd); + slotp->pcsc.req_fd = -1; + slotp->pcsc.rsp_fd = -1; + kill (slotp->pcsc.pid, SIGTERM); + slotp->pcsc.pid = (pid_t)(-1); + slotp->used = 0; + return sw; + +#else /*!NEED_PCSC_WRAPPER*/ + + long err; + struct pcsc_readerstate_s rdrstates[1]; + + memset (rdrstates, 0, sizeof *rdrstates); + rdrstates[0].reader = reader_table[slot].rdrname; + rdrstates[0].current_state = PCSC_STATE_UNAWARE; + err = pcsc_get_status_change (reader_table[slot].pcsc.context, + 0, + rdrstates, 1); + if (err == PCSC_E_TIMEOUT) + err = 0; /* Timeout is no error 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); + } + + + /* 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)? " unuse":"", */ + /* (rdrstates[0].event_state & PCSC_STATE_MUTE)? " mute":"" ); */ + + *status = 0; + if ( (rdrstates[0].event_state & PCSC_STATE_PRESENT) ) + *status |= 2; + if ( !(rdrstates[0].event_state & PCSC_STATE_MUTE) ) + *status |= 4; + /* 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 & 6) == 6 + && !(rdrstates[0].event_state & PCSC_STATE_INUSE) ) + *status |= 1; + + return 0; +#endif /*!NEED_PCSC_WRAPPER*/ +} + + +/* 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: CT API error code. */ +static int +pcsc_send_apdu (int slot, unsigned char *apdu, size_t apdulen, + unsigned char *buffer, size_t *buflen, + struct pininfo_s *pininfo) +{ +#ifdef NEED_PCSC_WRAPPER + long err; + reader_table_t slotp; + size_t len, full_len; + int i, n; + unsigned char msgbuf[9]; + int sw = SW_HOST_CARD_IO_ERROR; + + if (!reader_table[slot].atrlen + && (err = reset_pcsc_reader (slot))) + return err; + + if (DBG_CARD_IO) + log_printhex (" PCSC_data:", apdu, apdulen); + + slotp = reader_table + slot; + + if (slotp->pcsc.req_fd == -1 + || slotp->pcsc.rsp_fd == -1 + || slotp->pcsc.pid == (pid_t)(-1) ) + { + log_error ("pcsc_send_apdu: pcsc-wrapper not running\n"); + return sw; + } + + msgbuf[0] = 0x03; /* TRANSMIT command. */ + len = apdulen; + msgbuf[1] = (len >> 24); + msgbuf[2] = (len >> 16); + msgbuf[3] = (len >> 8); + msgbuf[4] = (len ); + if ( writen (slotp->pcsc.req_fd, msgbuf, 5) + || writen (slotp->pcsc.req_fd, apdu, len)) + { + log_error ("error sending PC/SC TRANSMIT request: %s\n", + strerror (errno)); + goto command_failed; + } + + /* Read the response. */ + if ((i=readn (slotp->pcsc.rsp_fd, msgbuf, 9, &len)) || len != 9) + { + log_error ("error receiving PC/SC TRANSMIT response: %s\n", + i? strerror (errno) : "premature EOF"); + goto command_failed; + } + len = (msgbuf[1] << 24) | (msgbuf[2] << 16) | (msgbuf[3] << 8 ) | msgbuf[4]; + if (msgbuf[0] != 0x81 || len < 4) + { + log_error ("invalid response header from PC/SC received\n"); + goto command_failed; + } + len -= 4; /* Already read the error code. */ + err = (msgbuf[5] << 24) | (msgbuf[6] << 16) | (msgbuf[7] << 8 ) | msgbuf[8]; + if (err) + { + log_error ("pcsc_transmit failed: %s (0x%lx)\n", + pcsc_error_string (err), err); + return pcsc_error_to_sw (err); + } + + full_len = len; + + n = *buflen < len ? *buflen : len; + if ((i=readn (slotp->pcsc.rsp_fd, buffer, n, &len)) || len != n) + { + log_error ("error receiving PC/SC TRANSMIT response: %s\n", + i? strerror (errno) : "premature EOF"); + goto command_failed; + } + *buflen = n; + + full_len -= len; + if (full_len) + { + log_error ("pcsc_send_apdu: provided buffer too short - truncated\n"); + err = SW_HOST_INV_VALUE; + } + /* We need to read any rest of the response, to keep the + protocol runnng. */ + while (full_len) + { + unsigned char dummybuf[128]; + + n = full_len < DIM (dummybuf) ? full_len : DIM (dummybuf); + if ((i=readn (slotp->pcsc.rsp_fd, dummybuf, n, &len)) || len != n) + { + log_error ("error receiving PC/SC TRANSMIT response: %s\n", + i? strerror (errno) : "premature EOF"); + goto command_failed; + } + full_len -= n; + } + + return err; + + command_failed: + close (slotp->pcsc.req_fd); + close (slotp->pcsc.rsp_fd); + slotp->pcsc.req_fd = -1; + slotp->pcsc.rsp_fd = -1; + kill (slotp->pcsc.pid, SIGTERM); + slotp->pcsc.pid = (pid_t)(-1); + slotp->used = 0; + return sw; + +#else /*!NEED_PCSC_WRAPPER*/ + + long err; + struct pcsc_io_request_s send_pci; + unsigned long recv_len; + + if (!reader_table[slot].atrlen + && (err = reset_pcsc_reader (slot))) + return err; + + if (DBG_CARD_IO) + log_printhex (" PCSC_data:", apdu, apdulen); + + 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); + + return pcsc_error_to_sw (err); +#endif /*!NEED_PCSC_WRAPPER*/ +} + + +static int +close_pcsc_reader (int slot) +{ +#ifdef NEED_PCSC_WRAPPER + long err; + reader_table_t slotp; + size_t len; + int i; + unsigned char msgbuf[9]; + + slotp = reader_table + slot; + + if (slotp->pcsc.req_fd == -1 + || slotp->pcsc.rsp_fd == -1 + || slotp->pcsc.pid == (pid_t)(-1) ) + { + log_error ("close_pcsc_reader: pcsc-wrapper not running\n"); + return 0; + } + + msgbuf[0] = 0x02; /* CLOSE command. */ + len = 0; + msgbuf[1] = (len >> 24); + msgbuf[2] = (len >> 16); + msgbuf[3] = (len >> 8); + msgbuf[4] = (len ); + if ( writen (slotp->pcsc.req_fd, msgbuf, 5) ) + { + log_error ("error sending PC/SC CLOSE request: %s\n", + strerror (errno)); + goto command_failed; + } + + /* Read the response. */ + if ((i=readn (slotp->pcsc.rsp_fd, msgbuf, 9, &len)) || len != 9) + { + log_error ("error receiving PC/SC CLOSE response: %s\n", + i? strerror (errno) : "premature EOF"); + goto command_failed; + } + len = (msgbuf[1] << 24) | (msgbuf[2] << 16) | (msgbuf[3] << 8 ) | msgbuf[4]; + if (msgbuf[0] != 0x81 || len < 4) + { + log_error ("invalid response header from PC/SC received\n"); + goto command_failed; + } + len -= 4; /* Already read the error code. */ + err = (msgbuf[5] << 24) | (msgbuf[6] << 16) | (msgbuf[7] << 8 ) | msgbuf[8]; + if (err) + log_error ("pcsc_close failed: %s (0x%lx)\n", + pcsc_error_string (err), err); + + /* We will close the wrapper in any case - errors are merely + informational. */ + + command_failed: + close (slotp->pcsc.req_fd); + close (slotp->pcsc.rsp_fd); + slotp->pcsc.req_fd = -1; + slotp->pcsc.rsp_fd = -1; + kill (slotp->pcsc.pid, SIGTERM); + slotp->pcsc.pid = (pid_t)(-1); + slotp->used = 0; + return 0; + +#else /*!NEED_PCSC_WRAPPER*/ + + pcsc_release_context (reader_table[slot].pcsc.context); + xfree (reader_table[slot].rdrname); + reader_table[slot].rdrname = NULL; + reader_table[slot].used = 0; + return 0; +#endif /*!NEED_PCSC_WRAPPER*/ +} + +/* Note: It is a pitty that we can't return proper error codes. */ +static int +open_pcsc_reader (const char *portstr) +{ +#ifdef NEED_PCSC_WRAPPER +/* Open the PC/SC reader using the pcsc_wrapper program. This is + needed to cope with different thread models and other peculiarities + of libpcsclite. */ + int slot; + reader_table_t slotp; + int fd, rp[2], wp[2]; + int n, i; + pid_t pid; + size_t len; + unsigned char msgbuf[9]; + int err; + unsigned int dummy_status; + int sw = SW_HOST_CARD_IO_ERROR; + + slot = new_reader_slot (); + if (slot == -1) + return -1; + slotp = reader_table + slot; + + /* Fire up the pcsc wrapper. We don't use any fork/exec code from + the common directy but implement it direclty so that this file + may still be source copied. */ + + if (pipe (rp) == -1) + { + log_error ("error creating a pipe: %s\n", strerror (errno)); + slotp->used = 0; + return -1; + } + if (pipe (wp) == -1) + { + log_error ("error creating a pipe: %s\n", strerror (errno)); + close (rp[0]); + close (rp[1]); + slotp->used = 0; + return -1; + } + + pid = fork (); + if (pid == -1) + { + log_error ("error forking process: %s\n", strerror (errno)); + close (rp[0]); + close (rp[1]); + close (wp[0]); + close (wp[1]); + slotp->used = 0; + return -1; + } + slotp->pcsc.pid = pid; + + if (!pid) + { /* + === Child === + */ + + /* Double fork. */ + pid = fork (); + if (pid == -1) + _exit (31); + if (pid) + _exit (0); /* Immediate exit this parent, so that the child + gets cleaned up by the init process. */ + + /* Connect our pipes. */ + if (wp[0] != 0 && dup2 (wp[0], 0) == -1) + log_fatal ("dup2 stdin failed: %s\n", strerror (errno)); + if (rp[1] != 1 && dup2 (rp[1], 1) == -1) + log_fatal ("dup2 stdout failed: %s\n", strerror (errno)); + + /* Send stderr to the bit bucket. */ + fd = open ("/dev/null", O_WRONLY); + if (fd == -1) + log_fatal ("can't open `/dev/null': %s", strerror (errno)); + if (fd != 2 && dup2 (fd, 2) == -1) + log_fatal ("dup2 stderr failed: %s\n", strerror (errno)); + + /* Close all other files. */ + n = sysconf (_SC_OPEN_MAX); + if (n < 0) + n = MAX_OPEN_FDS; + for (i=3; i < n; i++) + close(i); + errno = 0; + + execl (GNUPG_LIBDIR "/pcsc-wrapper", + "pcsc-wrapper", + "--", + "1", /* API version */ + opt.pcsc_driver, /* Name of the PC/SC library. */ + NULL); + _exit (31); + } + + /* + === Parent === + */ + close (wp[0]); + close (rp[1]); + slotp->pcsc.req_fd = wp[1]; + slotp->pcsc.rsp_fd = rp[0]; + + /* Wait for the intermediate child to terminate. */ +#ifdef USE_GNU_PTH +#define WAIT pth_waitpid +#else +#define WAIT waitpid +#endif + while ( (i=WAIT (pid, NULL, 0)) == -1 && errno == EINTR) + ; +#undef X + + /* Now send the open request. */ + msgbuf[0] = 0x01; /* OPEN command. */ + len = portstr? strlen (portstr):0; + msgbuf[1] = (len >> 24); + msgbuf[2] = (len >> 16); + msgbuf[3] = (len >> 8); + msgbuf[4] = (len ); + if ( writen (slotp->pcsc.req_fd, msgbuf, 5) + || (portstr && writen (slotp->pcsc.req_fd, portstr, len))) + { + log_error ("error sending PC/SC OPEN request: %s\n", + strerror (errno)); + goto command_failed; + } + /* Read the response. */ + if ((i=readn (slotp->pcsc.rsp_fd, msgbuf, 9, &len)) || len != 9) + { + log_error ("error receiving PC/SC OPEN response: %s\n", + i? strerror (errno) : "premature EOF"); + goto command_failed; + } + len = (msgbuf[1] << 24) | (msgbuf[2] << 16) | (msgbuf[3] << 8 ) | msgbuf[4]; + if (msgbuf[0] != 0x81 || len < 4) + { + log_error ("invalid response header from PC/SC received\n"); + goto command_failed; + } + len -= 4; /* Already read the error code. */ + if (len > DIM (slotp->atr)) + { + log_error ("PC/SC returned a too large ATR (len=%lx)\n", + (unsigned long)len); + goto command_failed; + } + err = (msgbuf[5] << 24) | (msgbuf[6] << 16) | (msgbuf[7] << 8 ) | msgbuf[8]; + if (err) + { + log_error ("PC/SC OPEN failed: %s\n", pcsc_error_string (err)); + sw = pcsc_error_to_sw (err); + goto command_failed; + } + + slotp->last_status = 0; + + /* The open request may return a zero for the ATR length to + indicate that no card is present. */ + n = len; + if (n) + { + if ((i=readn (slotp->pcsc.rsp_fd, slotp->atr, n, &len)) || len != n) + { + log_error ("error receiving PC/SC OPEN response: %s\n", + i? strerror (errno) : "premature EOF"); + goto command_failed; + } + /* If we got to here we know that a card is present + and usable. Thus remember this. */ + slotp->last_status = (1|2|4| 0x8000); + } + slotp->atrlen = len; + + 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].check_keypad = NULL; + reader_table[slot].dump_status_reader = dump_pcsc_reader_status; + + /* Read the status so that IS_T0 will be set. */ + pcsc_get_status (slot, &dummy_status); + + dump_reader_status (slot); + return slot; + + command_failed: + close (slotp->pcsc.req_fd); + close (slotp->pcsc.rsp_fd); + slotp->pcsc.req_fd = -1; + slotp->pcsc.rsp_fd = -1; + kill (slotp->pcsc.pid, SIGTERM); + slotp->pcsc.pid = (pid_t)(-1); + slotp->used = 0; + /* There is no way to return SW. */ + return -1; + +#else /*!NEED_PCSC_WRAPPER */ + long err; + int slot; + char *list = NULL; + unsigned long nreader, listlen, atrlen; + char *p; + unsigned long card_state, card_protocol; + + slot = new_reader_slot (); + if (slot == -1) + return -1; + + err = pcsc_establish_context (PCSC_SCOPE_SYSTEM, NULL, NULL, + &reader_table[slot].pcsc.context); + if (err) + { + log_error ("pcsc_establish_context failed: %s (0x%lx)\n", + pcsc_error_string (err), err); + reader_table[slot].used = 0; + return -1; + } + + err = pcsc_list_readers (reader_table[slot].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"); + pcsc_release_context (reader_table[slot].pcsc.context); + reader_table[slot].used = 0; + return -1 /*SW_HOST_OUT_OF_CORE*/; + } + err = pcsc_list_readers (reader_table[slot].pcsc.context, + NULL, list, &nreader); + } + if (err) + { + log_error ("pcsc_list_readers failed: %s (0x%lx)\n", + pcsc_error_string (err), err); + pcsc_release_context (reader_table[slot].pcsc.context); + reader_table[slot].used = 0; + xfree (list); + return -1 /*pcsc_error_to_sw (err)*/; + } + + listlen = nreader; + p = list; + while (nreader) + { + if (!*p && !p[1]) + break; + if (*p) + log_info ("detected reader `%s'\n", p); + if (nreader < (strlen (p)+1)) + { + log_error ("invalid response from pcsc_list_readers\n"); + break; + } + nreader -= strlen (p)+1; + p += strlen (p) + 1; + } + + reader_table[slot].rdrname = xtrymalloc (strlen (portstr? portstr : list)+1); + if (!reader_table[slot].rdrname) + { + log_error ("error allocating memory for reader name\n"); + pcsc_release_context (reader_table[slot].pcsc.context); + reader_table[slot].used = 0; + return -1 /*SW_HOST_OUT_OF_CORE*/; + } + strcpy (reader_table[slot].rdrname, portstr? portstr : list); + xfree (list); + list = NULL; + + err = pcsc_connect (reader_table[slot].pcsc.context, + reader_table[slot].rdrname, + PCSC_SHARE_EXCLUSIVE, + PCSC_PROTOCOL_T0|PCSC_PROTOCOL_T1, + &reader_table[slot].pcsc.card, + &reader_table[slot].pcsc.protocol); + if (err == PCSC_E_NO_SMARTCARD) + reader_table[slot].pcsc.card = 0; + else if (err) + { + log_error ("pcsc_connect failed: %s (0x%lx)\n", + pcsc_error_string (err), err); + pcsc_release_context (reader_table[slot].pcsc.context); + xfree (reader_table[slot].rdrname); + reader_table[slot].rdrname = NULL; + reader_table[slot].used = 0; + return -1 /*pcsc_error_to_sw (err)*/; + } + + reader_table[slot].atrlen = 0; + reader_table[slot].last_status = 0; + if (!err) + { + char reader[250]; + unsigned long readerlen; + + atrlen = 32; + 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, 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; + /* If we got to here we know that a card is present + and usable. Thus remember this. */ + reader_table[slot].last_status = (1|2|4| 0x8000); + reader_table[slot].is_t0 = !!(card_protocol & PCSC_PROTOCOL_T0); + } + } + + 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].check_keypad = NULL; + reader_table[slot].dump_status_reader = dump_pcsc_reader_status; + +/* log_debug ("state from pcsc_status: 0x%lx\n", card_state); */ +/* log_debug ("protocol from pcsc_status: 0x%lx\n", card_protocol); */ + + dump_reader_status (slot); + return slot; +#endif /*!NEED_PCSC_WRAPPER */ +} + + + + +#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); + reader_table[slot].used = 0; + return 0; +} + + +static int +shutdown_ccid_reader (int slot) +{ + ccid_shutdown_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. */ + assert (sizeof slotp->atr >= sizeof atr); + slotp->atrlen = atrlen; + memcpy (slotp->atr, atr, atrlen); + dump_reader_status (slot); + return 0; +} + + +static int +get_status_ccid (int slot, unsigned int *status) +{ + int rc; + int bits; + + rc = ccid_slot_status (reader_table[slot].ccid.handle, &bits); + if (rc) + return -1; + + if (bits == 0) + *status = 1|2|4; + else if (bits == 1) + *status = 2; + 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, + struct pininfo_s *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_data:", apdu, apdulen); + + maxbuflen = *buflen; + if (pininfo) + err = ccid_transceive_secure (reader_table[slot].ccid.handle, + apdu, apdulen, + pininfo->mode, + pininfo->minlen, + pininfo->maxlen, + pininfo->padlen, + 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 keypad. Return 0 on success. For a description of the pin + parameters, see ccid-driver.c */ +static int +check_ccid_keypad (int slot, int command, int pin_mode, + int pinlen_min, int pinlen_max, int pin_padlen) +{ + unsigned char apdu[] = { 0, 0, 0, 0x81 }; + + apdu[1] = command; + return ccid_transceive_secure (reader_table[slot].ccid.handle, + apdu, sizeof apdu, + pin_mode, pinlen_min, pinlen_max, pin_padlen, + NULL, 0, NULL); +} + + +/* Open the reader and try to read an ATR. */ +static int +open_ccid_reader (const char *portstr) +{ + int err; + int slot; + reader_table_t slotp; + + slot = new_reader_slot (); + if (slot == -1) + return -1; + slotp = reader_table + slot; + + err = ccid_open_reader (&slotp->ccid.handle, portstr); + if (err) + { + slotp->used = 0; + return -1; + } + + err = ccid_get_atr (slotp->ccid.handle, + slotp->atr, sizeof slotp->atr, &slotp->atrlen); + if (err) + { + slotp->atrlen = 0; + err = 0; + } + else + { + /* If we got to here we know that a card is present + and usable. Thus remember this. */ + reader_table[slot].last_status = (1|2|4| 0x8000); + } + + reader_table[slot].close_reader = close_ccid_reader; + reader_table[slot].shutdown_reader = shutdown_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_keypad = check_ccid_keypad; + reader_table[slot].dump_status_reader = dump_ccid_reader_status; + + dump_reader_status (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); + reader_table[slot].used = 0; + 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 err; + reader_table_t slotp; + rapdu_msg_t msg = NULL; + int oldslot; + + 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, + struct pininfo_s *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_data:", apdu, apdulen); + + 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; + 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_keypad = NULL; + reader_table[slot].dump_status_reader = NULL; + + dump_reader_status (slot); + rapdu_msg_release (msg); + return slot; + + failure: + rapdu_msg_release (msg); + rapdu_release (slotp->rapdu.handle); + slotp->used = 0; + return -1; +} + +#endif /*USE_G10CODE_RAPDU*/ + + + +/* + Driver Access + */ + + +static int +lock_slot (int slot) +{ +#ifdef USE_GNU_PTH + if (!pth_mutex_acquire (&reader_table[slot].lock, 0, NULL)) + { + log_error ("failed to acquire apdu lock: %s\n", strerror (errno)); + return SW_HOST_LOCKING_FAILED; + } +#endif /*USE_GNU_PTH*/ + return 0; +} + +static int +trylock_slot (int slot) +{ +#ifdef USE_GNU_PTH + if (!pth_mutex_acquire (&reader_table[slot].lock, TRUE, NULL)) + { + if (errno == EBUSY) + return SW_HOST_BUSY; + log_error ("failed to acquire apdu lock: %s\n", strerror (errno)); + return SW_HOST_LOCKING_FAILED; + } +#endif /*USE_GNU_PTH*/ + return 0; +} + +static void +unlock_slot (int slot) +{ +#ifdef USE_GNU_PTH + if (!pth_mutex_release (&reader_table[slot].lock)) + log_error ("failed to release apdu lock: %s\n", strerror (errno)); +#endif /*USE_GNU_PTH*/ +} + + +/* Open the reader and return an internal slot number or -1 on + error. If PORTSTR is NULL we default to a suitable port (for ctAPI: + the first USB reader. For PC/SC the first listed reader). */ +int +apdu_open_reader (const char *portstr) +{ + static int pcsc_api_loaded, ct_api_loaded; + +#ifdef HAVE_LIBUSB + if (!opt.disable_ccid) + { + int slot, i; + const char *s; + + slot = open_ccid_reader (portstr); + if (slot != -1) + return slot; /* got one */ + + /* If a CCID reader specification has been given, the user does + not want a fallback to other drivers. */ + if (portstr) + for (s=portstr, i=0; *s; s++) + if (*s == ':' && (++i == 3)) + return -1; + } + +#endif /* HAVE_LIBUSB */ + + if (opt.ctapi_driver && *opt.ctapi_driver) + { + int port = portstr? atoi (portstr) : 32768; + + if (!ct_api_loaded) + { + void *handle; + + handle = dlopen (opt.ctapi_driver, RTLD_LAZY); + if (!handle) + { + log_error ("apdu_open_reader: failed to open driver: %s\n", + dlerror ()); + return -1; + } + CT_init = dlsym (handle, "CT_init"); + CT_data = dlsym (handle, "CT_data"); + CT_close = dlsym (handle, "CT_close"); + if (!CT_init || !CT_data || !CT_close) + { + log_error ("apdu_open_reader: invalid CT-API driver\n"); + dlclose (handle); + return -1; + } + ct_api_loaded = 1; + } + return open_ct_reader (port); + } + + + /* No ctAPI configured, so lets try the PC/SC API */ + if (!pcsc_api_loaded) + { +#ifndef NEED_PCSC_WRAPPER + void *handle; + + handle = dlopen (opt.pcsc_driver, RTLD_LAZY); + if (!handle) + { + log_error ("apdu_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_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"); + + 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_set_timeout */) + { + /* Note that set_timeout is currently not used and also not + available under Windows. */ + log_error ("apdu_open_reader: invalid PC/SC driver " + "(%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 ); + dlclose (handle); + return -1; + } +#endif /*!NEED_PCSC_WRAPPER*/ + pcsc_api_loaded = 1; + } + + return open_pcsc_reader (portstr); +} + + +/* 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 availabe 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 +#ifdef _WIN32 + errno = ENOENT; +#else + errno = ENOSYS; +#endif + return -1; +#endif +} + + +int +apdu_close_reader (int slot) +{ + if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used ) + return SW_HOST_NO_DRIVER; + if (reader_table[slot].close_reader) + return reader_table[slot].close_reader (slot); + return SW_HOST_NOT_SUPPORTED; +} + +/* Shutdown a reader; that is basically the same as a close but keeps + the handle ready for later use. A apdu_reset_header should be used + to get it active again. */ +int +apdu_shutdown_reader (int slot) +{ + if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used ) + return SW_HOST_NO_DRIVER; + if (reader_table[slot].shutdown_reader) + return reader_table[slot].shutdown_reader (slot); + return SW_HOST_NOT_SUPPORTED; +} + +/* 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; +} + +/* Do a reset for the card in reader at SLOT. */ +int +apdu_reset (int slot) +{ + int sw; + + if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used ) + return SW_HOST_NO_DRIVER; + + if ((sw = lock_slot (slot))) + return sw; + + reader_table[slot].last_status = 0; + if (reader_table[slot].reset_reader) + sw = reader_table[slot].reset_reader (slot); + + if (!sw) + { + /* If we got to here we know that a card is present + and usable. Thus remember this. */ + reader_table[slot].last_status = (1|2|4| 0x8000); + } + + unlock_slot (slot); + return sw; +} + + +/* Activate a card if it has not yet been done. This is a kind of + reset-if-required. It is useful to test for presence of a card + before issuing a bunch of apdu commands. It does not wait on a + locked card. */ +int +apdu_activate (int slot) +{ + int sw; + unsigned int s; + + if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used ) + return SW_HOST_NO_DRIVER; + + if ((sw = trylock_slot (slot))) + return sw; + + if (reader_table[slot].get_status_reader) + sw = reader_table[slot].get_status_reader (slot, &s); + + if (!sw) + { + if (!(s & 2)) /* Card not present. */ + sw = SW_HOST_NO_CARD; + else if ( ((s & 2) && !(s & 4)) + || !reader_table[slot].atrlen ) + { + /* We don't have an ATR or a card is present though inactive: + do a reset now. */ + if (reader_table[slot].reset_reader) + { + reader_table[slot].last_status = 0; + sw = reader_table[slot].reset_reader (slot); + if (!sw) + { + /* If we got to here we know that a card is present + and usable. Thus remember this. */ + reader_table[slot].last_status = (1|2|4| 0x8000); + } + } + } + } + + unlock_slot (slot); + return sw; +} + + + +unsigned char * +apdu_get_atr (int slot, size_t *atrlen) +{ + unsigned char *buf; + + if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used ) + return NULL; + + buf = xtrymalloc (reader_table[slot].atrlen); + if (!buf) + return NULL; + memcpy (buf, reader_table[slot].atr, reader_table[slot].atrlen); + *atrlen = reader_table[slot].atrlen; + return buf; +} + + + +/* 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 + + bit 0 = card present and usable + bit 1 = card present + bit 2 = card active + bit 3 = card access locked [not yet implemented] + + For must application, testing bit 0 is sufficient. + + CHANGED will receive the value of the counter tracking the number + of card insertions. This value may be used to detect a card + change. +*/ +int +apdu_get_status (int slot, int hang, + unsigned int *status, unsigned int *changed) +{ + int sw; + unsigned int s; + + 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); + + unlock_slot (slot); + + if (sw) + { + reader_table[slot].last_status = 0; + return sw; + } + + /* Keep track of changes. We use one extra bit to test whether we + have checked the status at least once. */ + if ( s != (reader_table[slot].last_status & 0x07ff) + || !reader_table[slot].last_status ) + { + reader_table[slot].change_counter++; + /* Make sure that the ATR is invalid so that a reset will be by + activate. */ + reader_table[slot].atrlen = 0; + } + reader_table[slot].last_status = (s | 0x8000); + + if (status) + *status = s; + if (changed) + *changed = reader_table[slot].change_counter; + return 0; +} + + +/* Check whether the reader supports the ISO command code COMMAND on + the keypad. Return 0 on success. For a description of the pin + parameters, see ccid-driver.c */ +int +apdu_check_keypad (int slot, int command, int pin_mode, + int pinlen_min, int pinlen_max, int pin_padlen) +{ + if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used ) + return SW_HOST_NO_DRIVER; + + if (reader_table[slot].check_keypad) + return reader_table[slot].check_keypad (slot, command, + pin_mode, pinlen_min, pinlen_max, + pin_padlen); + 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, struct pininfo_s *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 trabceiver function. Parameters are described at + apdu_send_le with the exception of PININFO which indicates keypad + related operations if not NULL. */ +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, + struct pininfo_s *pininfo) +{ +#define RESULTLEN 256 + unsigned char result[RESULTLEN+10]; /* 10 extra in case of bugs in + the driver. */ + size_t resultlen; + unsigned char apdu[5+256+1]; + size_t apdulen; + int sw; + long rc; /* we need a long here due to PC/SC. */ + + 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 p0=%02X p1=%02X lc=%d le=%d\n", + class, ins, p0, p1, lc, le); + + if (lc != -1 && (lc > 255 || lc < 0)) + return SW_WRONG_LENGTH; + if (le != -1 && (le > 256 || le < 0)) + return SW_WRONG_LENGTH; + if ((!data && lc != -1) || (data && lc == -1)) + return SW_HOST_INV_VALUE; + + if ((sw = lock_slot (slot))) + return sw; + + apdulen = 0; + apdu[apdulen++] = class; + apdu[apdulen++] = ins; + apdu[apdulen++] = p0; + apdu[apdulen++] = p1; + if (lc != -1) + { + apdu[apdulen++] = lc; + memcpy (apdu+apdulen, data, lc); + apdulen += lc; + /* 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) + apdu[apdulen++] = le; /* Truncation is okay because 0 means 256. */ + assert (sizeof (apdu) >= apdulen); + /* As safeguard don't pass any garbage from the stack to the driver. */ + memset (apdu+apdulen, 0, sizeof (apdu) - apdulen); + resultlen = RESULTLEN; + rc = send_apdu (slot, apdu, apdulen, result, &resultlen, pininfo); + if (rc || resultlen < 2) + { + log_error ("apdu_send_simple(%d) failed: %s\n", + slot, apdu_strerror (rc)); + unlock_slot (slot); + 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 (" dump: ", result, resultlen); + } + + if (sw == SW_SUCCESS || sw == SW_EOF_REACHED) + { + if (retbuf) + { + *retbuf = xtrymalloc (resultlen? resultlen : 1); + if (!*retbuf) + { + unlock_slot (slot); + 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); + return SW_HOST_OUT_OF_CORE; + } + 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); + apdulen = 0; + apdu[apdulen++] = class; + apdu[apdulen++] = 0xC0; + apdu[apdulen++] = 0; + apdu[apdulen++] = 0; + apdu[apdulen++] = len; + memset (apdu+apdulen, 0, sizeof (apdu) - apdulen); + resultlen = RESULTLEN; + 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); + 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 (" dump: ", result, resultlen); + } + + 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); + 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); + + if (DBG_CARD_IO && retbuf && sw == SW_SUCCESS) + log_printhex (" dump: ", *retbuf, *retbuflen); + + return sw; +#undef RESULTLEN +} + +/* Send an APDU to the card in SLOT. The APDU is created from all + given parameters: CLASS, INS, P0, P1, LC, DATA, LE. A value of -1 + for LC won't sent this field and the data field; in this case DATA + must also be passed as NULL. The return value is the status word + or -1 for an invalid SLOT or other non card related error. If + RETBUF is not NULL, it will receive an allocated buffer with the + returned data. The length of that data will be put into + *RETBUFLEN. The caller is reponsible for releasing the buffer even + in case of errors. */ +int +apdu_send_le(int slot, int class, int ins, int p0, int p1, + int lc, const char *data, int le, + unsigned char **retbuf, size_t *retbuflen) +{ + return send_le (slot, class, ins, p0, p1, + lc, data, le, + retbuf, retbuflen, + NULL); +} + + +/* Send an APDU to the card in SLOT. The APDU is created from all + given parameters: CLASS, INS, P0, P1, LC, DATA. A value of -1 for + LC won't sent this field and the data field; in this case DATA must + also be passed as NULL. The return value is the status word or -1 + for an invalid SLOT or other non card related error. If RETBUF is + not NULL, it will receive an allocated buffer with the returned + data. The length of that data will be put into *RETBUFLEN. The + caller is reponsible for releasing the buffer even in case of + errors. */ +int +apdu_send (int slot, int class, int ins, int p0, int p1, + int lc, const char *data, unsigned char **retbuf, size_t *retbuflen) +{ + return send_le (slot, class, ins, p0, p1, lc, data, 256, + retbuf, retbuflen, NULL); +} + +/* Send an APDU to the card in SLOT. The APDU is created from all + given parameters: CLASS, INS, P0, P1, LC, DATA. A value of -1 for + LC won't sent this field and the data field; in this case DATA must + also be passed as NULL. The return value is the status word or -1 + for an invalid SLOT or other non card related error. No data will be + returned. */ +int +apdu_send_simple (int slot, int class, int ins, int p0, int p1, + int lc, const char *data) +{ + return send_le (slot, class, ins, p0, p1, lc, data, -1, NULL, NULL, NULL); +} + + +/* Same as apdu_send_simple but uses the keypad of the reader. */ +int +apdu_send_simple_kp (int slot, int class, int ins, int p0, int p1, + int lc, const char *data, + int pin_mode, + int pinlen_min, int pinlen_max, int pin_padlen) +{ + struct pininfo_s pininfo; + + pininfo.mode = pin_mode; + pininfo.minlen = pinlen_min; + pininfo.maxlen = pinlen_max; + pininfo.padlen = pin_padlen; + return send_le (slot, class, ins, p0, p1, lc, data, -1, + NULL, NULL, &pininfo); +} + + +/* This is a more generic version of the apdu sending routine. It + takes an already formatted APDU in APDUDATA or length APDUDATALEN + and returns the with the 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. The function does not return a regular status word but 0 + on success. If the slot is locked, the fucntion returns + immediately.*/ +int +apdu_send_direct (int slot, const unsigned char *apdudata, size_t apdudatalen, + int handle_more, + unsigned char **retbuf, size_t *retbuflen) +{ +#define RESULTLEN 256 + unsigned char apdu[5+256+1]; + size_t apdulen; + unsigned char result[RESULTLEN+10]; /* 10 extra in case of bugs in + the driver. */ + size_t resultlen; + 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 ((sw = trylock_slot (slot))) + return sw; + + /* We simply trunctate a too long APDU. */ + if (apdudatalen > sizeof apdu) + apdudatalen = sizeof apdu; + apdulen = apdudatalen; + memcpy (apdu, apdudata, apdudatalen); + class = apdulen? *apdu : 0; + + resultlen = RESULTLEN; + rc = send_apdu (slot, apdu, apdulen, result, &resultlen, NULL); + if (rc || resultlen < 2) + { + log_error ("apdu_send_direct(%d) failed: %s\n", + slot, apdu_strerror (rc)); + unlock_slot (slot); + 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 (" dump: ", result, resultlen); + } + + 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); + return SW_HOST_OUT_OF_CORE; + } + 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); + apdulen = 0; + apdu[apdulen++] = class; + apdu[apdulen++] = 0xC0; + apdu[apdulen++] = 0; + apdu[apdulen++] = 0; + apdu[apdulen++] = len; + memset (apdu+apdulen, 0, sizeof (apdu) - apdulen); + resultlen = RESULTLEN; + 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); + 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 (" dump: ", result, resultlen); + } + + 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); + return SW_HOST_OUT_OF_CORE; + } + p = tmp + (p - *retbuf); + *retbuf = tmp; + } + memcpy (p, result, resultlen); + p += resultlen; + } + } + else + log_info ("apdu_send_sdirect(%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); + return SW_HOST_OUT_OF_CORE; + } + *retbuflen = resultlen; + memcpy (*retbuf, result, resultlen); + } + } + + unlock_slot (slot); + + /* Append the status word - we reseved the two extra bytes while + allocating the buffer. */ + if (retbuf) + { + (*retbuf)[(*retbuflen)++] = (sw >> 8); + (*retbuf)[(*retbuflen)++] = sw; + } + + if (DBG_CARD_IO && retbuf) + log_printhex (" dump: ", *retbuf, *retbuflen); + + return 0; +#undef RESULTLEN +} diff --git a/g10/apdu.h b/g10/apdu.h new file mode 100644 index 0000000..d8e3b36 --- /dev/null +++ b/g10/apdu.h @@ -0,0 +1,123 @@ +/* apdu.h - ISO 7816 APDU functions and low level I/O + * Copyright (C) 2003 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * $Id: apdu.h 3916 2005-10-27 09:14:27Z wk $ + */ + +#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_EEPROM_FAILURE = 0x6581, + SW_WRONG_LENGTH = 0x6700, + SW_CHV_WRONG = 0x6982, + SW_CHV_BLOCKED = 0x6983, + SW_USE_CONDITIONS = 0x6985, + SW_BAD_PARAMETER = 0x6a80, /* (in the data field) */ + SW_NOT_SUPPORTED = 0x6a81, + SW_FILE_NOT_FOUND = 0x6a82, + SW_RECORD_NOT_FOUND = 0x6a83, + 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 follwoing statuswords are no real ones but used to map host + OS errors into status words. A status word is 16 bit so that + those values can't be issued by a card. */ + SW_HOST_OUT_OF_CORE = 0x10001, /* No way yet to differentiate + between errnos on a failed malloc. */ + SW_HOST_INV_VALUE = 0x10002, + SW_HOST_INCOMPLETE_CARD_RESPONSE = 0x10003, + 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_KEYPAD = 0x1000e +}; + + +#define SW_EXACT_LENGTH_P(a) (((a)&~0xff) == SW_EXACT_LENGTH) + + +/* Note , that apdu_open_reader returns no status word but -1 on error. */ +int apdu_open_reader (const char *portstr); +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_shutdown_reader (int slot); +int apdu_close_reader (int slot); +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 do return status words. */ + +int apdu_activate (int slot); +int apdu_reset (int slot); +int apdu_get_status (int slot, int hang, + unsigned int *status, unsigned int *changed); +int apdu_check_keypad (int slot, int command, int pin_mode, + int pinlen_min, int pinlen_max, int pin_padlen); +int apdu_send_simple (int slot, int class, int ins, int p0, int p1, + int lc, const char *data); +int apdu_send_simple_kp (int slot, int class, int ins, int p0, int p1, + int lc, const char *data, + int pin_mode, + int pinlen_min, int pinlen_max, int pin_padlen); +int apdu_send (int slot, int class, int ins, int p0, int p1, + int lc, const char *data, + unsigned char **retbuf, size_t *retbuflen); +int apdu_send_le (int slot, int class, int ins, int p0, int p1, + int lc, const char *data, int le, + unsigned char **retbuf, size_t *retbuflen); +int apdu_send_direct (int slot, + const unsigned char *apdudata, size_t apdudatalen, + int handle_more, + unsigned char **retbuf, size_t *retbuflen); + + +#endif /*APDU_H*/ + + + diff --git a/g10/app-common.h b/g10/app-common.h new file mode 100644 index 0000000..d517425 --- /dev/null +++ b/g10/app-common.h @@ -0,0 +1,197 @@ +/* app-common.h - Common declarations for all card applications + * Copyright (C) 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 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * $Id: app-common.h 3754 2005-05-31 10:11:01Z wk $ + */ + +#ifndef GNUPG_SCD_APP_COMMON_H +#define GNUPG_SCD_APP_COMMON_H + +#if GNUPG_MAJOR_VERSION == 1 +# ifdef ENABLE_AGENT_SUPPORT +# include "assuan.h" +# endif +#else +# include <ksba.h> +#endif + + +struct app_local_s; /* Defined by all app-*.c. */ + +struct app_ctx_s { + int initialized; /* The application has been initialied and the + function pointers may be used. Note that for + unsupported operations the particular + function pointer is set to NULL */ + int slot; /* Used reader. */ + + /* If this is used by GnuPG 1.4 we need to know the assuan context + in case we need to divert the operation to an already running + agent. This if ASSUAN_CTX is not NULL we take this as indication + that all operations are diverted to gpg-agent. */ +#if GNUPG_MAJOR_VERSION == 1 + assuan_context_t assuan_ctx; +#endif /*GNUPG_MAJOR_VERSION == 1*/ + + unsigned char *serialno; /* Serialnumber in raw form, allocated. */ + size_t serialnolen; /* Length in octets of serialnumber. */ + const char *apptype; + unsigned int card_version; + int did_chv1; + int force_chv1; /* True if the card does not cache CHV1. */ + int did_chv2; + int did_chv3; + struct app_local_s *app_local; /* Local to the application. */ + struct { + void (*deinit) (app_t app); + gpg_error_t (*learn_status) (app_t app, ctrl_t ctrl); + gpg_error_t (*readcert) (app_t app, const char *certid, + unsigned char **cert, size_t *certlen); + gpg_error_t (*readkey) (app_t app, const char *certid, + 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, 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, + 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, 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, 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 (*writekey) (app_t app, ctrl_t ctrl, + const char *certid, 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 *keynostr, unsigned int flags, + 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, int reset_mode, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg); + gpg_error_t (*check_pin) (app_t app, const char *keyidstr, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg); + } fnc; + +}; + +#if GNUPG_MAJOR_VERSION == 1 +gpg_error_t app_select_openpgp (app_t app); +gpg_error_t app_get_serial_and_stamp (app_t app, char **serial, time_t *stamp); +gpg_error_t app_openpgp_storekey (app_t app, int keyno, + unsigned char *template, size_t template_len, + time_t created_at, + const unsigned char *m, size_t mlen, + const unsigned char *e, size_t elen, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg); +#else +/*-- app-help.c --*/ +gpg_error_t app_help_get_keygrip_string (ksba_cert_t cert, char *hexkeygrip); +size_t app_help_read_length_of_cert (int slot, int fid, size_t *r_certoff); + + +/*-- app.c --*/ +gpg_error_t select_application (ctrl_t ctrl, int slot, const char *name, + app_t *r_app); +void release_application (app_t app); +gpg_error_t app_munge_serialno (app_t app); +gpg_error_t app_get_serial_and_stamp (app_t app, char **serial, time_t *stamp); +gpg_error_t app_write_learn_status (app_t app, ctrl_t ctrl); +gpg_error_t app_readcert (app_t app, const char *certid, + unsigned char **cert, size_t *certlen); +gpg_error_t app_readkey (app_t app, 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, 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, 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, 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, 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_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, unsigned int flags, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg); +gpg_error_t app_get_challenge (app_t app, size_t nbytes, + unsigned char *buffer); +gpg_error_t app_change_pin (app_t app, ctrl_t ctrl, + const char *chvnostr, int reset_mode, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg); +gpg_error_t app_check_pin (app_t app, 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); + + +#endif + + + +#endif /*GNUPG_SCD_APP_COMMON_H*/ + + + diff --git a/g10/app-openpgp.c b/g10/app-openpgp.c new file mode 100644 index 0000000..1d83963 --- /dev/null +++ b/g10/app-openpgp.c @@ -0,0 +1,2528 @@ +/* app-openpgp.c - The OpenPGP card application. + * 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 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * $Id: app-openpgp.c 4178 2006-06-28 09:37:42Z wk $ + */ + +#include <config.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <time.h> + +#if 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 "util.h" +#include "cardglue.h" +#else /* GNUPG_MAJOR_VERSION != 1 */ +#include "scdaemon.h" +#endif /* GNUPG_MAJOR_VERSION != 1 */ + +#include "i18n.h" +#include "iso7816.h" +#include "app-common.h" +#include "tlv.h" + + +static struct { + int tag; + int constructed; + int get_from; /* Constructed DO with this DO or 0 for direct access. */ + int binary; + int dont_cache; + int flush_on_error; + int get_immediate_in_v11; /* 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. */ + char *desc; +} data_objects[] = { + { 0x005E, 0, 0, 1, 0, 0, 0, "Login Data" }, + { 0x5F50, 0, 0, 0, 0, 0, 0, "URL" }, + { 0x0065, 1, 0, 1, 0, 0, 0, "Cardholder Related Data"}, + { 0x005B, 0, 0x65, 0, 0, 0, 0, "Name" }, + { 0x5F2D, 0, 0x65, 0, 0, 0, 0, "Language preferences" }, + { 0x5F35, 0, 0x65, 0, 0, 0, 0, "Sex" }, + { 0x006E, 1, 0, 1, 0, 0, 0, "Application Related Data" }, + { 0x004F, 0, 0x6E, 1, 0, 0, 0, "AID" }, + { 0x0073, 1, 0, 1, 0, 0, 0, "Discretionary Data Objects" }, + { 0x0047, 0, 0x6E, 1, 1, 0, 0, "Card Capabilities" }, + { 0x00C0, 0, 0x6E, 1, 1, 0, 0, "Extended Card Capabilities" }, + { 0x00C1, 0, 0x6E, 1, 1, 0, 0, "Algorithm Attributes Signature" }, + { 0x00C2, 0, 0x6E, 1, 1, 0, 0, "Algorithm Attributes Decryption" }, + { 0x00C3, 0, 0x6E, 1, 1, 0, 0, "Algorithm Attributes Authentication" }, + { 0x00C4, 0, 0x6E, 1, 0, 1, 1, "CHV Status Bytes" }, + { 0x00C5, 0, 0x6E, 1, 0, 0, 0, "Fingerprints" }, + { 0x00C6, 0, 0x6E, 1, 0, 0, 0, "CA Fingerprints" }, + { 0x00CD, 0, 0x6E, 1, 0, 0, 0, "Generation time" }, + { 0x007A, 1, 0, 1, 0, 0, 0, "Security Support Template" }, + { 0x0093, 0, 0x7A, 1, 1, 0, 0, "Digital Signature Counter" }, + { 0x0101, 0, 0, 0, 0, 0, 0, "Private DO 1"}, + { 0x0102, 0, 0, 0, 0, 0, 0, "Private DO 2"}, + { 0x0103, 0, 0, 0, 0, 0, 0, "Private DO 3"}, + { 0x0104, 0, 0, 0, 0, 0, 0, "Private DO 4"}, + { 0 } +}; + + +/* 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. Thsi + is usullay only required for corss checks + because the length of an S-expression is + implicitly available. */ + } pk[3]; + + /* Keep track of card capabilities. */ + struct + { + unsigned int get_challenge:1; + unsigned int key_import:1; + unsigned int change_force_chv:1; + unsigned int private_dos:1; + } 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; +}; + + + +/***** 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); + + + + + +/* 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. */ +static gpg_error_t +get_cached_data (app_t app, int tag, + unsigned char **result, size_t *resultlen, + int get_immediate) +{ + gpg_error_t err; + int i; + unsigned char *p; + size_t len; + struct cache_s *c; + + *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 (gpg_err_code_from_errno (errno)); + memcpy (p, c->data, c->length); + *result = p; + } + + *resultlen = c->length; + + return 0; + } + } + + err = iso7816_get_data (app->slot, tag, &p, &len); + if (err) + return err; + *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) + { + memcpy (c->data, p, len); + 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; + + 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->card_version > 0x0100 && data_objects[i].get_immediate_in_v11) + { + rc = iso7816_get_data (app->slot, 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)); + 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)); + 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; + + rc = iso7816_get_data (slot, 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); + 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 fucntion 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 + (0x28) and consist of key=value pairs. There is one key defined: + + F=<flags> + + Were 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 syncronized + Bit 1 = CHV2 has been been set to the default PIN of "123456" + (this implies that bit 0 is also set). + +*/ +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; + + /* 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') + 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); + 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; + } + next: + for (; buflen && *buffer != '\x18'; buflen--, buffer++) + if (*buffer == '\n') + buflen = 1; + } + while (buflen); + + xfree (relptr); +} + +/* Note, that FPR must be at least 20 bytes. */ +static gpg_error_t +store_fpr (int slot, int keynumber, u32 timestamp, + const unsigned char *m, size_t mlen, + const unsigned char *e, size_t elen, + unsigned char *fpr, unsigned int card_version) +{ + unsigned int n, nbits; + unsigned char *buffer, *p; + int rc; + + for (; mlen && !*m; mlen--, m++) /* strip leading zeroes */ + ; + for (; elen && !*e; elen--, e++) /* strip leading zeroes */ + ; + + n = 6 + 2 + mlen + 2 + elen; + p = buffer = xtrymalloc (3 + n); + if (!buffer) + return gpg_error_from_errno (errno); + + *p++ = 0x99; /* ctb */ + *p++ = n >> 8; /* 2 byte length header */ + *p++ = n; + *p++ = 4; /* key packet version */ + *p++ = timestamp >> 24; + *p++ = timestamp >> 16; + *p++ = timestamp >> 8; + *p++ = timestamp; + *p++ = 1; /* RSA */ + nbits = count_bits (m, mlen); + *p++ = nbits >> 8; + *p++ = nbits; + memcpy (p, m, mlen); p += mlen; + nbits = count_bits (e, elen); + *p++ = nbits >> 8; + *p++ = nbits; + memcpy (p, e, elen); p += elen; + + gcry_md_hash_buffer (GCRY_MD_SHA1, fpr, buffer, n+3); + + xfree (buffer); + + rc = iso7816_put_data (slot, (card_version > 0x0007? 0xC7 : 0xC6) + + keynumber, fpr, 20); + if (rc) + log_error (_("failed to store the fingerprint: %s\n"),gpg_strerror (rc)); + + if (!rc && card_version > 0x0100) + { + unsigned char buf[4]; + + buf[0] = timestamp >> 24; + buf[1] = timestamp >> 16; + buf[2] = timestamp >> 8; + buf[3] = timestamp; + + rc = iso7816_put_data (slot, 0xCE + keynumber, 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. */ + for (i=0; i< 20; i++) + sprintf (buf+2*i, "%02X", fpr[i]); + if (number == -1) + *numbuf = 0; /* Don't print the key number */ + else + sprintf (numbuf, "%d", number); + send_status_info (ctrl, keyword, + numbuf, (size_t)strlen(numbuf), + buf, (size_t)strlen (buf), NULL, 0); +} + +static void +send_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 = (stamp[0] << 24) | (stamp[1]<<16) | (stamp[2]<<8) | stamp[3]; + 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 *p, *buf = xmalloc (alen*2+1); + + for (p=buf; alen; a++, alen--, p += 2) + sprintf (p, "%02X", *a); + + send_status_info (ctrl, "KEY-DATA", + name, (size_t)strlen(name), + buf, (size_t)strlen (buf), + NULL, 0); + xfree (buf); +} + +/* 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 }, + { "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 }, + { 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, but we have it already in the app + context and the stamp argument is required anyway which we + can't by other means. The AID DO is available anyway but not + hex formatted. */ + char *serial; + time_t stamp; + char tmp[50]; + + if (!app_get_serial_and_stamp (app, &serial, &stamp)) + { + sprintf (tmp, "%lu", (unsigned long)stamp); + send_status_info (ctrl, "SERIALNO", + serial, strlen (serial), + tmp, strlen (tmp), + NULL, 0); + xfree (serial); + } + return 0; + } + if (table[idx].special == -2) + { + char tmp[50]; + + sprintf (tmp, "gc=%d ki=%d fc=%d pd=%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); + send_status_info (ctrl, table[idx].name, tmp, strlen (tmp), NULL, 0); + return 0; + } + + 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 + send_status_info (ctrl, table[idx].name, value, valuelen, NULL, 0); + + xfree (relptr); + } + return rc; +} + +/* 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. */ +#if GNUPG_MAJOR_VERSION > 1 +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; + int i; + + assert (keyno >=0 && keyno <= 2); + + relptr = get_one_do (app, 0x00C5, &value, &valuelen, NULL); + if (relptr && valuelen >= 60) + { + for (i = 0; i < 20; i++) + sprintf (fpr + (i * 2), "%02X", value[(keyno*20)+i]); + } + else + err = gpg_error (GPG_ERR_NOT_FOUND); + xfree (relptr); + return err; +} +#endif /*GNUPG_MAJOR_VERSION > 1*/ + + +/* 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. */ +#if GNUPG_MAJOR_VERSION > 1 +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 + corresponsing 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]; + 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_errno (errno); + 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. */ + i = 0; /* Avoid erroneous compiler warning. */ + 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; +} +#endif /*GNUPG_MAJOR_VERSION > 1*/ + + +/* Get the public key for KEYNO and store it as an S-expresion 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 soley indicated by the + presence of the app->app_local->pk[KEYNO-1].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-expresion + here but need to open encode it. */ +#if GNUPG_MAJOR_VERSION > 1 +static gpg_error_t +get_public_key (app_t app, int keyno) +{ + gpg_error_t err = 0; + unsigned char *buffer; + const unsigned char *keydata, *m, *e; + size_t buflen, keydatalen, mlen, elen; + unsigned char *mbuf = NULL; + unsigned char *ebuf = NULL; + unsigned char *keybuf = NULL; + unsigned char *keybuf_p; + + if (keyno < 1 || keyno > 3) + return gpg_error (GPG_ERR_INV_ID); + keyno--; + + /* 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; + + if (app->card_version > 0x0100) + { + /* We may simply read the public key out of these cards. */ + err = iso7816_read_public_key (app->slot, + keyno == 0? "\xB6" : + keyno == 1? "\xB8" : "\xA4", + 2, + &buffer, &buflen); + if (err) + { + log_error (_("reading public key failed: %s\n"), gpg_strerror (err)); + goto leave; + } + + 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; + } + + m = find_tlv (keydata, keydatalen, 0x0081, &mlen); + if (!m) + { + err = gpg_error (GPG_ERR_CARD); + log_error (_("response does not contain the RSA modulus\n")); + goto leave; + } + + + e = find_tlv (keydata, keydatalen, 0x0082, &elen); + if (!e) + { + err = gpg_error (GPG_ERR_CARD); + log_error (_("response does not contain the RSA public exponent\n")); + goto leave; + } + + /* Prepend numbers with a 0 if needed. */ + if (mlen && (*m & 0x80)) + { + mbuf = xtrymalloc ( mlen + 1); + if (!mbuf) + { + err = gpg_error_from_errno (errno); + goto leave; + } + *mbuf = 0; + memcpy (mbuf+1, m, mlen); + mlen++; + m = mbuf; + } + if (elen && (*e & 0x80)) + { + ebuf = xtrymalloc ( elen + 1); + if (!ebuf) + { + err = gpg_error_from_errno (errno); + goto leave; + } + *ebuf = 0; + memcpy (ebuf+1, e, elen); + elen++; + e = ebuf; + } + + } + 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 = asprintf (&command, + "gpg --list-keys --with-colons --with-key-data '%s'", + fpr); + if (ret < 0) + { + err = gpg_error_from_errno (errno); + goto leave; + } + + fp = popen (command, "r"); + free (command); + if (!fp) + { + err = gpg_error_from_errno (errno); + log_error ("running gpg failed: %s\n", gpg_strerror (err)); + goto leave; + } + + err = retrieve_key_material (fp, hexkeyid, &m, &mlen, &e, &elen); + fclose (fp); + if (err) + { + log_error ("error while retrieving key material through pipe: %s\n", + gpg_strerror (err)); + goto leave; + } + } + + /* Allocate a buffer to construct the S-expression. */ + /* FIXME: We should provide a generalized S-expression creation + mechanism. */ + keybuf = xtrymalloc (50 + 2*35 + mlen + elen + 1); + if (!keybuf) + { + err = gpg_error_from_errno (errno); + goto leave; + } + + sprintf (keybuf, "(10:public-key(3:rsa(1:n%u:", (unsigned int) mlen); + keybuf_p = keybuf + strlen (keybuf); + memcpy (keybuf_p, m, mlen); + keybuf_p += mlen; + sprintf (keybuf_p, ")(1:e%u:", (unsigned int)elen); + keybuf_p += strlen (keybuf_p); + memcpy (keybuf_p, e, elen); + keybuf_p += elen; + strcpy (keybuf_p, ")))"); + keybuf_p += strlen (keybuf_p); + + app->app_local->pk[keyno].key = keybuf; + app->app_local->pk[keyno].keylen = (keybuf_p - keybuf); + + leave: + /* Set a flag to indicate that we tried to read the key. */ + app->app_local->pk[keyno].read_done = 1; + + xfree (buffer); + xfree (mbuf); + xfree (ebuf); + return 0; +} +#endif /* GNUPG_MAJOR_VERSION > 1 */ + + + +/* Send the KEYPAIRINFO back. KEYNO 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 keyno) +{ + gpg_error_t err = 0; + /* 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. */ +#if GNUPG_MAJOR_VERSION > 1 + unsigned char grip[20]; + char gripstr[41]; + char idbuf[50]; + int i; + + err = get_public_key (app, keyno); + if (err) + goto leave; + + assert (keyno >= 1 && keyno <= 3); + if (!app->app_local->pk[keyno-1].key) + goto leave; /* No such key - ignore. */ + + err = keygrip_from_canon_sexp (app->app_local->pk[keyno-1].key, + app->app_local->pk[keyno-1].keylen, + grip); + if (err) + goto leave; + + for (i=0; i < 20; i++) + sprintf (gripstr+i*2, "%02X", grip[i]); + + sprintf (idbuf, "OPENPGP.%d", keyno); + send_status_info (ctrl, "KEYPAIRINFO", + gripstr, 40, + idbuf, strlen (idbuf), + NULL, (size_t)0); + + leave: +#endif /* GNUPG_MAJOR_VERSION > 1 */ + + return err; +} + + +/* Handle the LEARN command for OpenPGP. */ +static gpg_error_t +do_learn_status (app_t app, ctrl_t ctrl) +{ + do_getattr (app, ctrl, "EXTCAP"); + do_getattr (app, ctrl, "DISP-NAME"); + do_getattr (app, ctrl, "DISP-LANG"); + do_getattr (app, ctrl, "DISP-SEX"); + do_getattr (app, ctrl, "PUBKEY-URL"); + do_getattr (app, ctrl, "LOGIN-DATA"); + do_getattr (app, ctrl, "KEY-FPR"); + if (app->card_version > 0x0100) + do_getattr (app, ctrl, "KEY-TIME"); + do_getattr (app, ctrl, "CA-FPR"); + do_getattr (app, ctrl, "CHV-STATUS"); + do_getattr (app, ctrl, "SIG-COUNTER"); + if (app->app_local->extcap.private_dos) + { + do_getattr (app, ctrl, "PRIVATE-DO-1"); + do_getattr (app, ctrl, "PRIVATE-DO-2"); + if (app->did_chv2) + do_getattr (app, ctrl, "PRIVATE-DO-3"); + if (app->did_chv3) + do_getattr (app, ctrl, "PRIVATE-DO-4"); + } + send_keypair_info (app, ctrl, 1); + send_keypair_info (app, ctrl, 2); + send_keypair_info (app, ctrl, 3); + return 0; +} + + +/* 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, const char *keyid, unsigned char **pk, size_t *pklen) +{ +#if GNUPG_MAJOR_VERSION > 1 + gpg_error_t err; + int keyno; + unsigned char *buf; + + if (!strcmp (keyid, "OPENPGP.1")) + keyno = 1; + else if (!strcmp (keyid, "OPENPGP.2")) + keyno = 2; + else if (!strcmp (keyid, "OPENPGP.3")) + keyno = 3; + else + return gpg_error (GPG_ERR_INV_ID); + + err = get_public_key (app, keyno); + if (err) + return err; + + buf = app->app_local->pk[keyno-1].key; + if (!buf) + return gpg_error (GPG_ERR_NO_PUBKEY); + *pk = buf; + *pklen = app->app_local->pk[keyno-1].keylen;; + return 0; +#else + return gpg_error (GPG_ERR_NOT_IMPLEMENTED); +#endif +} + + + +/* 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 = 0; + + if (!app->did_chv2) + { + char *pinvalue; + + rc = pincb (pincb_arg, "PIN", &pinvalue); + if (rc) + { + log_info (_("PIN callback returned error: %s\n"), gpg_strerror (rc)); + return rc; + } + + if (strlen (pinvalue) < 6) + { + log_error (_("PIN for CHV%d is too short;" + " minimum length is %d\n"), 2, 6); + xfree (pinvalue); + return gpg_error (GPG_ERR_BAD_PIN); + } + + rc = iso7816_verify (app->slot, 0x82, pinvalue, strlen (pinvalue)); + 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; + + if (!app->did_chv1 && !app->force_chv1) + { + rc = iso7816_verify (app->slot, 0x81, pinvalue, strlen (pinvalue)); + 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)); + xfree (pinvalue); + flush_cache_after_error (app); + return rc; + } + app->did_chv1 = 1; + } + xfree (pinvalue); + } + return rc; +} + +/* 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 GNUPG_MAJOR_VERSION != 1 + if (!opt.allow_admin) + { + log_info (_("access to admin commands is not configured\n")); + return gpg_error (GPG_ERR_EACCES); + } +#endif + + if (!app->did_chv3) + { + char *pinvalue; + void *relptr; + unsigned char *value; + size_t valuelen; + + 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); + } + if (value[6] == 0) + { + log_info (_("card is permanently locked!\n")); + xfree (relptr); + return gpg_error (GPG_ERR_BAD_PIN); + } + + log_info(_("%d Admin PIN attempts remaining before card" + " is permanently locked\n"), value[6]); + xfree (relptr); + + /* TRANSLATORS: Do not translate the "|A|" prefix but + keep it at the start of the string. We need this elsewhere + to get some infos on the string. */ + rc = pincb (pincb_arg, _("|A|Admin PIN"), &pinvalue); + if (rc) + { + log_info (_("PIN callback returned error: %s\n"), gpg_strerror (rc)); + return rc; + } + + if (strlen (pinvalue) < 8) + { + log_error (_("PIN for CHV%d is too short;" + " minimum length is %d\n"), 3, 8); + xfree (pinvalue); + return gpg_error (GPG_ERR_BAD_PIN); + } + + rc = iso7816_verify (app->slot, 0x83, pinvalue, strlen (pinvalue)); + 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, 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 need_chv; + int special; + } table[] = { + { "DISP-NAME", 0x005B, 3 }, + { "LOGIN-DATA", 0x005E, 3, 2 }, + { "DISP-LANG", 0x5F2D, 3 }, + { "DISP-SEX", 0x5F35, 3 }, + { "PUBKEY-URL", 0x5F50, 3 }, + { "CHV-STATUS-1", 0x00C4, 3, 1 }, + { "CA-FPR-1", 0x00CA, 3 }, + { "CA-FPR-2", 0x00CB, 3 }, + { "CA-FPR-3", 0x00CC, 3 }, + { "PRIVATE-DO-1", 0x0101, 2 }, + { "PRIVATE-DO-2", 0x0102, 3 }, + { "PRIVATE-DO-3", 0x0103, 2 }, + { "PRIVATE-DO-4", 0x0104, 3 }, + { NULL, 0 } + }; + + + for (idx=0; table[idx].name && strcmp (table[idx].name, name); idx++) + ; + if (!table[idx].name) + return gpg_error (GPG_ERR_INV_NAME); + + 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].tag); + rc = iso7816_put_data (app->slot, table[idx].tag, value, valuelen); + if (rc) + log_error ("failed to set `%s': %s\n", table[idx].name, gpg_strerror (rc)); + + if (table[idx].special == 1) + app->force_chv1 = (valuelen && *value == 0); + else if (table[idx].special == 2) + parse_login_data (app); + + return rc; +} + + +/* Handle the PASSWD command. */ +static gpg_error_t +do_change_pin (app_t app, ctrl_t ctrl, const char *chvnostr, int reset_mode, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg) +{ + int rc = 0; + int chvno = atoi (chvnostr); + char *pinvalue; + + if (reset_mode && chvno == 3) + { + rc = gpg_error (GPG_ERR_INV_ID); + goto leave; + } + else if (reset_mode || chvno == 3) + { + /* 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) + { + /* 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; + } + + if (chvno == 3) + app->did_chv3 = 0; + else + app->did_chv1 = app->did_chv2 = 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. */ + rc = pincb (pincb_arg, chvno == 3? _("|AN|New Admin PIN") : _("|N|New PIN"), + &pinvalue); + if (rc) + { + log_error (_("error getting new PIN: %s\n"), gpg_strerror (rc)); + goto leave; + } + + if (reset_mode) + { + rc = iso7816_reset_retry_counter (app->slot, 0x81, + pinvalue, strlen (pinvalue)); + if (!rc) + rc = iso7816_reset_retry_counter (app->slot, 0x82, + pinvalue, strlen (pinvalue)); + } + else + { + 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 + rc = iso7816_change_reference_data (app->slot, 0x80 + chvno, NULL, 0, + pinvalue, strlen (pinvalue)); + } + xfree (pinvalue); + if (rc) + flush_cache_after_error (app); + + leave: + 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. */ +static gpg_error_t +does_key_exist (app_t app, int keyidx, 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, 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 + log_info (_("generating new key\n")); + return 0; +} + + + +/* 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, 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 char *template = NULL; + unsigned char *tp; + size_t template_len; + unsigned char fprbuf[20]; + u32 created_at = 0; + + 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, 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)) + { + err = gpg_error (GPG_ERR_WRONG_PUBKEY_ALGO); + 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; + } + nbits = rsa_n? count_bits (rsa_n, rsa_n_len) : 0; + if (nbits != 1024) + { + log_error (_("RSA modulus missing or not of size %d bits\n"), 1024); + err = gpg_error (GPG_ERR_BAD_SECKEY); + goto leave; + } + nbits = rsa_e? count_bits (rsa_e, rsa_e_len) : 0; + if (nbits < 2 || nbits > 32) + { + log_error (_("RSA public exponent missing or larger than %d bits\n"), + 32); + err = gpg_error (GPG_ERR_BAD_SECKEY); + goto leave; + } + nbits = rsa_p? count_bits (rsa_p, rsa_p_len) : 0; + if (nbits != 512) + { + log_error (_("RSA prime %s missing or not of size %d bits\n"), "P", 512); + err = gpg_error (GPG_ERR_BAD_SECKEY); + goto leave; + } + nbits = rsa_q? count_bits (rsa_q, rsa_q_len) : 0; + if (nbits != 512) + { + log_error (_("RSA prime %s missing or not of size %d bits\n"), "Q", 512); + err = gpg_error (GPG_ERR_BAD_SECKEY); + goto leave; + } + + + /* Build the private key template as described in section 4.3.3.6 of + the OpenPGP card specs: + 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_errno (errno); + 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); + + + /* 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; + + /* 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, + (app->card_version > 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->slot, keyno, created_at, + rsa_n, rsa_n_len, rsa_e, rsa_e_len, + fprbuf, app->card_version); + if (err) + goto leave; + + + leave: + xfree (template); + return err; +} + + +/* Handle the GENKEY command. */ +static gpg_error_t +do_genkey (app_t app, ctrl_t ctrl, const char *keynostr, unsigned int flags, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg) +{ + int rc; + char numbuf[30]; + unsigned char fprbuf[20]; + const unsigned char *keydata, *m, *e; + unsigned char *buffer = NULL; + size_t buflen, keydatalen, mlen, elen; + time_t created_at; + int keyno = atoi (keynostr); + int force = (flags & 1); + time_t start_at; + + if (keyno < 1 || keyno > 3) + return gpg_error (GPG_ERR_INV_ID); + keyno--; + + /* 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. */ + rc = does_key_exist (app, keyno, force); + if (rc) + return rc; + + /* Prepare for key generation by verifying the ADmin PIN. */ + rc = verify_chv3 (app, pincb, pincb_arg); + if (rc) + goto leave; + +#if 1 + log_info (_("please wait while key is being generated ...\n")); + start_at = time (NULL); + rc = iso7816_generate_keypair +#else +#warning key generation temporary replaced by reading an existing key. + rc = iso7816_read_public_key +#endif + (app->slot, + keyno == 0? "\xB6" : + keyno == 1? "\xB8" : "\xA4", + 2, + &buffer, &buflen); + if (rc) + { + rc = gpg_error (GPG_ERR_CARD); + log_error (_("generating key failed\n")); + goto leave; + } + log_info (_("key generation completed (%d seconds)\n"), + (int)(time (NULL) - start_at)); + keydata = find_tlv (buffer, buflen, 0x7F49, &keydatalen); + if (!keydata) + { + rc = gpg_error (GPG_ERR_CARD); + log_error (_("response does not contain the public key data\n")); + goto leave; + } + + m = find_tlv (keydata, keydatalen, 0x0081, &mlen); + if (!m) + { + rc = gpg_error (GPG_ERR_CARD); + log_error (_("response does not contain the RSA modulus\n")); + goto leave; + } +/* log_printhex ("RSA n:", m, mlen); */ + send_key_data (ctrl, "n", m, mlen); + + e = find_tlv (keydata, keydatalen, 0x0082, &elen); + if (!e) + { + rc = gpg_error (GPG_ERR_CARD); + log_error (_("response does not contain the RSA public exponent\n")); + goto leave; + } +/* log_printhex ("RSA e:", e, elen); */ + send_key_data (ctrl, "e", e, elen); + + created_at = gnupg_get_time (); + sprintf (numbuf, "%lu", (unsigned long)created_at); + send_status_info (ctrl, "KEY-CREATED-AT", + numbuf, (size_t)strlen(numbuf), NULL, 0); + + rc = store_fpr (app->slot, keyno, (u32)created_at, + m, mlen, e, elen, fprbuf, app->card_version); + if (rc) + goto leave; + send_fpr_if_not_null (ctrl, "KEY-FPR", -1, fprbuf); + + + leave: + xfree (buffer); + return rc; +} + + +static unsigned long +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 >= 1 && keyno <= 3); + + rc = get_cached_data (app, 0x006E, &buffer, &buflen, 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-1)*20; + for (i=0; i < 20; i++) + if (sha1fpr[i] != fpr[i]) + { + xfree (buffer); + return gpg_error (GPG_ERR_WRONG_SECKEY); + } + xfree (buffer); + return 0; +} + + + /* 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 we + assume that this is okay. */ +static gpg_error_t +check_against_given_fingerprint (app_t app, const char *fpr, int keyno) +{ + 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, keyno, tmp); +} + + + +/* Compute a digital signature on INDATA which is expected to be the + raw message digest. For this application the KEYIDSTR consists of + the serialnumber and the fingerprint delimited by a slash. + + Note that this fucntion may return the error code + GPG_ERR_WRONG_CARD to indicate that the card currently present does + not match the one required for the requested action (e.g. the + serial number does not match). */ +static gpg_error_t +do_sign (app_t app, 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; + unsigned char data[35]; + unsigned char tmp_sn[20]; /* actually 16 but we use it also for the fpr. */ + const char *s; + int n; + const char *fpr = NULL; + unsigned long sigcount; + + if (!keyidstr || !*keyidstr) + return gpg_error (GPG_ERR_INV_VALUE); + if (indatalen == 20) + ; + else if (indatalen == (15 + 20) && hashalgo == GCRY_MD_SHA1 + && !memcmp (indata, sha1_prefix, 15)) + ; + else if (indatalen == (15 + 20) && hashalgo == GCRY_MD_RMD160 + && !memcmp (indata, rmd160_prefix, 15)) + ; + else + { + log_error(_("card does not support digest algorithm %s\n"), + digest_algo_to_string(hashalgo)); + return gpg_error (GPG_ERR_INV_VALUE); + } + + /* Check whether an OpenPGP card of any version has been requested. */ + if (strlen (keyidstr) < 32 || strncmp (keyidstr, "D27600012401", 12)) + return gpg_error (GPG_ERR_INV_ID); + + for (s=keyidstr, n=0; hexdigitp (s); s++, n++) + ; + if (n != 32) + return gpg_error (GPG_ERR_INV_ID); + else if (!*s) + ; /* no fingerprint given: we allow this for now. */ + else if (*s == '/') + fpr = s + 1; + else + return gpg_error (GPG_ERR_INV_ID); + + for (s=keyidstr, n=0; n < 16; s += 2, n++) + tmp_sn[n] = xtoi_2 (s); + + if (app->serialnolen != 16) + return gpg_error (GPG_ERR_INV_CARD); + if (memcmp (app->serialno, tmp_sn, 16)) + return gpg_error (GPG_ERR_WRONG_CARD); + + /* If a fingerprint has been specified check it against the one on + the card. This is allows for a meaningful error message in case + the key on the card has been replaced but the shadow information + known to gpg was not updated. If there is no fingerprint, gpg + will detect a bogus signature anyway due to the + verify-after-signing feature. */ + rc = fpr? check_against_given_fingerprint (app, fpr, 1) : 0; + if (rc) + return rc; + + if (hashalgo == GCRY_MD_SHA1) + memcpy (data, sha1_prefix, 15); + else if (hashalgo == GCRY_MD_RMD160) + memcpy (data, rmd160_prefix, 15); + else + return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM); + memcpy (data+15, indata, indatalen); + + sigcount = get_sig_counter (app); + log_info (_("signatures created so far: %lu\n"), sigcount); + + if (!app->did_chv1 || app->force_chv1 ) + { + char *pinvalue; + + { + char *prompt; +#define PROMPTSTRING _("||Please enter the PIN%%0A[sigs done: %lu]") + + prompt = malloc (strlen (PROMPTSTRING) + 50); + if (!prompt) + return gpg_error_from_errno (errno); + sprintf (prompt, PROMPTSTRING, sigcount); + rc = pincb (pincb_arg, prompt, &pinvalue); + free (prompt); +#undef PROMPTSTRING + } + if (rc) + { + log_info (_("PIN callback returned error: %s\n"), gpg_strerror (rc)); + return rc; + } + + if (strlen (pinvalue) < 6) + { + log_error (_("PIN for CHV%d is too short;" + " minimum length is %d\n"), 1, 6); + xfree (pinvalue); + return gpg_error (GPG_ERR_BAD_PIN); + } + + rc = iso7816_verify (app->slot, 0x81, pinvalue, strlen (pinvalue)); + if (rc) + { + log_error (_("verify CHV%d failed: %s\n"), 1, gpg_strerror (rc)); + xfree (pinvalue); + flush_cache_after_error (app); + return rc; + } + app->did_chv1 = 1; + if (!app->did_chv2) + { + /* We should also verify CHV2. */ + rc = iso7816_verify (app->slot, 0x82, pinvalue, strlen (pinvalue)); + 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); + } + + rc = iso7816_compute_ds (app->slot, data, 35, outdata, outdatalen); + return rc; +} + +/* Compute a digital signature using the INTERNAL AUTHENTICATE command + on INDATA which is expected to be the raw message digest. For this + application the KEYIDSTR consists of the serialnumber and the + fingerprint delimited by a slash. Optionally the id OPENPGP.3 may + be given. + + Note that this fucntion may return the error code + GPG_ERR_WRONG_CARD to indicate that the card currently present does + not match the one required for the requested action (e.g. the + serial number does not match). */ +static gpg_error_t +do_auth (app_t app, 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; + unsigned char tmp_sn[20]; /* actually 16 but we use it also for the fpr. */ + const char *s; + int n; + const char *fpr = NULL; + + if (!keyidstr || !*keyidstr) + return gpg_error (GPG_ERR_INV_VALUE); + if (indatalen > 50) /* For a 1024 bit key. */ + return gpg_error (GPG_ERR_INV_VALUE); + + /* Check whether an OpenPGP card of any version has been requested. */ + if (!strcmp (keyidstr, "OPENPGP.3")) + ; + else if (strlen (keyidstr) < 32 || strncmp (keyidstr, "D27600012401", 12)) + return gpg_error (GPG_ERR_INV_ID); + else + { + for (s=keyidstr, n=0; hexdigitp (s); s++, n++) + ; + if (n != 32) + return gpg_error (GPG_ERR_INV_ID); + else if (!*s) + ; /* no fingerprint given: we allow this for now. */ + else if (*s == '/') + fpr = s + 1; + else + return gpg_error (GPG_ERR_INV_ID); + + for (s=keyidstr, n=0; n < 16; s += 2, n++) + tmp_sn[n] = xtoi_2 (s); + + if (app->serialnolen != 16) + return gpg_error (GPG_ERR_INV_CARD); + if (memcmp (app->serialno, tmp_sn, 16)) + return gpg_error (GPG_ERR_WRONG_CARD); + } + + /* If a fingerprint has been specified check it against the one on + the card. This is allows for a meaningful error message in case + the key on the card has been replaced but the shadow information + known to gpg was not updated. If there is no fingerprint, gpg + will detect a bogus signature anyway due to the + verify-after-signing feature. */ + rc = fpr? check_against_given_fingerprint (app, fpr, 3) : 0; + if (rc) + return rc; + + rc = verify_chv2 (app, pincb, pincb_arg); + if (!rc) + rc = iso7816_internal_authenticate (app->slot, indata, indatalen, + outdata, outdatalen); + return rc; +} + + +static gpg_error_t +do_decipher (app_t app, 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; + unsigned char tmp_sn[20]; /* actually 16 but we use it also for the fpr. */ + const char *s; + int n; + const char *fpr = NULL; + + if (!keyidstr || !*keyidstr || !indatalen) + return gpg_error (GPG_ERR_INV_VALUE); + + /* Check whether an OpenPGP card of any version has been requested. */ + if (strlen (keyidstr) < 32 || strncmp (keyidstr, "D27600012401", 12)) + return gpg_error (GPG_ERR_INV_ID); + + for (s=keyidstr, n=0; hexdigitp (s); s++, n++) + ; + if (n != 32) + return gpg_error (GPG_ERR_INV_ID); + else if (!*s) + ; /* no fingerprint given: we allow this for now. */ + else if (*s == '/') + fpr = s + 1; + else + return gpg_error (GPG_ERR_INV_ID); + + for (s=keyidstr, n=0; n < 16; s += 2, n++) + tmp_sn[n] = xtoi_2 (s); + + if (app->serialnolen != 16) + return gpg_error (GPG_ERR_INV_CARD); + if (memcmp (app->serialno, tmp_sn, 16)) + return gpg_error (GPG_ERR_WRONG_CARD); + + /* If a fingerprint has been specified check it against the one on + the card. This is allows for a meaningful error message in case + the key on the card has been replaced but the shadow information + known to gpg was not updated. If there is no fingerprint, the + decryption will won't produce the right plaintext anyway. */ + rc = fpr? check_against_given_fingerprint (app, fpr, 2) : 0; + if (rc) + return rc; + + rc = verify_chv2 (app, pincb, pincb_arg); + if (!rc) + rc = iso7816_decipher (app->slot, indata, indatalen, 0, + outdata, outdatalen); + 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, const char *keyidstr, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg) +{ + unsigned char tmp_sn[20]; + const char *s; + int n; + int admin_pin = 0; + + if (!keyidstr || !*keyidstr) + return gpg_error (GPG_ERR_INV_VALUE); + + /* Check whether an OpenPGP card of any version has been requested. */ + if (strlen (keyidstr) < 32 || strncmp (keyidstr, "D27600012401", 12)) + return gpg_error (GPG_ERR_INV_ID); + + for (s=keyidstr, n=0; hexdigitp (s); s++, n++) + ; + if (n != 32) + return gpg_error (GPG_ERR_INV_ID); + else if (!*s) + ; /* No fingerprint given: we allow this for now. */ + else if (*s == '/') + ; /* We ignore a fingerprint. */ + else if (!strcmp (s, "[CHV3]") ) + admin_pin = 1; + else + return gpg_error (GPG_ERR_INV_ID); + + for (s=keyidstr, n=0; n < 16; s += 2, n++) + tmp_sn[n] = xtoi_2 (s); + + if (app->serialnolen != 16) + return gpg_error (GPG_ERR_INV_CARD); + if (memcmp (app->serialno, tmp_sn, 16)) + return gpg_error (GPG_ERR_WRONG_CARD); + + /* 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 (value[6] < 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); +} + + + + +/* 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; + int rc; + unsigned char *buffer; + size_t buflen; + void *relptr; + + rc = iso7816_select_application (slot, aid, sizeof aid); + if (!rc) + { + unsigned int manufacturer; + + app->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. */ + rc = iso7816_get_data (slot, 0x004F, &buffer, &buflen); + if (rc) + goto leave; + if (opt.verbose) + { + log_info ("AID: "); + log_printhex ("", buffer, buflen); + } + + app->card_version = buffer[6] << 8; + app->card_version |= 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) + { + rc = gpg_error (gpg_err_code_from_errno (errno)); + goto leave; + } + + relptr = get_one_do (app, 0x00C4, &buffer, &buflen, NULL); + if (!relptr) + { + log_error (_("can't access %s - invalid OpenPGP card?\n"), + "CHV Status Bytes"); + goto leave; + } + app->force_chv1 = (buflen && *buffer == 0); + xfree (relptr); + + relptr = get_one_do (app, 0x00C0, &buffer, &buflen, NULL); + if (!relptr) + { + log_error (_("can't access %s - invalid OpenPGP card?\n"), + "Extended Capability Flags" ); + goto leave; + } + if (buflen) + { + 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); + } + xfree (relptr); + + /* Some of the first cards accidently don't set the + CHANGE_FORCE_CHV bit but allow it anyway. */ + if (app->card_version <= 0x0100 && manufacturer == 1) + app->app_local->extcap.change_force_chv = 1; + + parse_login_data (app); + + if (opt.verbose > 1) + dump_all_do (slot); + + app->fnc.deinit = do_deinit; + app->fnc.learn_status = do_learn_status; + app->fnc.readkey = do_readkey; + app->fnc.getattr = do_getattr; + app->fnc.setattr = do_setattr; + 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 (rc) + do_deinit (app); + return rc; +} + + + diff --git a/g10/armor.c b/g10/armor.c new file mode 100644 index 0000000..65804a7 --- /dev/null +++ b/g10/armor.c @@ -0,0 +1,1474 @@ +/* armor.c - Armor flter + * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, + * 2006 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <assert.h> +#include <ctype.h> + +#include "errors.h" +#include "iobuf.h" +#include "memory.h" +#include "util.h" +#include "filter.h" +#include "packet.h" +#include "options.h" +#include "main.h" +#include "status.h" +#include "i18n.h" + +#define MAX_LINELEN 20000 + +#define CRCINIT 0xB704CE +#define CRCPOLY 0X864CFB +#define CRCUPDATE(a,c) do { \ + a = ((a) << 8) ^ crc_table[((a)&0xff >> 16) ^ (c)]; \ + a &= 0x00ffffff; \ + } while(0) +static u32 crc_table[256]; +static byte bintoasc[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/"; +static byte asctobin[256]; /* runtime initialized */ +static int is_initialized; + + +typedef enum { + fhdrHASArmor = 0, + fhdrNOArmor, + fhdrINIT, + fhdrINITCont, + fhdrINITSkip, + fhdrCHECKBegin, + fhdrWAITHeader, + fhdrWAITClearsig, + fhdrSKIPHeader, + fhdrCLEARSIG, + fhdrREADClearsig, + fhdrNullClearsig, + fhdrEMPTYClearsig, + fhdrCHECKClearsig, + fhdrCHECKClearsig2, + fhdrCHECKDashEscaped, + fhdrCHECKDashEscaped2, + fhdrCHECKDashEscaped3, + fhdrREADClearsigNext, + fhdrENDClearsig, + fhdrENDClearsigHelp, + fhdrTESTSpaces, + fhdrCLEARSIGSimple, + fhdrCLEARSIGSimpleNext, + fhdrTEXT, + fhdrTEXTSimple, + fhdrERROR, + fhdrERRORShow, + fhdrEOF +} fhdr_state_t; + + +/* if we encounter this armor string with this index, go + * into a mode which fakes packets and wait for the next armor */ +#define BEGIN_SIGNATURE 2 +#define BEGIN_SIGNED_MSG_IDX 3 +static char *head_strings[] = { + "BEGIN PGP MESSAGE", + "BEGIN PGP PUBLIC KEY BLOCK", + "BEGIN PGP SIGNATURE", + "BEGIN PGP SIGNED MESSAGE", + "BEGIN PGP ARMORED FILE", /* gnupg extension */ + "BEGIN PGP PRIVATE KEY BLOCK", + "BEGIN PGP SECRET KEY BLOCK", /* only used by pgp2 */ + NULL +}; +static char *tail_strings[] = { + "END PGP MESSAGE", + "END PGP PUBLIC KEY BLOCK", + "END PGP SIGNATURE", + "END dummy", + "END PGP ARMORED FILE", + "END PGP PRIVATE KEY BLOCK", + "END PGP SECRET KEY BLOCK", + NULL +}; + + +/* Create a new context for armor filters. */ +armor_filter_context_t * +new_armor_context (void) +{ + armor_filter_context_t *afx; + + afx = xcalloc (1, sizeof *afx); + afx->refcount = 1; + + return afx; +} + +/* Release an armor filter context. Passing NULL is explicitly + allowed and a no-op. */ +void +release_armor_context (armor_filter_context_t *afx) +{ + if (!afx) + return; + + /* In contrast to 2.0, we use in 1.4 heap based contexts only in a + very few places and in general keep the stack based contexts. A + REFCOUNT of 0 indicates a stack based context and thus we don't + do anything in this case. */ + if (!afx->refcount) + return; + + if ( --afx->refcount ) + return; + xfree (afx); +} + +/* Push the armor filter onto the iobuf stream IOBUF. */ +int +push_armor_filter (armor_filter_context_t *afx, iobuf_t iobuf) +{ + int rc; + + if (!afx->refcount) + return iobuf_push_filter (iobuf, armor_filter, afx); + + afx->refcount++; + rc = iobuf_push_filter (iobuf, armor_filter, afx); + if (rc) + afx->refcount--; + return rc; +} + + + + + +static void +initialize(void) +{ + int i, j; + u32 t; + byte *s; + + /* init the crc lookup table */ + crc_table[0] = 0; + for(i=j=0; j < 128; j++ ) { + t = crc_table[j]; + if( t & 0x00800000 ) { + t <<= 1; + crc_table[i++] = t ^ CRCPOLY; + crc_table[i++] = t; + } + else { + t <<= 1; + crc_table[i++] = t; + crc_table[i++] = t ^ CRCPOLY; + } + } + /* build the helptable for radix64 to bin conversion */ + for(i=0; i < 256; i++ ) + asctobin[i] = 255; /* used to detect invalid characters */ + for(s=bintoasc,i=0; *s; s++,i++ ) + asctobin[*s] = i; + + is_initialized=1; +} + +/**************** + * Check whether this is an armored file or not See also + * parse-packet.c for details on this code For unknown historic + * reasons we use a string here but only the first byte will be used. + * Returns: True if it seems to be armored + */ +static int +is_armored( const byte *buf ) +{ + int ctb, pkttype; + + ctb = *buf; + if( !(ctb & 0x80) ) + return 1; /* invalid packet: assume it is armored */ + pkttype = ctb & 0x40 ? (ctb & 0x3f) : ((ctb>>2)&0xf); + switch( pkttype ) { + case PKT_MARKER: + case PKT_SYMKEY_ENC: + case PKT_ONEPASS_SIG: + case PKT_PUBLIC_KEY: + case PKT_SECRET_KEY: + case PKT_PUBKEY_ENC: + case PKT_SIGNATURE: + case PKT_COMMENT: + case PKT_OLD_COMMENT: + case PKT_PLAINTEXT: + case PKT_COMPRESSED: + case PKT_ENCRYPTED: + return 0; /* seems to be a regular packet: not armored */ + } + + return 1; +} + + +/**************** + * Try to check whether the iobuf is armored + * Returns true if this may be the case; the caller should use the + * filter to do further processing. + */ +int +use_armor_filter( IOBUF a ) +{ + byte buf[1]; + int n; + + /* fixme: there might be a problem with iobuf_peek */ + n = iobuf_peek(a, buf, 1 ); + if( n == -1 ) + return 0; /* EOF, doesn't matter whether armored or not */ + if( !n ) + return 1; /* can't check it: try armored */ + return is_armored(buf); +} + + + + +static void +invalid_armor(void) +{ + write_status(STATUS_BADARMOR); + g10_exit(1); /* stop here */ +} + + +/**************** + * check whether the armor header is valid on a signed message. + * this is for security reasons: the header lines are not included in the + * hash and by using some creative formatting rules, Mallory could fake + * any text at the beginning of a document; assuming it is read with + * a simple viewer. We only allow the Hash Header. + */ +static int +parse_hash_header( const char *line ) +{ + const char *s, *s2; + unsigned found = 0; + + if( strlen(line) < 6 || strlen(line) > 60 ) + return 0; /* too short or too long */ + if( memcmp( line, "Hash:", 5 ) ) + return 0; /* invalid header */ + s = line+5; + for(s=line+5;;s=s2) { + for(; *s && (*s==' ' || *s == '\t'); s++ ) + ; + if( !*s ) + break; + for(s2=s+1; *s2 && *s2!=' ' && *s2 != '\t' && *s2 != ','; s2++ ) + ; + if( !strncmp( s, "RIPEMD160", s2-s ) ) + found |= 1; + else if( !strncmp( s, "SHA1", s2-s ) ) + found |= 2; + else if( !strncmp( s, "MD5", s2-s ) ) + found |= 4; + else if( !strncmp( s, "SHA224", s2-s ) ) + found |= 8; + else if( !strncmp( s, "SHA256", s2-s ) ) + found |= 16; + else if( !strncmp( s, "SHA384", s2-s ) ) + found |= 32; + else if( !strncmp( s, "SHA512", s2-s ) ) + found |= 64; + else + return 0; + for(; *s2 && (*s2==' ' || *s2 == '\t'); s2++ ) + ; + if( *s2 && *s2 != ',' ) + return 0; + if( *s2 ) + s2++; + } + return found; +} + + + +/**************** + * Check whether this is a armor line. + * returns: -1 if it is not a armor header or the index number of the + * armor header. + */ +static int +is_armor_header( byte *line, unsigned len ) +{ + const char *s; + byte *save_p, *p; + int save_c; + int i; + + if( len < 15 ) + return -1; /* too short */ + if( memcmp( line, "-----", 5 ) ) + return -1; /* no */ + p = strstr( line+5, "-----"); + if( !p ) + return -1; + save_p = p; + p += 5; + + /* Some Windows environments seem to add whitespace to the end of + the line, so we strip it here. This becomes strict if + --rfc2440 is set since 2440 reads "The header lines, therefore, + MUST start at the beginning of a line, and MUST NOT have text + following them on the same line." It is unclear whether "text" + refers to all text or just non-whitespace text. */ + + if(RFC2440) + { + if( *p == '\r' ) + p++; + if( *p == '\n' ) + p++; + } + else + while(*p==' ' || *p=='\r' || *p=='\n' || *p=='\t') + p++; + + if( *p ) + return -1; /* garbage after dashes */ + save_c = *save_p; *save_p = 0; + p = line+5; + for(i=0; (s=head_strings[i]); i++ ) + if( !strcmp(s, p) ) + break; + *save_p = save_c; + if( !s ) + return -1; /* unknown armor line */ + + if( opt.verbose > 1 ) + log_info(_("armor: %s\n"), head_strings[i]); + return i; +} + + + +/**************** + * Parse a header lines + * Return 0: Empty line (end of header lines) + * -1: invalid header line + * >0: Good header line + */ +static int +parse_header_line( armor_filter_context_t *afx, byte *line, unsigned int len ) +{ + byte *p; + int hashes=0; + unsigned int len2; + + len2 = check_trailing_ws( line, len ); + if( !len2 ) { + afx->buffer_pos = len2; /* (it is not the fine way to do it here) */ + return 0; /* WS only: same as empty line */ + } + + /* + This is fussy. The spec says that a header line is delimited + with a colon-space pair. This means that a line such as + "Comment: " (with nothing else) is actually legal as an empty + string comment. However, email and cut-and-paste being what it + is, that trailing space may go away. Therefore, we accept empty + headers delimited with only a colon. --rfc2440, as always, + makes this strict and enforces the colon-space pair. -dms + */ + + p = strchr( line, ':'); + if( !p || (RFC2440 && p[1]!=' ') + || (!RFC2440 && p[1]!=' ' && p[1]!='\n' && p[1]!='\r')) + { + log_error(_("invalid armor header: ")); + print_string( stderr, line, len, 0 ); + putc('\n', stderr); + return -1; + } + + /* Chop off the whitespace we detected before */ + len=len2; + line[len2]='\0'; + + if( opt.verbose ) { + log_info(_("armor header: ")); + print_string( stderr, line, len, 0 ); + putc('\n', stderr); + } + + if( afx->in_cleartext ) { + if( (hashes=parse_hash_header( line )) ) + afx->hashes |= hashes; + else if( strlen(line) > 15 && !memcmp( line, "NotDashEscaped:", 15 ) ) + afx->not_dash_escaped = 1; + else { + log_error(_("invalid clearsig header\n")); + return -1; + } + } + return 1; +} + + + +/* figure out whether the data is armored or not */ +static int +check_input( armor_filter_context_t *afx, IOBUF a ) +{ + int rc = 0; + int i; + byte *line; + unsigned len; + unsigned maxlen; + int hdr_line = -1; + + /* read the first line to see whether this is armored data */ + maxlen = MAX_LINELEN; + len = afx->buffer_len = iobuf_read_line( a, &afx->buffer, + &afx->buffer_size, &maxlen ); + line = afx->buffer; + if( !maxlen ) { + /* line has been truncated: assume not armored */ + afx->inp_checked = 1; + afx->inp_bypass = 1; + return 0; + } + + if( !len ) { + return -1; /* eof */ + } + + /* (the line is always a C string but maybe longer) */ + if( *line == '\n' || ( len && (*line == '\r' && line[1]=='\n') ) ) + ; + else if( !is_armored( line ) ) { + afx->inp_checked = 1; + afx->inp_bypass = 1; + return 0; + } + + /* find the armor header */ + while(len) { + i = is_armor_header( line, len ); + if( i >= 0 && !(afx->only_keyblocks && i != 1 && i != 5 && i != 6 )) { + hdr_line = i; + if( hdr_line == BEGIN_SIGNED_MSG_IDX ) { + if( afx->in_cleartext ) { + log_error(_("nested clear text signatures\n")); + rc = G10ERR_INVALID_ARMOR; + } + afx->in_cleartext = 1; + } + break; + } + /* read the next line (skip all truncated lines) */ + do { + maxlen = MAX_LINELEN; + afx->buffer_len = iobuf_read_line( a, &afx->buffer, + &afx->buffer_size, &maxlen ); + line = afx->buffer; + len = afx->buffer_len; + } while( !maxlen ); + } + + /* Parse the header lines. */ + while(len) { + /* Read the next line (skip all truncated lines). */ + do { + maxlen = MAX_LINELEN; + afx->buffer_len = iobuf_read_line( a, &afx->buffer, + &afx->buffer_size, &maxlen ); + line = afx->buffer; + len = afx->buffer_len; + } while( !maxlen ); + + i = parse_header_line( afx, line, len ); + if( i <= 0 ) { + if (i && RFC2440) + rc = G10ERR_INVALID_ARMOR; + break; + } + } + + + if( rc ) + invalid_armor(); + else if( afx->in_cleartext ) + afx->faked = 1; + else { + afx->inp_checked = 1; + afx->crc = CRCINIT; + afx->idx = 0; + afx->radbuf[0] = 0; + } + + return rc; +} + +#define PARTIAL_CHUNK 512 +#define PARTIAL_POW 9 + +/**************** + * Fake a literal data packet and wait for the next armor line + * fixme: empty line handling and null length clear text signature are + * not implemented/checked. + */ +static int +fake_packet( armor_filter_context_t *afx, IOBUF a, + size_t *retn, byte *buf, size_t size ) +{ + int rc = 0; + size_t len = 0; + int lastline = 0; + unsigned maxlen, n; + byte *p; + byte tempbuf[PARTIAL_CHUNK]; + size_t tempbuf_len=0; + + while( !rc && size-len>=(PARTIAL_CHUNK+1)) { + /* copy what we have in the line buffer */ + if( afx->faked == 1 ) + afx->faked++; /* skip the first (empty) line */ + else + { + /* It's full, so write this partial chunk */ + if(tempbuf_len==PARTIAL_CHUNK) + { + buf[len++]=0xE0+PARTIAL_POW; + memcpy(&buf[len],tempbuf,PARTIAL_CHUNK); + len+=PARTIAL_CHUNK; + tempbuf_len=0; + continue; + } + + while( tempbuf_len < PARTIAL_CHUNK + && afx->buffer_pos < afx->buffer_len ) + tempbuf[tempbuf_len++] = afx->buffer[afx->buffer_pos++]; + if( tempbuf_len==PARTIAL_CHUNK ) + continue; + } + + /* read the next line */ + maxlen = MAX_LINELEN; + afx->buffer_pos = 0; + afx->buffer_len = iobuf_read_line( a, &afx->buffer, + &afx->buffer_size, &maxlen ); + if( !afx->buffer_len ) { + rc = -1; /* eof (should not happen) */ + continue; + } + if( !maxlen ) + afx->truncated++; + + p = afx->buffer; + n = afx->buffer_len; + + /* Armor header or dash-escaped line? */ + if(p[0]=='-') + { + /* 2440bis-10: When reversing dash-escaping, an + implementation MUST strip the string "- " if it occurs + at the beginning of a line, and SHOULD warn on "-" and + any character other than a space at the beginning of a + line. */ + + if(p[1]==' ' && !afx->not_dash_escaped) + { + /* It's a dash-escaped line, so skip over the + escape. */ + afx->buffer_pos = 2; + } + else if(p[1]=='-' && p[2]=='-' && p[3]=='-' && p[4]=='-') + { + /* Five dashes in a row mean it's probably armor + header. */ + int type = is_armor_header( p, n ); + if( afx->not_dash_escaped && type != BEGIN_SIGNATURE ) + ; /* this is okay */ + else + { + if( type != BEGIN_SIGNATURE ) + { + log_info(_("unexpected armor: ")); + print_string( stderr, p, n, 0 ); + putc('\n', stderr); + } + + lastline = 1; + rc = -1; + } + } + else if(!afx->not_dash_escaped) + { + /* Bad dash-escaping. */ + log_info(_("invalid dash escaped line: ")); + print_string( stderr, p, n, 0 ); + putc('\n', stderr); + } + } + + /* Now handle the end-of-line canonicalization */ + if( !afx->not_dash_escaped ) + { + int crlf = n > 1 && p[n-2] == '\r' && p[n-1]=='\n'; + + /* PGP2 does not treat a tab as white space character */ + afx->buffer_len= + trim_trailing_chars( &p[afx->buffer_pos], n-afx->buffer_pos, + afx->pgp2mode ? " \r\n" : " \t\r\n"); + afx->buffer_len+=afx->buffer_pos; + /* the buffer is always allocated with enough space to append + * the removed [CR], LF and a Nul + * The reason for this complicated procedure is to keep at least + * the original type of lineending - handling of the removed + * trailing spaces seems to be impossible in our method + * of faking a packet; either we have to use a temporary file + * or calculate the hash here in this module and somehow find + * a way to send the hash down the processing line (well, a special + * faked packet could do the job). + */ + if( crlf ) + afx->buffer[afx->buffer_len++] = '\r'; + afx->buffer[afx->buffer_len++] = '\n'; + afx->buffer[afx->buffer_len] = '\0'; + } + } + + if( lastline ) { /* write last (ending) length header */ + if(tempbuf_len<192) + buf[len++]=tempbuf_len; + else + { + buf[len++]=((tempbuf_len-192)/256) + 192; + buf[len++]=(tempbuf_len-192) % 256; + } + memcpy(&buf[len],tempbuf,tempbuf_len); + len+=tempbuf_len; + + rc = 0; + afx->faked = 0; + afx->in_cleartext = 0; + /* and now read the header lines */ + afx->buffer_pos = 0; + for(;;) { + int i; + + /* read the next line (skip all truncated lines) */ + do { + maxlen = MAX_LINELEN; + afx->buffer_len = iobuf_read_line( a, &afx->buffer, + &afx->buffer_size, &maxlen ); + } while( !maxlen ); + p = afx->buffer; + n = afx->buffer_len; + if( !n ) { + rc = -1; + break; /* eof */ + } + i = parse_header_line( afx, p , n ); + if( i <= 0 ) { + if( i ) + invalid_armor(); + break; + } + } + afx->inp_checked = 1; + afx->crc = CRCINIT; + afx->idx = 0; + afx->radbuf[0] = 0; + } + + *retn = len; + return rc; +} + + +static int +invalid_crc(void) +{ + if ( opt.ignore_crc_error ) + return 0; + log_inc_errorcount(); + return G10ERR_INVALID_ARMOR; +} + + +static int +radix64_read( armor_filter_context_t *afx, IOBUF a, size_t *retn, + byte *buf, size_t size ) +{ + byte val; + int c=0, c2; /*init c because gcc is not clever enough for the continue*/ + int checkcrc=0; + int rc = 0; + size_t n = 0; + int idx, i, onlypad=0; + u32 crc; + + crc = afx->crc; + idx = afx->idx; + val = afx->radbuf[0]; + for( n=0; n < size; ) { + + if( afx->buffer_pos < afx->buffer_len ) + c = afx->buffer[afx->buffer_pos++]; + else { /* read the next line */ + unsigned maxlen = MAX_LINELEN; + afx->buffer_pos = 0; + afx->buffer_len = iobuf_read_line( a, &afx->buffer, + &afx->buffer_size, &maxlen ); + if( !maxlen ) + afx->truncated++; + if( !afx->buffer_len ) + break; /* eof */ + continue; + } + + again: + if( c == '\n' || c == ' ' || c == '\r' || c == '\t' ) + continue; + else if( c == '=' ) { /* pad character: stop */ + /* some mailers leave quoted-printable encoded characters + * so we try to workaround this */ + if( afx->buffer_pos+2 < afx->buffer_len ) { + int cc1, cc2, cc3; + cc1 = afx->buffer[afx->buffer_pos]; + cc2 = afx->buffer[afx->buffer_pos+1]; + cc3 = afx->buffer[afx->buffer_pos+2]; + if( isxdigit(cc1) && isxdigit(cc2) + && strchr( "=\n\r\t ", cc3 )) { + /* well it seems to be the case - adjust */ + c = isdigit(cc1)? (cc1 - '0'): (ascii_toupper(cc1)-'A'+10); + c <<= 4; + c |= isdigit(cc2)? (cc2 - '0'): (ascii_toupper(cc2)-'A'+10); + afx->buffer_pos += 2; + afx->qp_detected = 1; + goto again; + } + } + else if(n==0) + onlypad=1; + + if( idx == 1 ) + buf[n++] = val; + checkcrc++; + break; + } + else if( (c = asctobin[(c2=c)]) == 255 ) { + log_error(_("invalid radix64 character %02X skipped\n"), c2); + continue; + } + switch(idx) { + case 0: val = c << 2; break; + case 1: val |= (c>>4)&3; buf[n++]=val;val=(c<<4)&0xf0;break; + case 2: val |= (c>>2)&15; buf[n++]=val;val=(c<<6)&0xc0;break; + case 3: val |= c&0x3f; buf[n++] = val; break; + } + idx = (idx+1) % 4; + } + + for(i=0; i < n; i++ ) + crc = (crc << 8) ^ crc_table[((crc >> 16)&0xff) ^ buf[i]]; + crc &= 0x00ffffff; + afx->crc = crc; + afx->idx = idx; + afx->radbuf[0] = val; + + if( checkcrc ) { + afx->any_data = 1; + afx->inp_checked=0; + afx->faked = 0; + for(;;) { /* skip lf and pad characters */ + if( afx->buffer_pos < afx->buffer_len ) + c = afx->buffer[afx->buffer_pos++]; + else { /* read the next line */ + unsigned maxlen = MAX_LINELEN; + afx->buffer_pos = 0; + afx->buffer_len = iobuf_read_line( a, &afx->buffer, + &afx->buffer_size, &maxlen ); + if( !maxlen ) + afx->truncated++; + if( !afx->buffer_len ) + break; /* eof */ + continue; + } + if( c == '\n' || c == ' ' || c == '\r' + || c == '\t' || c == '=' ) + continue; + break; + } + if( c == -1 ) + log_error(_("premature eof (no CRC)\n")); + else { + u32 mycrc = 0; + idx = 0; + do { + if( (c = asctobin[c]) == 255 ) + break; + switch(idx) { + case 0: val = c << 2; break; + case 1: val |= (c>>4)&3; mycrc |= val << 16;val=(c<<4)&0xf0;break; + case 2: val |= (c>>2)&15; mycrc |= val << 8;val=(c<<6)&0xc0;break; + case 3: val |= c&0x3f; mycrc |= val; break; + } + for(;;) { + if( afx->buffer_pos < afx->buffer_len ) + c = afx->buffer[afx->buffer_pos++]; + else { /* read the next line */ + unsigned maxlen = MAX_LINELEN; + afx->buffer_pos = 0; + afx->buffer_len = iobuf_read_line( a, &afx->buffer, + &afx->buffer_size, + &maxlen ); + if( !maxlen ) + afx->truncated++; + if( !afx->buffer_len ) + break; /* eof */ + continue; + } + break; + } + if( !afx->buffer_len ) + break; /* eof */ + } while( ++idx < 4 ); + if( c == -1 ) { + log_info(_("premature eof (in CRC)\n")); + rc = invalid_crc(); + } + else if( idx == 0 ) { + /* No CRC at all is legal ("MAY") */ + rc=0; + } + else if( idx != 4 ) { + log_info(_("malformed CRC\n")); + rc = invalid_crc(); + } + else if( mycrc != afx->crc ) { + log_info (_("CRC error; %06lX - %06lX\n"), + (ulong)afx->crc, (ulong)mycrc); + rc = invalid_crc(); + } + else { + rc = 0; + /* FIXME: Here we should emit another control packet, + * so that we know in mainproc that we are processing + * a clearsign message */ +#if 0 + for(rc=0;!rc;) { + rc = 0 /*check_trailer( &fhdr, c )*/; + if( !rc ) { + if( (c=iobuf_get(a)) == -1 ) + rc = 2; + } + } + if( rc == -1 ) + rc = 0; + else if( rc == 2 ) { + log_error(_("premature eof (in trailer)\n")); + rc = G10ERR_INVALID_ARMOR; + } + else { + log_error(_("error in trailer line\n")); + rc = G10ERR_INVALID_ARMOR; + } +#endif + } + } + } + + if( !n && !onlypad ) + rc = -1; + + *retn = n; + return rc; +} + +/**************** + * This filter is used to handle the armor stuff + */ +int +armor_filter( void *opaque, int control, + IOBUF a, byte *buf, size_t *ret_len) +{ + size_t size = *ret_len; + armor_filter_context_t *afx = opaque; + int rc=0, i, c; + byte radbuf[3]; + int idx, idx2; + size_t n=0; + u32 crc; +#if 0 + static FILE *fp ; + + if( !fp ) { + fp = fopen("armor.out", "w"); + assert(fp); + } +#endif + + if( DBG_FILTER ) + log_debug("armor-filter: control: %d\n", control ); + if( control == IOBUFCTRL_UNDERFLOW && afx->inp_bypass ) { + n = 0; + if( afx->buffer_len ) { + for(; n < size && afx->buffer_pos < afx->buffer_len; n++ ) + buf[n++] = afx->buffer[afx->buffer_pos++]; + if( afx->buffer_pos >= afx->buffer_len ) + afx->buffer_len = 0; + } + for(; n < size; n++ ) { + if( (c=iobuf_get(a)) == -1 ) + break; + buf[n] = c & 0xff; + } + if( !n ) + rc = -1; + *ret_len = n; + } + else if( control == IOBUFCTRL_UNDERFLOW ) { + /* We need some space for the faked packet. The minmum + * required size is the PARTIAL_CHUNK size plus a byte for the + * length itself */ + if( size < PARTIAL_CHUNK+1 ) + BUG(); /* supplied buffer too short */ + + if( afx->faked ) + rc = fake_packet( afx, a, &n, buf, size ); + else if( !afx->inp_checked ) { + rc = check_input( afx, a ); + if( afx->inp_bypass ) { + for(n=0; n < size && afx->buffer_pos < afx->buffer_len; ) + buf[n++] = afx->buffer[afx->buffer_pos++]; + if( afx->buffer_pos >= afx->buffer_len ) + afx->buffer_len = 0; + if( !n ) + rc = -1; + } + else if( afx->faked ) { + unsigned int hashes = afx->hashes; + const byte *sesmark; + size_t sesmarklen; + + sesmark = get_session_marker( &sesmarklen ); + if ( sesmarklen > 20 ) + BUG(); + + /* the buffer is at least 15+n*15 bytes long, so it + * is easy to construct the packets */ + + hashes &= 1|2|4|8|16|32|64; + if( !hashes ) { + hashes |= 4; /* default to MD 5 */ + /* This is non-ideal since PGP 5-8 have the same + end-of-line bugs as PGP 2. However, we only + enable pgp2mode if there is no Hash: header. */ + if( opt.pgp2_workarounds ) + afx->pgp2mode = 1; + } + n=0; + /* First a gpg control packet... */ + buf[n++] = 0xff; /* new format, type 63, 1 length byte */ + n++; /* see below */ + memcpy(buf+n, sesmark, sesmarklen ); n+= sesmarklen; + buf[n++] = CTRLPKT_CLEARSIGN_START; + buf[n++] = afx->not_dash_escaped? 0:1; /* sigclass */ + if( hashes & 1 ) + buf[n++] = DIGEST_ALGO_RMD160; + if( hashes & 2 ) + buf[n++] = DIGEST_ALGO_SHA1; + if( hashes & 4 ) + buf[n++] = DIGEST_ALGO_MD5; + if( hashes & 8 ) + buf[n++] = DIGEST_ALGO_SHA224; + if( hashes & 16 ) + buf[n++] = DIGEST_ALGO_SHA256; + if( hashes & 32 ) + buf[n++] = DIGEST_ALGO_SHA384; + if( hashes & 64 ) + buf[n++] = DIGEST_ALGO_SHA512; + buf[1] = n - 2; + + /* ...followed by an invented plaintext packet. + Amusingly enough, this packet is not compliant with + 2440 as the initial partial length is less than 512 + bytes. Of course, we'll accept it anyway ;) */ + + buf[n++] = 0xCB; /* new packet format, type 11 */ + buf[n++] = 0xE1; /* 2^1 == 2 bytes */ + buf[n++] = 't'; /* canonical text mode */ + buf[n++] = 0; /* namelength */ + buf[n++] = 0xE2; /* 2^2 == 4 more bytes */ + memset(buf+n, 0, 4); /* timestamp */ + n += 4; + } + else if( !rc ) + rc = radix64_read( afx, a, &n, buf, size ); + } + else + rc = radix64_read( afx, a, &n, buf, size ); +#if 0 + if( n ) + if( fwrite(buf, n, 1, fp ) != 1 ) + BUG(); +#endif + *ret_len = n; + } + else if( control == IOBUFCTRL_FLUSH && !afx->cancel ) { + if( !afx->status ) { /* write the header line */ + const char *s; + STRLIST comment=opt.comments; + + if( afx->what >= DIM(head_strings) ) + log_bug("afx->what=%d", afx->what); + iobuf_writestr(a, "-----"); + iobuf_writestr(a, head_strings[afx->what] ); + iobuf_writestr(a, "-----" ); + iobuf_writestr(a,afx->eol); + if( !opt.no_version ) + { + iobuf_writestr(a, "Version: GnuPG v" VERSION " (" + PRINTABLE_OS_NAME ")" ); + iobuf_writestr(a,afx->eol); + } + + /* write the comment strings */ + for(s=comment->d;comment;comment=comment->next,s=comment->d) + { + iobuf_writestr(a, "Comment: " ); + for( ; *s; s++ ) + { + if( *s == '\n' ) + iobuf_writestr(a, "\\n" ); + else if( *s == '\r' ) + iobuf_writestr(a, "\\r" ); + else if( *s == '\v' ) + iobuf_writestr(a, "\\v" ); + else + iobuf_put(a, *s ); + } + + iobuf_writestr(a,afx->eol); + } + + if ( afx->hdrlines ) { + for ( s = afx->hdrlines; *s; s++ ) { +#ifdef HAVE_DOSISH_SYSTEM + if ( *s == '\n' ) + iobuf_put( a, '\r'); +#endif + iobuf_put(a, *s ); + } + } + + iobuf_writestr(a,afx->eol); + afx->status++; + afx->idx = 0; + afx->idx2 = 0; + afx->crc = CRCINIT; + + } + crc = afx->crc; + idx = afx->idx; + idx2 = afx->idx2; + for(i=0; i < idx; i++ ) + radbuf[i] = afx->radbuf[i]; + + for(i=0; i < size; i++ ) + crc = (crc << 8) ^ crc_table[((crc >> 16)&0xff) ^ buf[i]]; + crc &= 0x00ffffff; + + for( ; size; buf++, size-- ) { + radbuf[idx++] = *buf; + if( idx > 2 ) { + idx = 0; + c = bintoasc[(*radbuf >> 2) & 077]; + iobuf_put(a, c); + c = bintoasc[(((*radbuf<<4)&060)|((radbuf[1] >> 4)&017))&077]; + iobuf_put(a, c); + c = bintoasc[(((radbuf[1]<<2)&074)|((radbuf[2]>>6)&03))&077]; + iobuf_put(a, c); + c = bintoasc[radbuf[2]&077]; + iobuf_put(a, c); + if( ++idx2 >= (64/4) ) + { /* pgp doesn't like 72 here */ + iobuf_writestr(a,afx->eol); + idx2=0; + } + } + } + for(i=0; i < idx; i++ ) + afx->radbuf[i] = radbuf[i]; + afx->idx = idx; + afx->idx2 = idx2; + afx->crc = crc; + } + else if( control == IOBUFCTRL_INIT ) + { + if( !is_initialized ) + initialize(); + + /* Figure out what we're using for line endings if the caller + didn't specify. */ + if(afx->eol[0]==0) + { +#ifdef HAVE_DOSISH_SYSTEM + afx->eol[0]='\r'; + afx->eol[1]='\n'; +#else + afx->eol[0]='\n'; +#endif + } + } + else if( control == IOBUFCTRL_CANCEL ) { + afx->cancel = 1; + } + else if( control == IOBUFCTRL_FREE ) { + if( afx->cancel ) + ; + else if( afx->status ) { /* pad, write cecksum, and bottom line */ + crc = afx->crc; + idx = afx->idx; + idx2 = afx->idx2; + for(i=0; i < idx; i++ ) + radbuf[i] = afx->radbuf[i]; + if( idx ) { + c = bintoasc[(*radbuf>>2)&077]; + iobuf_put(a, c); + if( idx == 1 ) { + c = bintoasc[((*radbuf << 4) & 060) & 077]; + iobuf_put(a, c); + iobuf_put(a, '='); + iobuf_put(a, '='); + } + else { /* 2 */ + c = bintoasc[(((*radbuf<<4)&060)|((radbuf[1]>>4)&017))&077]; + iobuf_put(a, c); + c = bintoasc[((radbuf[1] << 2) & 074) & 077]; + iobuf_put(a, c); + iobuf_put(a, '='); + } + if( ++idx2 >= (64/4) ) + { /* pgp doesn't like 72 here */ + iobuf_writestr(a,afx->eol); + idx2=0; + } + } + /* may need a linefeed */ + if( idx2 ) + iobuf_writestr(a,afx->eol); + /* write the CRC */ + iobuf_put(a, '='); + radbuf[0] = crc >>16; + radbuf[1] = crc >> 8; + radbuf[2] = crc; + c = bintoasc[(*radbuf >> 2) & 077]; + iobuf_put(a, c); + c = bintoasc[(((*radbuf<<4)&060)|((radbuf[1] >> 4)&017))&077]; + iobuf_put(a, c); + c = bintoasc[(((radbuf[1]<<2)&074)|((radbuf[2]>>6)&03))&077]; + iobuf_put(a, c); + c = bintoasc[radbuf[2]&077]; + iobuf_put(a, c); + iobuf_writestr(a,afx->eol); + /* and the the trailer */ + if( afx->what >= DIM(tail_strings) ) + log_bug("afx->what=%d", afx->what); + iobuf_writestr(a, "-----"); + iobuf_writestr(a, tail_strings[afx->what] ); + iobuf_writestr(a, "-----" ); + iobuf_writestr(a,afx->eol); + } + else if( !afx->any_data && !afx->inp_bypass ) { + log_error(_("no valid OpenPGP data found.\n")); + afx->no_openpgp_data = 1; + write_status_text( STATUS_NODATA, "1" ); + } + if( afx->truncated ) + log_info(_("invalid armor: line longer than %d characters\n"), + MAX_LINELEN ); + /* issue an error to enforce dissemination of correct software */ + if( afx->qp_detected ) + log_error(_("quoted printable character in armor - " + "probably a buggy MTA has been used\n") ); + xfree( afx->buffer ); + afx->buffer = NULL; + release_armor_context (afx); + } + else if( control == IOBUFCTRL_DESC ) + *(char**)buf = "armor_filter"; + return rc; +} + + +/**************** + * create a radix64 encoded string. + */ +char * +make_radix64_string( const byte *data, size_t len ) +{ + char *buffer, *p; + + buffer = p = xmalloc( (len+2)/3*4 + 1 ); + for( ; len >= 3 ; len -= 3, data += 3 ) { + *p++ = bintoasc[(data[0] >> 2) & 077]; + *p++ = bintoasc[(((data[0] <<4)&060)|((data[1] >> 4)&017))&077]; + *p++ = bintoasc[(((data[1]<<2)&074)|((data[2]>>6)&03))&077]; + *p++ = bintoasc[data[2]&077]; + } + if( len == 2 ) { + *p++ = bintoasc[(data[0] >> 2) & 077]; + *p++ = bintoasc[(((data[0] <<4)&060)|((data[1] >> 4)&017))&077]; + *p++ = bintoasc[((data[1]<<2)&074)]; + } + else if( len == 1 ) { + *p++ = bintoasc[(data[0] >> 2) & 077]; + *p++ = bintoasc[(data[0] <<4)&060]; + } + *p = 0; + return buffer; +} + + +/*********************************************** + * For the pipemode command we can't use the armor filter for various + * reasons, so we use this new unarmor_pump stuff to remove the armor + */ + +enum unarmor_state_e { + STA_init = 0, + STA_bypass, + STA_wait_newline, + STA_wait_dash, + STA_first_dash, + STA_compare_header, + STA_found_header_wait_newline, + STA_skip_header_lines, + STA_skip_header_lines_non_ws, + STA_read_data, + STA_wait_crc, + STA_read_crc, + STA_ready +}; + +struct unarmor_pump_s { + enum unarmor_state_e state; + byte val; + int checkcrc; + int pos; /* counts from 0..3 */ + u32 crc; + u32 mycrc; /* the one store in the data */ +}; + + + +UnarmorPump +unarmor_pump_new (void) +{ + UnarmorPump x; + + if( !is_initialized ) + initialize(); + x = xmalloc_clear (sizeof *x); + return x; +} + +void +unarmor_pump_release (UnarmorPump x) +{ + xfree (x); +} + +/* + * Get the next character from the ascii armor taken from the IOBUF + * created earlier by unarmor_pump_new(). + * Return: c = Character + * 256 = ignore this value + * -1 = End of current armor + * -2 = Premature EOF (not used) + * -3 = Invalid armor + */ +int +unarmor_pump (UnarmorPump x, int c) +{ + int rval = 256; /* default is to ignore the return value */ + + switch (x->state) { + case STA_init: + { + byte tmp[1]; + tmp[0] = c; + if ( is_armored (tmp) ) + x->state = c == '-'? STA_first_dash : STA_wait_newline; + else { + x->state = STA_bypass; + return c; + } + } + break; + case STA_bypass: + return c; /* return here to avoid crc calculation */ + case STA_wait_newline: + if (c == '\n') + x->state = STA_wait_dash; + break; + case STA_wait_dash: + x->state = c == '-'? STA_first_dash : STA_wait_newline; + break; + case STA_first_dash: /* just need for initalization */ + x->pos = 0; + x->state = STA_compare_header; + case STA_compare_header: + if ( "-----BEGIN PGP SIGNATURE-----"[++x->pos] == c ) { + if ( x->pos == 28 ) + x->state = STA_found_header_wait_newline; + } + else + x->state = c == '\n'? STA_wait_dash : STA_wait_newline; + break; + case STA_found_header_wait_newline: + /* to make CR,LF issues easier we simply allow for white space + behind the 5 dashes */ + if ( c == '\n' ) + x->state = STA_skip_header_lines; + else if ( c != '\r' && c != ' ' && c != '\t' ) + x->state = STA_wait_dash; /* garbage after the header line */ + break; + case STA_skip_header_lines: + /* i.e. wait for one empty line */ + if ( c == '\n' ) { + x->state = STA_read_data; + x->crc = CRCINIT; + x->val = 0; + x->pos = 0; + } + else if ( c != '\r' && c != ' ' && c != '\t' ) + x->state = STA_skip_header_lines_non_ws; + break; + case STA_skip_header_lines_non_ws: + /* like above but we already encountered non white space */ + if ( c == '\n' ) + x->state = STA_skip_header_lines; + break; + case STA_read_data: + /* fixme: we don't check for the trailing dash lines but rely + * on the armor stop characters */ + if( c == '\n' || c == ' ' || c == '\r' || c == '\t' ) + break; /* skip all kind of white space */ + + if( c == '=' ) { /* pad character: stop */ + if( x->pos == 1 ) /* in this case val has some value */ + rval = x->val; + x->state = STA_wait_crc; + break; + } + + { + int c2; + if( (c = asctobin[(c2=c)]) == 255 ) { + log_error(_("invalid radix64 character %02X skipped\n"), c2); + break; + } + } + + switch(x->pos) { + case 0: + x->val = c << 2; + break; + case 1: + x->val |= (c>>4)&3; + rval = x->val; + x->val = (c<<4)&0xf0; + break; + case 2: + x->val |= (c>>2)&15; + rval = x->val; + x->val = (c<<6)&0xc0; + break; + case 3: + x->val |= c&0x3f; + rval = x->val; + break; + } + x->pos = (x->pos+1) % 4; + break; + case STA_wait_crc: + if( c == '\n' || c == ' ' || c == '\r' || c == '\t' || c == '=' ) + break; /* skip ws and pad characters */ + /* assume that we are at the next line */ + x->state = STA_read_crc; + x->pos = 0; + x->mycrc = 0; + case STA_read_crc: + if( (c = asctobin[c]) == 255 ) { + rval = -1; /* ready */ + if( x->crc != x->mycrc ) { + log_info (_("CRC error; %06lX - %06lX\n"), + (ulong)x->crc, (ulong)x->mycrc); + if ( invalid_crc() ) + rval = -3; + } + x->state = STA_ready; /* not sure whether this is correct */ + break; + } + + switch(x->pos) { + case 0: + x->val = c << 2; + break; + case 1: + x->val |= (c>>4)&3; + x->mycrc |= x->val << 16; + x->val = (c<<4)&0xf0; + break; + case 2: + x->val |= (c>>2)&15; + x->mycrc |= x->val << 8; + x->val = (c<<6)&0xc0; + break; + case 3: + x->val |= c&0x3f; + x->mycrc |= x->val; + break; + } + x->pos = (x->pos+1) % 4; + break; + case STA_ready: + rval = -1; + break; + } + + if ( !(rval & ~255) ) { /* compute the CRC */ + x->crc = (x->crc << 8) ^ crc_table[((x->crc >> 16)&0xff) ^ rval]; + x->crc &= 0x00ffffff; + } + + return rval; +} diff --git a/g10/build-packet.c b/g10/build-packet.c new file mode 100644 index 0000000..a7ac5d8 --- /dev/null +++ b/g10/build-packet.c @@ -0,0 +1,1308 @@ +/* build-packet.c - assemble packets and write them + * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, + * 2006 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <ctype.h> + +#include "packet.h" +#include "errors.h" +#include "iobuf.h" +#include "mpi.h" +#include "util.h" +#include "cipher.h" +#include "memory.h" +#include "i18n.h" +#include "options.h" + +static int do_user_id( IOBUF out, int ctb, PKT_user_id *uid ); +static int do_public_key( IOBUF out, int ctb, PKT_public_key *pk ); +static int do_secret_key( IOBUF out, int ctb, PKT_secret_key *pk ); +static int do_symkey_enc( IOBUF out, int ctb, PKT_symkey_enc *enc ); +static int do_pubkey_enc( IOBUF out, int ctb, PKT_pubkey_enc *enc ); +static u32 calc_plaintext( PKT_plaintext *pt ); +static int do_plaintext( IOBUF out, int ctb, PKT_plaintext *pt ); +static int do_encrypted( IOBUF out, int ctb, PKT_encrypted *ed ); +static int do_encrypted_mdc( IOBUF out, int ctb, PKT_encrypted *ed ); +static int do_compressed( IOBUF out, int ctb, PKT_compressed *cd ); +static int do_signature( IOBUF out, int ctb, PKT_signature *sig ); +static int do_onepass_sig( IOBUF out, int ctb, PKT_onepass_sig *ops ); + +static int calc_header_length( u32 len, int new_ctb ); +static int write_16(IOBUF inp, u16 a); +static int write_32(IOBUF inp, u32 a); +static int write_header( IOBUF out, int ctb, u32 len ); +static int write_sign_packet_header( IOBUF out, int ctb, u32 len ); +static int write_header2( IOBUF out, int ctb, u32 len, int hdrlen ); +static int write_new_header( IOBUF out, int ctb, u32 len, int hdrlen ); +static int write_version( IOBUF out, int ctb ); + +/**************** + * Build a packet and write it to INP + * Returns: 0 := okay + * >0 := error + * Note: Caller must free the packet + */ +int +build_packet( IOBUF out, PACKET *pkt ) +{ + int new_ctb=0, rc=0, ctb; + int pkttype; + + if( DBG_PACKET ) + log_debug("build_packet() type=%d\n", pkt->pkttype ); + assert( pkt->pkt.generic ); + + switch( (pkttype = pkt->pkttype) ) + { + case PKT_PLAINTEXT: new_ctb = pkt->pkt.plaintext->new_ctb; break; + case PKT_ENCRYPTED: + case PKT_ENCRYPTED_MDC: new_ctb = pkt->pkt.encrypted->new_ctb; break; + case PKT_COMPRESSED:new_ctb = pkt->pkt.compressed->new_ctb; break; + case PKT_USER_ID: + if( pkt->pkt.user_id->attrib_data ) + pkttype = PKT_ATTRIBUTE; + break; + default: break; + } + + if( new_ctb || pkttype > 15 ) /* new format */ + ctb = 0xc0 | (pkttype & 0x3f); + else + ctb = 0x80 | ((pkttype & 15)<<2); + switch( pkttype ) + { + case PKT_ATTRIBUTE: + case PKT_USER_ID: + rc = do_user_id( out, ctb, pkt->pkt.user_id ); + break; + case PKT_OLD_COMMENT: + case PKT_COMMENT: + /* + Ignore these. Theoretically, this will never be called as + we have no way to output comment packets any longer, but + just in case there is some code path that would end up + outputting a comment that was written before comments were + dropped (in the public key?) this is a no-op. + */ + break; + case PKT_PUBLIC_SUBKEY: + case PKT_PUBLIC_KEY: + rc = do_public_key( out, ctb, pkt->pkt.public_key ); + break; + case PKT_SECRET_SUBKEY: + case PKT_SECRET_KEY: + rc = do_secret_key( out, ctb, pkt->pkt.secret_key ); + break; + case PKT_SYMKEY_ENC: + rc = do_symkey_enc( out, ctb, pkt->pkt.symkey_enc ); + break; + case PKT_PUBKEY_ENC: + rc = do_pubkey_enc( out, ctb, pkt->pkt.pubkey_enc ); + break; + case PKT_PLAINTEXT: + rc = do_plaintext( out, ctb, pkt->pkt.plaintext ); + break; + case PKT_ENCRYPTED: + rc = do_encrypted( out, ctb, pkt->pkt.encrypted ); + break; + case PKT_ENCRYPTED_MDC: + rc = do_encrypted_mdc( out, ctb, pkt->pkt.encrypted ); + break; + case PKT_COMPRESSED: + rc = do_compressed( out, ctb, pkt->pkt.compressed ); + break; + case PKT_SIGNATURE: + rc = do_signature( out, ctb, pkt->pkt.signature ); + break; + case PKT_ONEPASS_SIG: + rc = do_onepass_sig( out, ctb, pkt->pkt.onepass_sig ); + break; + case PKT_RING_TRUST: + break; /* ignore it (keyring.c does write it directly)*/ + case PKT_MDC: /* we write it directly, so we should never see it here. */ + default: + log_bug("invalid packet type in build_packet()\n"); + break; + } + + return rc; +} + +/**************** + * calculate the length of a packet described by PKT + */ +u32 +calc_packet_length( PACKET *pkt ) +{ + u32 n=0; + int new_ctb = 0; + + assert( pkt->pkt.generic ); + switch( pkt->pkttype ) { + case PKT_PLAINTEXT: + n = calc_plaintext( pkt->pkt.plaintext ); + new_ctb = pkt->pkt.plaintext->new_ctb; + break; + case PKT_ATTRIBUTE: + case PKT_USER_ID: + case PKT_COMMENT: + case PKT_PUBLIC_KEY: + case PKT_SECRET_KEY: + case PKT_SYMKEY_ENC: + case PKT_PUBKEY_ENC: + case PKT_ENCRYPTED: + case PKT_SIGNATURE: + case PKT_ONEPASS_SIG: + case PKT_RING_TRUST: + case PKT_COMPRESSED: + default: + log_bug("invalid packet type in calc_packet_length()"); + break; + } + + n += calc_header_length(n, new_ctb); + return n; +} + +static void +write_fake_data( IOBUF out, MPI a ) +{ + if( a ) { + unsigned int i; + void *p; + + p = mpi_get_opaque( a, &i ); + iobuf_write( out, p, i ); + } +} + +static int +do_user_id( IOBUF out, int ctb, PKT_user_id *uid ) +{ + if( uid->attrib_data ) + { + write_header(out, ctb, uid->attrib_len); + if( iobuf_write( out, uid->attrib_data, uid->attrib_len ) ) + return G10ERR_WRITE_FILE; + } + else + { + write_header2( out, ctb, uid->len, 2 ); + if( iobuf_write( out, uid->name, uid->len ) ) + return G10ERR_WRITE_FILE; + } + return 0; +} + +static int +do_public_key( IOBUF out, int ctb, PKT_public_key *pk ) +{ + int rc = 0; + int n, i; + IOBUF a = iobuf_temp(); + + if( !pk->version ) + iobuf_put( a, 3 ); + else + iobuf_put( a, pk->version ); + write_32(a, pk->timestamp ); + if( pk->version < 4 ) { + u16 ndays; + if( pk->expiredate ) + ndays = (u16)((pk->expiredate - pk->timestamp) / 86400L); + else + ndays = 0; + write_16(a, ndays ); + } + iobuf_put(a, pk->pubkey_algo ); + n = pubkey_get_npkey( pk->pubkey_algo ); + if( !n ) + write_fake_data( a, pk->pkey[0] ); + for(i=0; i < n; i++ ) + mpi_write(a, pk->pkey[i] ); + + write_header2(out, ctb, iobuf_get_temp_length(a), pk->hdrbytes); + if( iobuf_write_temp( out, a ) ) + rc = G10ERR_WRITE_FILE; + + iobuf_close(a); + return rc; +} + + +static int +do_secret_key( IOBUF out, int ctb, PKT_secret_key *sk ) +{ + int rc = 0; + int i, nskey, npkey; + IOBUF a = iobuf_temp(); /* build in a self-enlarging buffer */ + + /* Write the version number - if none is specified, use 3 */ + if( !sk->version ) + iobuf_put( a, 3 ); + else + iobuf_put( a, sk->version ); + write_32(a, sk->timestamp ); + + /* v3 needs the expiration time */ + if( sk->version < 4 ) { + u16 ndays; + if( sk->expiredate ) + ndays = (u16)((sk->expiredate - sk->timestamp) / 86400L); + else + ndays = 0; + write_16(a, ndays); + } + + iobuf_put(a, sk->pubkey_algo ); + + /* get number of secret and public parameters. They are held in + one array first the public ones, then the secret ones */ + nskey = pubkey_get_nskey( sk->pubkey_algo ); + npkey = pubkey_get_npkey( sk->pubkey_algo ); + + /* If we don't have any public parameters - which is the case if + we don't know the algorithm used - the parameters are stored as + one blob in a faked (opaque) MPI */ + if( !npkey ) { + write_fake_data( a, sk->skey[0] ); + goto leave; + } + assert( npkey < nskey ); + + /* Writing the public parameters is easy */ + for(i=0; i < npkey; i++ ) + mpi_write(a, sk->skey[i] ); + + /* build the header for protected (encrypted) secret parameters */ + if( sk->is_protected ) { + if( is_RSA(sk->pubkey_algo) && sk->version < 4 + && !sk->protect.s2k.mode ) { + /* the simple rfc1991 (v3) way */ + iobuf_put(a, sk->protect.algo ); + iobuf_write(a, sk->protect.iv, sk->protect.ivlen ); + } + else { + /* OpenPGP protection according to rfc2440 */ + iobuf_put(a, sk->protect.sha1chk? 0xfe : 0xff ); + iobuf_put(a, sk->protect.algo ); + if( sk->protect.s2k.mode >= 1000 ) { + /* These modes are not possible in OpenPGP, we use them + to implement our extensions, 101 can be seen as a + private/experimental extension (this is not + specified in rfc2440 but the same scheme is used + for all other algorithm identifiers) */ + iobuf_put(a, 101 ); + iobuf_put(a, sk->protect.s2k.hash_algo ); + iobuf_write(a, "GNU", 3 ); + iobuf_put(a, sk->protect.s2k.mode - 1000 ); + } + else { + iobuf_put(a, sk->protect.s2k.mode ); + iobuf_put(a, sk->protect.s2k.hash_algo ); + } + if( sk->protect.s2k.mode == 1 + || sk->protect.s2k.mode == 3 ) + iobuf_write(a, sk->protect.s2k.salt, 8 ); + if( sk->protect.s2k.mode == 3 ) + iobuf_put(a, sk->protect.s2k.count ); + + /* For out special modes 1001, 1002 we do not need an IV */ + if( sk->protect.s2k.mode != 1001 + && sk->protect.s2k.mode != 1002 ) + iobuf_write(a, sk->protect.iv, sk->protect.ivlen ); + } + } + else + iobuf_put(a, 0 ); + + if( sk->protect.s2k.mode == 1001 ) + ; /* GnuPG extension - don't write a secret key at all */ + else if( sk->protect.s2k.mode == 1002 ) + { /* GnuPG extension - divert to OpenPGP smartcard. */ + iobuf_put(a, sk->protect.ivlen ); /* length of the serial + number or 0 for no serial + number. */ + /* The serial number gets stored in the IV field. */ + iobuf_write(a, sk->protect.iv, sk->protect.ivlen); + } + else if( sk->is_protected && sk->version >= 4 ) { + /* The secret key is protected - write it out as it is */ + byte *p; + unsigned int ndata; + + assert( mpi_is_opaque( sk->skey[npkey] ) ); + p = mpi_get_opaque( sk->skey[npkey], &ndata ); + iobuf_write(a, p, ndata ); + } + else if( sk->is_protected ) { + /* The secret key is protected te old v4 way. */ + for( ; i < nskey; i++ ) { + byte *p; + unsigned int ndata; + + assert (mpi_is_opaque (sk->skey[i])); + p = mpi_get_opaque (sk->skey[i], &ndata); + iobuf_write (a, p, ndata); + } + write_16(a, sk->csum ); + } + else { + /* non-protected key */ + for( ; i < nskey; i++ ) + mpi_write(a, sk->skey[i] ); + write_16(a, sk->csum ); + } + + leave: + /* Build the header of the packet - which we must do after writing all + the other stuff, so that we know the length of the packet */ + write_header2(out, ctb, iobuf_get_temp_length(a), sk->hdrbytes); + /* And finally write it out the real stream */ + if( iobuf_write_temp( out, a ) ) + rc = G10ERR_WRITE_FILE; + + iobuf_close(a); /* close the remporary buffer */ + return rc; +} + +static int +do_symkey_enc( IOBUF out, int ctb, PKT_symkey_enc *enc ) +{ + int rc = 0; + IOBUF a = iobuf_temp(); + + assert( enc->version == 4 ); + switch( enc->s2k.mode ) { + case 0: case 1: case 3: break; + default: log_bug("do_symkey_enc: s2k=%d\n", enc->s2k.mode ); + } + iobuf_put( a, enc->version ); + iobuf_put( a, enc->cipher_algo ); + iobuf_put( a, enc->s2k.mode ); + iobuf_put( a, enc->s2k.hash_algo ); + if( enc->s2k.mode == 1 || enc->s2k.mode == 3 ) { + iobuf_write(a, enc->s2k.salt, 8 ); + if( enc->s2k.mode == 3 ) + iobuf_put(a, enc->s2k.count); + } + if( enc->seskeylen ) + iobuf_write(a, enc->seskey, enc->seskeylen ); + + write_header(out, ctb, iobuf_get_temp_length(a) ); + if( iobuf_write_temp( out, a ) ) + rc = G10ERR_WRITE_FILE; + + iobuf_close(a); + return rc; +} + + +static int +do_pubkey_enc( IOBUF out, int ctb, PKT_pubkey_enc *enc ) +{ + int rc = 0; + int n, i; + IOBUF a = iobuf_temp(); + + write_version( a, ctb ); + if( enc->throw_keyid ) { + write_32(a, 0 ); /* don't tell Eve who can decrypt the message */ + write_32(a, 0 ); + } + else { + write_32(a, enc->keyid[0] ); + write_32(a, enc->keyid[1] ); + } + iobuf_put(a,enc->pubkey_algo ); + n = pubkey_get_nenc( enc->pubkey_algo ); + if( !n ) + write_fake_data( a, enc->data[0] ); + for(i=0; i < n; i++ ) + mpi_write(a, enc->data[i] ); + + write_header(out, ctb, iobuf_get_temp_length(a) ); + if( iobuf_write_temp( out, a ) ) + rc = G10ERR_WRITE_FILE; + + iobuf_close(a); + return rc; +} + + +static u32 +calc_plaintext( PKT_plaintext *pt ) +{ + /* Truncate namelen to the maximum 255 characters. Note this means + that a function that calls build_packet with an illegal literal + packet will get it back legalized. */ + + if(pt->namelen>255) + pt->namelen=255; + + return pt->len? (1 + 1 + pt->namelen + 4 + pt->len) : 0; +} + +static int +do_plaintext( IOBUF out, int ctb, PKT_plaintext *pt ) +{ + int i, rc = 0; + u32 n; + byte buf[1000]; /* this buffer has the plaintext! */ + int nbytes; + + write_header(out, ctb, calc_plaintext( pt ) ); + iobuf_put(out, pt->mode ); + iobuf_put(out, pt->namelen ); + for(i=0; i < pt->namelen; i++ ) + iobuf_put(out, pt->name[i] ); + if( write_32(out, pt->timestamp ) ) + rc = G10ERR_WRITE_FILE; + + n = 0; + while( (nbytes=iobuf_read(pt->buf, buf, 1000)) != -1 ) { + if( iobuf_write(out, buf, nbytes) == -1 ) { + rc = G10ERR_WRITE_FILE; + break; + } + n += nbytes; + } + wipememory(buf,1000); /* burn the buffer */ + if( (ctb&0x40) && !pt->len ) + iobuf_set_partial_block_mode(out, 0 ); /* turn off partial */ + if( pt->len && n != pt->len ) + log_error("do_plaintext(): wrote %lu bytes but expected %lu bytes\n", + (ulong)n, (ulong)pt->len ); + + return rc; +} + + + +static int +do_encrypted( IOBUF out, int ctb, PKT_encrypted *ed ) +{ + int rc = 0; + u32 n; + + n = ed->len ? (ed->len + ed->extralen) : 0; + write_header(out, ctb, n ); + + /* This is all. The caller has to write the real data */ + + return rc; +} + +static int +do_encrypted_mdc( IOBUF out, int ctb, PKT_encrypted *ed ) +{ + int rc = 0; + u32 n; + + assert( ed->mdc_method ); + + /* Take version number and the following MDC packet in account. */ + n = ed->len ? (ed->len + ed->extralen + 1 + 22) : 0; + write_header(out, ctb, n ); + iobuf_put(out, 1 ); /* version */ + + /* This is all. The caller has to write the real data */ + + return rc; +} + + +static int +do_compressed( IOBUF out, int ctb, PKT_compressed *cd ) +{ + int rc = 0; + + /* We must use the old convention and don't use blockmode for tyhe + sake of PGP 2 compatibility. However if the new_ctb flag was + set, CTB is already formatted as new style and write_header2 + does create a partial length encoding using new the new + style. */ + write_header2(out, ctb, 0, 0); + iobuf_put(out, cd->algorithm ); + + /* This is all. The caller has to write the real data */ + + return rc; +} + + +/**************** + * Delete all subpackets of type REQTYPE and return a bool whether a packet + * was deleted. + */ +int +delete_sig_subpkt (subpktarea_t *area, sigsubpkttype_t reqtype ) +{ + int buflen; + sigsubpkttype_t type; + byte *buffer, *bufstart; + size_t n; + size_t unused = 0; + int okay = 0; + + if( !area ) + return 0; + buflen = area->len; + buffer = area->data; + for(;;) { + if( !buflen ) { + okay = 1; + break; + } + bufstart = buffer; + n = *buffer++; buflen--; + if( n == 255 ) { + if( buflen < 4 ) + break; + n = (buffer[0] << 24) | (buffer[1] << 16) + | (buffer[2] << 8) | buffer[3]; + buffer += 4; + buflen -= 4; + } + else if( n >= 192 ) { + if( buflen < 2 ) + break; + n = (( n - 192 ) << 8) + *buffer + 192; + buffer++; + buflen--; + } + if( buflen < n ) + break; + + type = *buffer & 0x7f; + if( type == reqtype ) { + buffer++; + buflen--; + n--; + if( n > buflen ) + break; + buffer += n; /* point to next subpkt */ + buflen -= n; + memmove (bufstart, buffer, buflen); /* shift */ + unused += buffer - bufstart; + buffer = bufstart; + } + else { + buffer += n; buflen -=n; + } + } + + if (!okay) + log_error ("delete_subpkt: buffer shorter than subpacket\n"); + assert (unused <= area->len); + area->len -= unused; + return !!unused; +} + + +/**************** + * Create or update a signature subpacket for SIG of TYPE. This + * functions knows where to put the data (hashed or unhashed). The + * function may move data from the unhashed part to the hashed one. + * Note: All pointers into sig->[un]hashed (e.g. returned by + * parse_sig_subpkt) are not valid after a call to this function. The + * data to put into the subpaket should be in a buffer with a length + * of buflen. + */ +void +build_sig_subpkt (PKT_signature *sig, sigsubpkttype_t type, + const byte *buffer, size_t buflen ) +{ + byte *p; + int critical, hashed; + subpktarea_t *oldarea, *newarea; + size_t nlen, n, n0; + + critical = (type & SIGSUBPKT_FLAG_CRITICAL); + type &= ~SIGSUBPKT_FLAG_CRITICAL; + + /* Sanity check buffer sizes */ + if(parse_one_sig_subpkt(buffer,buflen,type)<0) + BUG(); + + switch(type) + { + case SIGSUBPKT_NOTATION: + case SIGSUBPKT_POLICY: + case SIGSUBPKT_REV_KEY: + case SIGSUBPKT_SIGNATURE: + /* we do allow multiple subpackets */ + break; + + default: + /* we don't allow multiple subpackets */ + delete_sig_subpkt(sig->hashed,type); + delete_sig_subpkt(sig->unhashed,type); + break; + } + + /* Any special magic that needs to be done for this type so the + packet doesn't need to be reparsed? */ + switch(type) + { + case SIGSUBPKT_NOTATION: + sig->flags.notation=1; + break; + + case SIGSUBPKT_POLICY: + sig->flags.policy_url=1; + break; + + case SIGSUBPKT_PREF_KS: + sig->flags.pref_ks=1; + break; + + case SIGSUBPKT_EXPORTABLE: + if(buffer[0]) + sig->flags.exportable=1; + else + sig->flags.exportable=0; + break; + + case SIGSUBPKT_REVOCABLE: + if(buffer[0]) + sig->flags.revocable=1; + else + sig->flags.revocable=0; + break; + + case SIGSUBPKT_TRUST: + sig->trust_depth=buffer[0]; + sig->trust_value=buffer[1]; + break; + + case SIGSUBPKT_REGEXP: + sig->trust_regexp=buffer; + break; + + /* This should never happen since we don't currently allow + creating such a subpacket, but just in case... */ + case SIGSUBPKT_SIG_EXPIRE: + if(buffer_to_u32(buffer)+sig->timestamp<=make_timestamp()) + sig->flags.expired=1; + else + sig->flags.expired=0; + break; + + default: + break; + } + + if( (buflen+1) >= 8384 ) + nlen = 5; /* write 5 byte length header */ + else if( (buflen+1) >= 192 ) + nlen = 2; /* write 2 byte length header */ + else + nlen = 1; /* just a 1 byte length header */ + + switch( type ) + { + /* The issuer being unhashed is a historical oddity. It + should work equally as well hashed. Of course, if even an + unhashed issuer is tampered with, it makes it awfully hard + to verify the sig... */ + case SIGSUBPKT_ISSUER: + case SIGSUBPKT_SIGNATURE: + hashed = 0; + break; + default: + hashed = 1; + break; + } + + if( critical ) + type |= SIGSUBPKT_FLAG_CRITICAL; + + oldarea = hashed? sig->hashed : sig->unhashed; + + /* Calculate new size of the area and allocate */ + n0 = oldarea? oldarea->len : 0; + n = n0 + nlen + 1 + buflen; /* length, type, buffer */ + if (oldarea && n <= oldarea->size) { /* fits into the unused space */ + newarea = oldarea; + /*log_debug ("updating area for type %d\n", type );*/ + } + else if (oldarea) { + newarea = xrealloc (oldarea, sizeof (*newarea) + n - 1); + newarea->size = n; + /*log_debug ("reallocating area for type %d\n", type );*/ + } + else { + newarea = xmalloc (sizeof (*newarea) + n - 1); + newarea->size = n; + /*log_debug ("allocating area for type %d\n", type );*/ + } + newarea->len = n; + + p = newarea->data + n0; + if (nlen == 5) { + *p++ = 255; + *p++ = (buflen+1) >> 24; + *p++ = (buflen+1) >> 16; + *p++ = (buflen+1) >> 8; + *p++ = (buflen+1); + *p++ = type; + memcpy (p, buffer, buflen); + } + else if (nlen == 2) { + *p++ = (buflen+1-192) / 256 + 192; + *p++ = (buflen+1-192) % 256; + *p++ = type; + memcpy (p, buffer, buflen); + } + else { + *p++ = buflen+1; + *p++ = type; + memcpy (p, buffer, buflen); + } + + if (hashed) + sig->hashed = newarea; + else + sig->unhashed = newarea; +} + +/**************** + * Put all the required stuff from SIG into subpackets of sig. + * Hmmm, should we delete those subpackets which are in a wrong area? + */ +void +build_sig_subpkt_from_sig( PKT_signature *sig ) +{ + u32 u; + byte buf[8]; + + u = sig->keyid[0]; + buf[0] = (u >> 24) & 0xff; + buf[1] = (u >> 16) & 0xff; + buf[2] = (u >> 8) & 0xff; + buf[3] = u & 0xff; + u = sig->keyid[1]; + buf[4] = (u >> 24) & 0xff; + buf[5] = (u >> 16) & 0xff; + buf[6] = (u >> 8) & 0xff; + buf[7] = u & 0xff; + build_sig_subpkt( sig, SIGSUBPKT_ISSUER, buf, 8 ); + + u = sig->timestamp; + buf[0] = (u >> 24) & 0xff; + buf[1] = (u >> 16) & 0xff; + buf[2] = (u >> 8) & 0xff; + buf[3] = u & 0xff; + build_sig_subpkt( sig, SIGSUBPKT_SIG_CREATED, buf, 4 ); + + if(sig->expiredate) + { + if(sig->expiredate>sig->timestamp) + u=sig->expiredate-sig->timestamp; + else + u=1; /* A 1-second expiration time is the shortest one + OpenPGP has */ + + buf[0] = (u >> 24) & 0xff; + buf[1] = (u >> 16) & 0xff; + buf[2] = (u >> 8) & 0xff; + buf[3] = u & 0xff; + + /* Mark this CRITICAL, so if any implementation doesn't + understand sigs that can expire, it'll just disregard this + sig altogether. */ + + build_sig_subpkt( sig, SIGSUBPKT_SIG_EXPIRE | SIGSUBPKT_FLAG_CRITICAL, + buf, 4 ); + } +} + +void +build_attribute_subpkt(PKT_user_id *uid,byte type, + const void *buf,u32 buflen, + const void *header,u32 headerlen) +{ + byte *attrib; + int idx; + + if(1+headerlen+buflen>8383) + idx=5; + else if(1+headerlen+buflen>191) + idx=2; + else + idx=1; + + /* realloc uid->attrib_data to the right size */ + + uid->attrib_data=xrealloc(uid->attrib_data, + uid->attrib_len+idx+1+headerlen+buflen); + + attrib=&uid->attrib_data[uid->attrib_len]; + + if(idx==5) + { + attrib[0]=255; + attrib[1]=(1+headerlen+buflen) >> 24; + attrib[2]=(1+headerlen+buflen) >> 16; + attrib[3]=(1+headerlen+buflen) >> 8; + attrib[4]=1+headerlen+buflen; + } + else if(idx==2) + { + attrib[0]=(1+headerlen+buflen-192) / 256 + 192; + attrib[1]=(1+headerlen+buflen-192) % 256; + } + else + attrib[0]=1+headerlen+buflen; /* Good luck finding a JPEG this small! */ + + attrib[idx++]=type; + + /* Tack on our data at the end */ + + if(headerlen>0) + memcpy(&attrib[idx],header,headerlen); + memcpy(&attrib[idx+headerlen],buf,buflen); + uid->attrib_len+=idx+headerlen+buflen; +} + +struct notation * +string_to_notation(const char *string,int is_utf8) +{ + const char *s; + int saw_at=0; + struct notation *notation; + + notation=xmalloc_clear(sizeof(*notation)); + + if(*string=='-') + { + notation->flags.ignore=1; + string++; + } + + if(*string=='!') + { + notation->flags.critical=1; + string++; + } + + /* If and when the IETF assigns some official name tags, we'll have + to add them here. */ + + for( s=string ; *s != '='; s++ ) + { + if( *s=='@') + saw_at++; + + /* -notationname is legal without an = sign */ + if(!*s && notation->flags.ignore) + break; + + if( !*s || !isascii (*s) || (!isgraph(*s) && !isspace(*s)) ) + { + log_error(_("a notation name must have only printable characters" + " or spaces, and end with an '='\n") ); + goto fail; + } + } + + notation->name=xmalloc((s-string)+1); + strncpy(notation->name,string,s-string); + notation->name[s-string]='\0'; + + if(!saw_at && !opt.expert) + { + log_error(_("a user notation name must contain the '@' character\n")); + goto fail; + } + + if (saw_at > 1) + { + log_error(_("a notation name must not contain more than" + " one '@' character\n")); + goto fail; + } + + if(*s) + { + const char *i=s+1; + int highbit=0; + + /* we only support printable text - therefore we enforce the use + of only printable characters (an empty value is valid) */ + for(s++; *s ; s++ ) + { + if ( !isascii (*s) ) + highbit=1; + else if (iscntrl(*s)) + { + log_error(_("a notation value must not use any" + " control characters\n")); + goto fail; + } + } + + if(!highbit || is_utf8) + notation->value=xstrdup(i); + else + notation->value=native_to_utf8(i); + } + + return notation; + + fail: + free_notation(notation); + return NULL; +} + +struct notation * +sig_to_notation(PKT_signature *sig) +{ + const byte *p; + size_t len; + int seq=0,crit; + struct notation *list=NULL; + + while((p=enum_sig_subpkt(sig->hashed,SIGSUBPKT_NOTATION,&len,&seq,&crit))) + { + int n1,n2; + struct notation *n=NULL; + + if(len<8) + { + log_info(_("WARNING: invalid notation data found\n")); + continue; + } + + n1=(p[4]<<8)|p[5]; + n2=(p[6]<<8)|p[7]; + + if(8+n1+n2!=len) + { + log_info(_("WARNING: invalid notation data found\n")); + continue; + } + + n=xmalloc_clear(sizeof(*n)); + n->name=xmalloc(n1+1); + + memcpy(n->name,&p[8],n1); + n->name[n1]='\0'; + + if(p[0]&0x80) + { + n->value=xmalloc(n2+1); + memcpy(n->value,&p[8+n1],n2); + n->value[n2]='\0'; + } + else + { + n->bdat=xmalloc(n2); + n->blen=n2; + memcpy(n->bdat,&p[8+n1],n2); + + n->value=xmalloc(2+strlen(_("not human readable"))+2+1); + strcpy(n->value,"[ "); + strcat(n->value,_("not human readable")); + strcat(n->value," ]"); + } + + n->flags.critical=crit; + + n->next=list; + list=n; + } + + return list; +} + +void +free_notation(struct notation *notation) +{ + while(notation) + { + struct notation *n=notation; + + xfree(n->name); + xfree(n->value); + xfree(n->altvalue); + xfree(n->bdat); + notation=n->next; + xfree(n); + } +} + +static int +do_signature( IOBUF out, int ctb, PKT_signature *sig ) +{ + int rc = 0; + int n, i; + IOBUF a = iobuf_temp(); + + if( !sig->version ) + iobuf_put( a, 3 ); + else + iobuf_put( a, sig->version ); + if( sig->version < 4 ) + iobuf_put(a, 5 ); /* constant */ + iobuf_put(a, sig->sig_class ); + if( sig->version < 4 ) { + write_32(a, sig->timestamp ); + write_32(a, sig->keyid[0] ); + write_32(a, sig->keyid[1] ); + } + iobuf_put(a, sig->pubkey_algo ); + iobuf_put(a, sig->digest_algo ); + if( sig->version >= 4 ) { + size_t nn; + /* timestamp and keyid must have been packed into the + * subpackets prior to the call of this function, because + * these subpackets are hashed */ + nn = sig->hashed? sig->hashed->len : 0; + write_16(a, nn); + if( nn ) + iobuf_write( a, sig->hashed->data, nn ); + nn = sig->unhashed? sig->unhashed->len : 0; + write_16(a, nn); + if( nn ) + iobuf_write( a, sig->unhashed->data, nn ); + } + iobuf_put(a, sig->digest_start[0] ); + iobuf_put(a, sig->digest_start[1] ); + n = pubkey_get_nsig( sig->pubkey_algo ); + if( !n ) + write_fake_data( a, sig->data[0] ); + for(i=0; i < n; i++ ) + mpi_write(a, sig->data[i] ); + + if( is_RSA(sig->pubkey_algo) && sig->version < 4 ) + write_sign_packet_header(out, ctb, iobuf_get_temp_length(a) ); + else + write_header(out, ctb, iobuf_get_temp_length(a) ); + if( iobuf_write_temp( out, a ) ) + rc = G10ERR_WRITE_FILE; + + iobuf_close(a); + return rc; +} + + +static int +do_onepass_sig( IOBUF out, int ctb, PKT_onepass_sig *ops ) +{ + int rc = 0; + IOBUF a = iobuf_temp(); + + write_version( a, ctb ); + iobuf_put(a, ops->sig_class ); + iobuf_put(a, ops->digest_algo ); + iobuf_put(a, ops->pubkey_algo ); + write_32(a, ops->keyid[0] ); + write_32(a, ops->keyid[1] ); + iobuf_put(a, ops->last ); + + write_header(out, ctb, iobuf_get_temp_length(a) ); + if( iobuf_write_temp( out, a ) ) + rc = G10ERR_WRITE_FILE; + + iobuf_close(a); + return rc; +} + + +static int +write_16(IOBUF out, u16 a) +{ + iobuf_put(out, a>>8); + if( iobuf_put(out,a) ) + return -1; + return 0; +} + +static int +write_32(IOBUF out, u32 a) +{ + iobuf_put(out, a>> 24); + iobuf_put(out, a>> 16); + iobuf_put(out, a>> 8); + if( iobuf_put(out, a) ) + return -1; + return 0; +} + + +/**************** + * calculate the length of a header + */ +static int +calc_header_length( u32 len, int new_ctb ) +{ + if( !len ) + return 1; /* only the ctb */ + + if( new_ctb ) { + if( len < 192 ) + return 2; + if( len < 8384 ) + return 3; + else + return 6; + } + if( len < 256 ) + return 2; + if( len < 65536 ) + return 3; + + return 5; +} + +/**************** + * Write the CTB and the packet length + */ +static int +write_header( IOBUF out, int ctb, u32 len ) +{ + return write_header2( out, ctb, len, 0 ); +} + + +static int +write_sign_packet_header( IOBUF out, int ctb, u32 len ) +{ + /* work around a bug in the pgp read function for signature packets, + * which are not correctly coded and silently assume at some + * point 2 byte length headers.*/ + iobuf_put(out, 0x89 ); + iobuf_put(out, len >> 8 ); + return iobuf_put(out, len ) == -1 ? -1:0; +} + +/**************** + * If HDRLEN is > 0, try to build a header of this length. We need + * this so that we can hash packets without reading them again. If + * len is 0, write a partial or indeterminate length header, unless + * hdrlen is specified in which case write an actual zero length + * (using the specified hdrlen). + */ +static int +write_header2( IOBUF out, int ctb, u32 len, int hdrlen ) +{ + if( ctb & 0x40 ) + return write_new_header( out, ctb, len, hdrlen ); + + if( hdrlen ) + { + if( hdrlen == 2 && len < 256 ) + ; + else if( hdrlen == 3 && len < 65536 ) + ctb |= 1; + else + ctb |= 2; + } + else + { + if( !len ) + ctb |= 3; + else if( len < 256 ) + ; + else if( len < 65536 ) + ctb |= 1; + else + ctb |= 2; + } + + if( iobuf_put(out, ctb ) ) + return -1; + + if( len || hdrlen ) + { + if( ctb & 2 ) + { + if(iobuf_put(out, len >> 24 )) + return -1; + if(iobuf_put(out, len >> 16 )) + return -1; + } + + if( ctb & 3 ) + if(iobuf_put(out, len >> 8 )) + return -1; + + if( iobuf_put(out, len ) ) + return -1; + } + + return 0; +} + + +static int +write_new_header( IOBUF out, int ctb, u32 len, int hdrlen ) +{ + if( hdrlen ) + log_bug("can't cope with hdrlen yet\n"); + + if( iobuf_put(out, ctb ) ) + return -1; + if( !len ) { + iobuf_set_partial_block_mode(out, 512 ); + } + else { + if( len < 192 ) { + if( iobuf_put(out, len ) ) + return -1; + } + else if( len < 8384 ) { + len -= 192; + if( iobuf_put( out, (len / 256) + 192) ) + return -1; + if( iobuf_put( out, (len % 256) ) ) + return -1; + } + else { + if( iobuf_put( out, 0xff ) ) + return -1; + if( iobuf_put( out, (len >> 24)&0xff ) ) + return -1; + if( iobuf_put( out, (len >> 16)&0xff ) ) + return -1; + if( iobuf_put( out, (len >> 8)&0xff ) ) + return -1; + if( iobuf_put( out, len & 0xff ) ) + return -1; + } + } + return 0; +} + +static int +write_version( IOBUF out, int ctb ) +{ + if( iobuf_put( out, 3 ) ) + return -1; + return 0; +} diff --git a/g10/card-util.c b/g10/card-util.c new file mode 100644 index 0000000..0c83654 --- /dev/null +++ b/g10/card-util.c @@ -0,0 +1,1610 @@ +/* card-util.c - Utility functions for the OpenPGP card. + * 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 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <assert.h> + +#if GNUPG_MAJOR_VERSION != 1 +#include "gpg.h" +#endif /*GNUPG_MAJOR_VERSION != 1*/ +#include "util.h" +#include "i18n.h" +#include "ttyio.h" +#include "status.h" +#include "options.h" +#include "main.h" +#include "keyserver-internal.h" +#if GNUPG_MAJOR_VERSION == 1 +#ifdef HAVE_LIBREADLINE +#include <stdio.h> +#include <readline/readline.h> +#endif /*HAVE_LIBREADLINE*/ +#include "cardglue.h" +#else /*GNUPG_MAJOR_VERSION!=1*/ +#include "call-agent.h" +#endif /*GNUPG_MAJOR_VERSION!=1*/ + +#define CONTROL_D ('D' - 'A' + 1) + + +/* Change the PIN of a an OpenPGP card. This is an interactive + function. */ +void +change_pin (int chvno, int allow_admin) +{ + struct agent_card_info_s info; + int rc; + + rc = agent_learn (&info); + if (rc) + { + log_error (_("OpenPGP card not available: %s\n"), + gpg_strerror (rc)); + return; + } + + log_info (_("OpenPGP card no. %s detected\n"), + info.serialno? info.serialno : "[none]"); + + agent_clear_pin_cache (info.serialno); + + if (opt.batch) + { + agent_release_card_info (&info); + log_error (_("can't do this in batch mode\n")); + return; + } + + if(!allow_admin) + { + rc = agent_scd_change_pin (1, info.serialno); + if (rc) + tty_printf ("Error changing the PIN: %s\n", gpg_strerror (rc)); + else + { + write_status (STATUS_SC_OP_SUCCESS); + tty_printf ("PIN changed.\n"); + } + } + else + for (;;) + { + char *answer; + + tty_printf ("\n"); + tty_printf ("1 - change PIN\n" + "2 - unblock PIN\n" + "3 - change Admin PIN\n" + "Q - quit\n"); + tty_printf ("\n"); + + answer = cpr_get("cardutil.change_pin.menu",_("Your selection? ")); + cpr_kill_prompt(); + if (strlen (answer) != 1) + continue; + + rc = 0; + if (*answer == '1') + { + rc = agent_scd_change_pin (1, info.serialno); + if (rc) + tty_printf ("Error changing the PIN: %s\n", gpg_strerror (rc)); + else + { + write_status (STATUS_SC_OP_SUCCESS); + tty_printf ("PIN changed.\n"); + } + } + else if (*answer == '2') + { + rc = agent_scd_change_pin (101, info.serialno); + if (rc) + tty_printf ("Error unblocking the PIN: %s\n", gpg_strerror (rc)); + else + { + write_status (STATUS_SC_OP_SUCCESS); + tty_printf ("PIN unblocked and new PIN set.\n"); + } + } + else if (*answer == '3') + { + rc = agent_scd_change_pin (3, info.serialno); + if (rc) + tty_printf ("Error changing the PIN: %s\n", gpg_strerror (rc)); + else + { + write_status (STATUS_SC_OP_SUCCESS); + tty_printf ("PIN changed.\n"); + } + } + else if (*answer == 'q' || *answer == 'Q') + { + break; + } + } + + agent_release_card_info (&info); +} + +static const char * +get_manufacturer (unsigned int no) +{ + /* Note: Make sure that there is no colon or linefeed in the string. */ + switch (no) + { + case 0: + case 0xffff: return "test card"; + case 0x0001: return "PPC Card Systems"; + case 0x0002: return "Prism"; + case 0x0003: return "OpenFortress"; + default: return "unknown"; + } +} + + +static void +print_sha1_fpr (FILE *fp, const unsigned char *fpr) +{ + int i; + + if (fpr) + { + for (i=0; i < 20 ; i+=2, fpr += 2 ) + { + if (i == 10 ) + tty_fprintf (fp, " "); + tty_fprintf (fp, " %02X%02X", *fpr, fpr[1]); + } + } + else + tty_fprintf (fp, " [none]"); + tty_fprintf (fp, "\n"); +} + + +static void +print_sha1_fpr_colon (FILE *fp, const unsigned char *fpr) +{ + int i; + + if (fpr) + { + for (i=0; i < 20 ; i++, fpr++) + fprintf (fp, "%02X", *fpr); + } + putc (':', fp); +} + + +static void +print_name (FILE *fp, const char *text, const char *name) +{ + tty_fprintf (fp, "%s", text); + + /* FIXME: tty_printf_utf8_string2 eats everything after and + including an @ - e.g. when printing an url. */ + if (name && *name) + { + if (fp) + print_utf8_string2 (fp, name, strlen (name), '\n'); + else + tty_print_utf8_string2 (name, strlen (name), 0); + } + else + tty_fprintf (fp, _("[not set]")); + tty_fprintf (fp, "\n"); +} + +static void +print_isoname (FILE *fp, const char *text, const char *tag, const char *name) +{ + if (opt.with_colons) + fprintf (fp, "%s:", tag); + else + tty_fprintf (fp, "%s", text); + + if (name && *name) + { + char *p, *given, *buf = xstrdup (name); + + given = strstr (buf, "<<"); + for (p=buf; *p; p++) + if (*p == '<') + *p = ' '; + if (given && given[2]) + { + *given = 0; + given += 2; + if (opt.with_colons) + print_string (fp, given, strlen (given), ':'); + else if (fp) + print_utf8_string2 (fp, given, strlen (given), '\n'); + else + tty_print_utf8_string2 (given, strlen (given), 0); + + if (opt.with_colons) + putc (':', fp); + else if (*buf) + tty_fprintf (fp, " "); + } + + if (opt.with_colons) + print_string (fp, buf, strlen (buf), ':'); + else if (fp) + print_utf8_string2 (fp, buf, strlen (buf), '\n'); + else + tty_print_utf8_string2 (buf, strlen (buf), 0); + xfree (buf); + } + else + { + if (opt.with_colons) + putc (':', fp); + else + tty_fprintf (fp, _("[not set]")); + } + + if (opt.with_colons) + fputs (":\n", fp); + else + tty_fprintf (fp, "\n"); +} + +/* Return true if the SHA1 fingerprint FPR consists only of zeroes. */ +static int +fpr_is_zero (const char *fpr) +{ + int i; + + for (i=0; i < 20 && !fpr[i]; i++) + ; + return (i == 20); +} + + +/* Print all available information about the current card. */ +void +card_status (FILE *fp, char *serialno, size_t serialnobuflen) +{ + struct agent_card_info_s info; + PKT_public_key *pk = xcalloc (1, sizeof *pk); + int rc; + unsigned int uval; + const unsigned char *thefpr; + int i; + + if (serialno && serialnobuflen) + *serialno = 0; + + rc = agent_learn (&info); + if (rc) + { + if (opt.with_colons) + fputs ("AID:::\n", fp); + log_error (_("OpenPGP card not available: %s\n"), + gpg_strerror (rc)); + xfree (pk); + return; + } + + if (opt.with_colons) + fprintf (fp, "AID:%s:", info.serialno? info.serialno : ""); + else + tty_fprintf (fp, "Application ID ...: %s\n", + info.serialno? info.serialno : "[none]"); + if (!info.serialno || strncmp (info.serialno, "D27600012401", 12) + || strlen (info.serialno) != 32 ) + { + if (opt.with_colons) + fputs ("unknown:\n", fp); + log_info ("not an OpenPGP card\n"); + agent_release_card_info (&info); + xfree (pk); + return; + } + + if (!serialno) + ; + else if (strlen (serialno)+1 > serialnobuflen) + log_error ("serial number longer than expected\n"); + else + strcpy (serialno, info.serialno); + + if (opt.with_colons) + fputs ("openpgp-card:\n", fp); + + + if (opt.with_colons) + { + fprintf (fp, "version:%.4s:\n", info.serialno+12); + uval = xtoi_2(info.serialno+16)*256 + xtoi_2 (info.serialno+18); + fprintf (fp, "vendor:%04x:%s:\n", uval, get_manufacturer (uval)); + fprintf (fp, "serial:%.8s:\n", info.serialno+20); + + print_isoname (fp, "Name of cardholder: ", "name", info.disp_name); + + fputs ("lang:", fp); + if (info.disp_lang) + print_string (fp, info.disp_lang, strlen (info.disp_lang), ':'); + fputs (":\n", fp); + + fprintf (fp, "sex:%c:\n", (info.disp_sex == 1? 'm': + info.disp_sex == 2? 'f' : 'u')); + + fputs ("url:", fp); + if (info.pubkey_url) + print_string (fp, info.pubkey_url, strlen (info.pubkey_url), ':'); + fputs (":\n", fp); + + fputs ("login:", fp); + if (info.login_data) + print_string (fp, info.login_data, strlen (info.login_data), ':'); + fputs (":\n", fp); + + fprintf (fp, "forcepin:%d:::\n", !info.chv1_cached); + fprintf (fp, "maxpinlen:%d:%d:%d:\n", + info.chvmaxlen[0], info.chvmaxlen[1], info.chvmaxlen[2]); + fprintf (fp, "pinretry:%d:%d:%d:\n", + info.chvretry[0], info.chvretry[1], info.chvretry[2]); + fprintf (fp, "sigcount:%lu:::\n", info.sig_counter); + + for (i=0; i < 4; i++) + { + if (info.private_do[i]) + { + fprintf (fp, "private_do:%d:", i+1); + print_string (fp, info.private_do[i], + strlen (info.private_do[i]), ':'); + fputs (":\n", fp); + } + } + + fputs ("cafpr:", fp); + print_sha1_fpr_colon (fp, info.cafpr1valid? info.cafpr1:NULL); + print_sha1_fpr_colon (fp, info.cafpr2valid? info.cafpr2:NULL); + print_sha1_fpr_colon (fp, info.cafpr3valid? info.cafpr3:NULL); + putc ('\n', fp); + fputs ("fpr:", fp); + print_sha1_fpr_colon (fp, info.fpr1valid? info.fpr1:NULL); + print_sha1_fpr_colon (fp, info.fpr2valid? info.fpr2:NULL); + print_sha1_fpr_colon (fp, info.fpr3valid? info.fpr3:NULL); + putc ('\n', fp); + fprintf (fp, "fprtime:%lu:%lu:%lu:\n", + (unsigned long)info.fpr1time, (unsigned long)info.fpr2time, + (unsigned long)info.fpr3time); + } + else + { + tty_fprintf (fp, "Version ..........: %.1s%c.%.1s%c\n", + info.serialno[12] == '0'?"":info.serialno+12, + info.serialno[13], + info.serialno[14] == '0'?"":info.serialno+14, + info.serialno[15]); + tty_fprintf (fp, "Manufacturer .....: %s\n", + get_manufacturer (xtoi_2(info.serialno+16)*256 + + xtoi_2 (info.serialno+18))); + tty_fprintf (fp, "Serial number ....: %.8s\n", info.serialno+20); + + print_isoname (fp, "Name of cardholder: ", "name", info.disp_name); + print_name (fp, "Language prefs ...: ", info.disp_lang); + tty_fprintf (fp, "Sex ..............: %s\n", + info.disp_sex == 1? _("male"): + info.disp_sex == 2? _("female") : _("unspecified")); + print_name (fp, "URL of public key : ", info.pubkey_url); + print_name (fp, "Login data .......: ", info.login_data); + if (info.private_do[0]) + print_name (fp, "Private DO 1 .....: ", info.private_do[0]); + if (info.private_do[1]) + print_name (fp, "Private DO 2 .....: ", info.private_do[1]); + if (info.private_do[2]) + print_name (fp, "Private DO 3 .....: ", info.private_do[2]); + if (info.private_do[3]) + print_name (fp, "Private DO 4 .....: ", info.private_do[3]); + if (info.cafpr1valid) + { + tty_fprintf (fp, "CA fingerprint %d .:", 1); + print_sha1_fpr (fp, info.cafpr1); + } + if (info.cafpr2valid) + { + tty_fprintf (fp, "CA fingerprint %d .:", 2); + print_sha1_fpr (fp, info.cafpr2); + } + if (info.cafpr3valid) + { + tty_fprintf (fp, "CA fingerprint %d .:", 3); + print_sha1_fpr (fp, info.cafpr3); + } + tty_fprintf (fp, "Signature PIN ....: %s\n", + info.chv1_cached? _("not forced"): _("forced")); + tty_fprintf (fp, "Max. PIN lengths .: %d %d %d\n", + info.chvmaxlen[0], info.chvmaxlen[1], info.chvmaxlen[2]); + tty_fprintf (fp, "PIN retry counter : %d %d %d\n", + info.chvretry[0], info.chvretry[1], info.chvretry[2]); + tty_fprintf (fp, "Signature counter : %lu\n", info.sig_counter); + tty_fprintf (fp, "Signature key ....:"); + print_sha1_fpr (fp, info.fpr1valid? info.fpr1:NULL); + if (info.fpr1valid && info.fpr1time) + tty_fprintf (fp, " created ....: %s\n", + isotimestamp (info.fpr1time)); + tty_fprintf (fp, "Encryption key....:"); + print_sha1_fpr (fp, info.fpr2valid? info.fpr2:NULL); + if (info.fpr2valid && info.fpr2time) + tty_fprintf (fp, " created ....: %s\n", + isotimestamp (info.fpr2time)); + tty_fprintf (fp, "Authentication key:"); + print_sha1_fpr (fp, info.fpr3valid? info.fpr3:NULL); + if (info.fpr3valid && info.fpr3time) + tty_fprintf (fp, " created ....: %s\n", + isotimestamp (info.fpr3time)); + tty_fprintf (fp, "General key info..: "); + + thefpr = (info.fpr1valid? info.fpr1 : info.fpr2valid? info.fpr2 : + info.fpr3valid? info.fpr3 : NULL); + if ( thefpr && !get_pubkey_byfprint (pk, thefpr, 20)) + { + KBNODE keyblock = NULL; + + print_pubkey_info (fp, pk); + + if ( !get_seckeyblock_byfprint (&keyblock, thefpr, 20) ) + print_card_key_info (fp, keyblock); + else if ( !get_keyblock_byfprint (&keyblock, thefpr, 20) ) + { + release_kbnode (keyblock); + keyblock = NULL; + + if (!auto_create_card_key_stub (info.serialno, + info.fpr1valid? info.fpr1:NULL, + info.fpr2valid? info.fpr2:NULL, + info.fpr3valid? info.fpr3:NULL)) + { + if ( !get_seckeyblock_byfprint (&keyblock, thefpr, 20) ) + print_card_key_info (fp, keyblock); + } + } + + release_kbnode (keyblock); + } + else + tty_fprintf (fp, "[none]\n"); + } + + free_public_key (pk); + agent_release_card_info (&info); +} + + +static char * +get_one_name (const char *prompt1, const char *prompt2) +{ + char *name; + int i; + + for (;;) + { + name = cpr_get (prompt1, prompt2); + if (!name) + return NULL; + trim_spaces (name); + cpr_kill_prompt (); + for (i=0; name[i] && name[i] >= ' ' && name[i] <= 126; i++) + ; + + /* The name must be in Latin-1 and not UTF-8 - lacking the code + to ensure this we restrict it to ASCII. */ + if (name[i]) + tty_printf (_("Error: Only plain ASCII is currently allowed.\n")); + else if (strchr (name, '<')) + tty_printf (_("Error: The \"<\" character may not be used.\n")); + else if (strstr (name, " ")) + tty_printf (_("Error: Double spaces are not allowed.\n")); + else + return name; + xfree (name); + } +} + + + +static int +change_name (void) +{ + char *surname = NULL, *givenname = NULL; + char *isoname, *p; + int rc; + + surname = get_one_name ("keygen.smartcard.surname", + _("Cardholder's surname: ")); + givenname = get_one_name ("keygen.smartcard.givenname", + _("Cardholder's given name: ")); + if (!surname || !givenname || (!*surname && !*givenname)) + { + xfree (surname); + xfree (givenname); + return -1; /*canceled*/ + } + + isoname = xmalloc ( strlen (surname) + 2 + strlen (givenname) + 1); + strcpy (stpcpy (stpcpy (isoname, surname), "<<"), givenname); + xfree (surname); + xfree (givenname); + for (p=isoname; *p; p++) + if (*p == ' ') + *p = '<'; + + if (strlen (isoname) > 39 ) + { + tty_printf (_("Error: Combined name too long " + "(limit is %d characters).\n"), 39); + xfree (isoname); + return -1; + } + + rc = agent_scd_setattr ("DISP-NAME", isoname, strlen (isoname), NULL ); + if (rc) + log_error ("error setting Name: %s\n", gpg_strerror (rc)); + + xfree (isoname); + return rc; +} + + +static int +change_url (void) +{ + char *url; + int rc; + + url = cpr_get ("cardedit.change_url", _("URL to retrieve public key: ")); + if (!url) + return -1; + trim_spaces (url); + cpr_kill_prompt (); + + if (strlen (url) > 254 ) + { + tty_printf (_("Error: URL too long " + "(limit is %d characters).\n"), 254); + xfree (url); + return -1; + } + + rc = agent_scd_setattr ("PUBKEY-URL", url, strlen (url), NULL ); + if (rc) + log_error ("error setting URL: %s\n", gpg_strerror (rc)); + xfree (url); + return rc; +} + + +/* Fetch the key from the URL given on the card or try to get it from + the default keyserver. */ +static int +fetch_url(void) +{ +#if GNUPG_MAJOR_VERSION == 1 + int rc; + struct agent_card_info_s info; + + memset(&info,0,sizeof(info)); + + rc=agent_scd_getattr("PUBKEY-URL",&info); + if(rc) + log_error("error retrieving URL from card: %s\n",gpg_strerror(rc)); + else + { + struct keyserver_spec *spec=NULL; + + rc=agent_scd_getattr("KEY-FPR",&info); + if(rc) + log_error("error retrieving key fingerprint from card: %s\n", + gpg_strerror(rc)); + else if (info.pubkey_url && *info.pubkey_url) + { + spec=parse_keyserver_uri(info.pubkey_url,1,NULL,0); + if(spec && info.fpr1valid) + { + /* This is not perfectly right. Currently, all card + fingerprints are 20 digits, but what about + fingerprints for a future v5 key? We should get the + length from somewhere lower in the code. In any + event, the fpr/keyid is not meaningful for straight + HTTP fetches, but using it allows the card to point + to HKP and LDAP servers as well. */ + rc=keyserver_import_fprint(info.fpr1,20,spec); + free_keyserver_spec(spec); + } + } + else if (info.fpr1valid) + { + rc = keyserver_import_fprint (info.fpr1, 20, opt.keyserver); + } + } + + return rc; +#else + return 0; +#endif +} + + +static int +change_login (const char *args) +{ + char *data; + int n; + int rc; + + if (args && *args == '<') /* Read it from a file */ + { + FILE *fp; + + for (args++; spacep (args); args++) + ; + fp = fopen (args, "rb"); +#if GNUPG_MAJOR_VERSION == 1 + if (fp && is_secured_file (fileno (fp))) + { + fclose (fp); + fp = NULL; + errno = EPERM; + } +#endif + if (!fp) + { + tty_printf (_("can't open `%s': %s\n"), args, strerror (errno)); + return -1; + } + + data = xmalloc (254); + n = fread (data, 1, 254, fp); + fclose (fp); + if (n < 0) + { + tty_printf (_("error reading `%s': %s\n"), args, strerror (errno)); + xfree (data); + return -1; + } + } + else + { + data = cpr_get ("cardedit.change_login", + _("Login data (account name): ")); + if (!data) + return -1; + trim_spaces (data); + cpr_kill_prompt (); + n = strlen (data); + } + + if (n > 254 ) + { + tty_printf (_("Error: Login data too long " + "(limit is %d characters).\n"), 254); + xfree (data); + return -1; + } + + rc = agent_scd_setattr ("LOGIN-DATA", data, n, NULL ); + if (rc) + log_error ("error setting login data: %s\n", gpg_strerror (rc)); + xfree (data); + return rc; +} + +static int +change_private_do (const char *args, int nr) +{ + char do_name[] = "PRIVATE-DO-X"; + char *data; + int n; + int rc; + + assert (nr >= 1 && nr <= 4); + do_name[11] = '0' + nr; + + if (args && (args = strchr (args, '<'))) /* Read it from a file */ + { + FILE *fp; + + /* Fixme: Factor this duplicated code out. */ + for (args++; spacep (args); args++) + ; + fp = fopen (args, "rb"); +#if GNUPG_MAJOR_VERSION == 1 + if (fp && is_secured_file (fileno (fp))) + { + fclose (fp); + fp = NULL; + errno = EPERM; + } +#endif + if (!fp) + { + tty_printf (_("can't open `%s': %s\n"), args, strerror (errno)); + return -1; + } + + data = xmalloc (254); + n = fread (data, 1, 254, fp); + fclose (fp); + if (n < 0) + { + tty_printf (_("error reading `%s': %s\n"), args, strerror (errno)); + xfree (data); + return -1; + } + } + else + { + data = cpr_get ("cardedit.change_private_do", + _("Private DO data: ")); + if (!data) + return -1; + trim_spaces (data); + cpr_kill_prompt (); + n = strlen (data); + } + + if (n > 254 ) + { + tty_printf (_("Error: Private DO too long " + "(limit is %d characters).\n"), 254); + xfree (data); + return -1; + } + + rc = agent_scd_setattr (do_name, data, n, NULL ); + if (rc) + log_error ("error setting private DO: %s\n", gpg_strerror (rc)); + xfree (data); + return rc; +} + +static int +change_lang (void) +{ + char *data, *p; + int rc; + + data = cpr_get ("cardedit.change_lang", + _("Language preferences: ")); + if (!data) + return -1; + trim_spaces (data); + cpr_kill_prompt (); + + if (strlen (data) > 8 || (strlen (data) & 1)) + { + tty_printf (_("Error: invalid length of preference string.\n")); + xfree (data); + return -1; + } + + for (p=data; *p && *p >= 'a' && *p <= 'z'; p++) + ; + if (*p) + { + tty_printf (_("Error: invalid characters in preference string.\n")); + xfree (data); + return -1; + } + + rc = agent_scd_setattr ("DISP-LANG", data, strlen (data), NULL ); + if (rc) + log_error ("error setting lang: %s\n", gpg_strerror (rc)); + xfree (data); + return rc; +} + + +static int +change_sex (void) +{ + char *data; + const char *str; + int rc; + + data = cpr_get ("cardedit.change_sex", + _("Sex ((M)ale, (F)emale or space): ")); + if (!data) + return -1; + trim_spaces (data); + cpr_kill_prompt (); + + if (!*data) + str = "9"; + else if ((*data == 'M' || *data == 'm') && !data[1]) + str = "1"; + else if ((*data == 'F' || *data == 'f') && !data[1]) + str = "2"; + else + { + tty_printf (_("Error: invalid response.\n")); + xfree (data); + return -1; + } + + rc = agent_scd_setattr ("DISP-SEX", str, 1, NULL ); + if (rc) + log_error ("error setting sex: %s\n", gpg_strerror (rc)); + xfree (data); + return rc; +} + + +static int +change_cafpr (int fprno) +{ + char *data; + const char *s; + int i, c, rc; + unsigned char fpr[20]; + + data = cpr_get ("cardedit.change_cafpr", _("CA fingerprint: ")); + if (!data) + return -1; + trim_spaces (data); + cpr_kill_prompt (); + + for (i=0, s=data; i < 20 && *s; ) + { + while (spacep(s)) + s++; + if (*s == ':') + s++; + while (spacep(s)) + s++; + c = hextobyte (s); + if (c == -1) + break; + fpr[i++] = c; + s += 2; + } + xfree (data); + if (i != 20 || *s) + { + tty_printf (_("Error: invalid formatted fingerprint.\n")); + return -1; + } + + rc = agent_scd_setattr (fprno==1?"CA-FPR-1": + fprno==2?"CA-FPR-2": + fprno==3?"CA-FPR-3":"x", fpr, 20, NULL ); + if (rc) + log_error ("error setting cafpr: %s\n", gpg_strerror (rc)); + return rc; +} + + + +static void +toggle_forcesig (void) +{ + struct agent_card_info_s info; + int rc; + int newstate; + + memset (&info, 0, sizeof info); + rc = agent_scd_getattr ("CHV-STATUS", &info); + if (rc) + { + log_error ("error getting current status: %s\n", gpg_strerror (rc)); + return; + } + newstate = !info.chv1_cached; + agent_release_card_info (&info); + + rc = agent_scd_setattr ("CHV-STATUS-1", newstate? "\x01":"", 1, NULL); + if (rc) + log_error ("error toggling signature PIN flag: %s\n", gpg_strerror (rc)); +} + + +/* Helper for the key generation/edit functions. */ +static int +get_info_for_key_operation (struct agent_card_info_s *info) +{ + int rc; + + memset (info, 0, sizeof *info); + rc = agent_scd_getattr ("SERIALNO", info); + if (rc || !info->serialno || strncmp (info->serialno, "D27600012401", 12) + || strlen (info->serialno) != 32 ) + { + log_error (_("key operation not possible: %s\n"), + rc ? gpg_strerror (rc) : _("not an OpenPGP card")); + return rc? rc: -1; + } + rc = agent_scd_getattr ("KEY-FPR", info); + if (!rc) + rc = agent_scd_getattr ("CHV-STATUS", info); + if (!rc) + rc = agent_scd_getattr ("DISP-NAME", info); + if (rc) + log_error (_("error getting current key info: %s\n"), gpg_strerror (rc)); + return rc; +} + + +/* Helper for the key generation/edit functions. */ +static int +check_pin_for_key_operation (struct agent_card_info_s *info, int *forced_chv1) +{ + int rc = 0; + + agent_clear_pin_cache (info->serialno); + + *forced_chv1 = !info->chv1_cached; + if (*forced_chv1) + { /* Switch of the forced mode so that during key generation we + don't get bothered with PIN queries for each + self-signature. */ + rc = agent_scd_setattr ("CHV-STATUS-1", "\x01", 1, info->serialno); + if (rc) + { + log_error ("error clearing forced signature PIN flag: %s\n", + gpg_strerror (rc)); + *forced_chv1 = 0; + } + } + + if (!rc) + { + /* Check the PIN now, so that we won't get asked later for each + binding signature. */ + rc = agent_scd_checkpin (info->serialno); + if (rc) + log_error ("error checking the PIN: %s\n", gpg_strerror (rc)); + } + return rc; +} + +/* Helper for the key generation/edit functions. */ +static void +restore_forced_chv1 (int *forced_chv1) +{ + int rc; + + if (*forced_chv1) + { /* Switch back to forced state. */ + rc = agent_scd_setattr ("CHV-STATUS-1", "", 1, NULL); + if (rc) + { + log_error ("error setting forced signature PIN flag: %s\n", + gpg_strerror (rc)); + } + } +} + +#if GNUPG_MAJOR_VERSION == 1 +/* Helper for the key generation/edit functions. */ +static void +show_card_key_info (struct agent_card_info_s *info) +{ + tty_fprintf (NULL, "Signature key ....:"); + print_sha1_fpr (NULL, info->fpr1valid? info->fpr1:NULL); + tty_fprintf (NULL, "Encryption key....:"); + print_sha1_fpr (NULL, info->fpr2valid? info->fpr2:NULL); + tty_fprintf (NULL, "Authentication key:"); + print_sha1_fpr (NULL, info->fpr3valid? info->fpr3:NULL); + tty_printf ("\n"); +} +#endif + +#if GNUPG_MAJOR_VERSION == 1 +/* Helper for the key generation/edit functions. */ +static int +replace_existing_key_p (struct agent_card_info_s *info, int keyno) +{ + assert (keyno >= 0 && keyno <= 3); + + if ((keyno == 1 && info->fpr1valid) + || (keyno == 2 && info->fpr2valid) + || (keyno == 3 && info->fpr3valid)) + { + tty_printf ("\n"); + log_info ("WARNING: such a key has already been stored on the card!\n"); + tty_printf ("\n"); + if ( !cpr_get_answer_is_yes( "cardedit.genkeys.replace_key", + _("Replace existing key? (y/N) "))) + return -1; + } + return 0; +} +#endif + + +static void +generate_card_keys (const char *serialno) +{ + struct agent_card_info_s info; + int forced_chv1; + int want_backup; + + if (get_info_for_key_operation (&info)) + return; + +#if GNUPG_MAJOR_VERSION == 1 + { + char *answer=cpr_get("cardedit.genkeys.backup_enc", + _("Make off-card backup of encryption key? (Y/n) ")); + + want_backup=answer_is_yes_no_default(answer,1); + cpr_kill_prompt(); + xfree(answer); + } +#else + want_backup = cpr_get_answer_is_yes + ( "cardedit.genkeys.backup_enc", + _("Make off-card backup of encryption key? (Y/n) ")); + /*FIXME: we need answer_is_yes_no_default()*/ +#endif + + if ( (info.fpr1valid && !fpr_is_zero (info.fpr1)) + || (info.fpr2valid && !fpr_is_zero (info.fpr2)) + || (info.fpr3valid && !fpr_is_zero (info.fpr3))) + { + tty_printf ("\n"); + log_info ("NOTE: keys are already stored on the card!\n"); + tty_printf ("\n"); + if ( !cpr_get_answer_is_yes( "cardedit.genkeys.replace_keys", + _("Replace existing keys? (y/N) "))) + { + agent_release_card_info (&info); + return; + } + } + else if (!info.disp_name || !*info.disp_name) + { + tty_printf ("\n"); + tty_printf (_("Please note that the factory settings of the PINs are\n" + " PIN = `%s' Admin PIN = `%s'\n" + "You should change them using the command --change-pin\n"), + "123456", "12345678"); + tty_printf ("\n"); + } + + if (check_pin_for_key_operation (&info, &forced_chv1)) + goto leave; + +#if GNUPG_MAJOR_VERSION == 1 + generate_keypair (NULL, info.serialno, + want_backup? opt.homedir:NULL); +#else + generate_keypair (NULL, info.serialno); +#endif + + leave: + agent_release_card_info (&info); + restore_forced_chv1 (&forced_chv1); +} + + +/* This function is used by the key edit menu to generate an arbitrary + subkey. */ +int +card_generate_subkey (KBNODE pub_keyblock, KBNODE sec_keyblock) +{ +#if GNUPG_MAJOR_VERSION == 1 + struct agent_card_info_s info; + int okay = 0; + int forced_chv1 = 0; + int keyno; + + if (get_info_for_key_operation (&info)) + return 0; + + show_card_key_info (&info); + + tty_printf (_("Please select the type of key to generate:\n")); + + tty_printf (_(" (1) Signature key\n")); + tty_printf (_(" (2) Encryption key\n")); + tty_printf (_(" (3) Authentication key\n")); + + for (;;) + { + char *answer = cpr_get ("cardedit.genkeys.subkeytype", + _("Your selection? ")); + cpr_kill_prompt(); + if (*answer == CONTROL_D) + { + xfree (answer); + goto leave; + } + keyno = *answer? atoi(answer): 0; + xfree(answer); + if (keyno >= 1 && keyno <= 3) + break; /* Okay. */ + tty_printf(_("Invalid selection.\n")); + } + + if (replace_existing_key_p (&info, keyno)) + goto leave; + + if (check_pin_for_key_operation (&info, &forced_chv1)) + goto leave; + + okay = generate_card_subkeypair (pub_keyblock, sec_keyblock, + keyno, info.serialno); + + leave: + agent_release_card_info (&info); + restore_forced_chv1 (&forced_chv1); + return okay; +#else + return 0; +#endif +} + + +/* Store the key at NODE into the smartcard and modify NODE to + carry the serialno stuff instead of the actual secret key + parameters. USE is the usage for that key; 0 means any + usage. */ +int +card_store_subkey (KBNODE node, int use) +{ +#if GNUPG_MAJOR_VERSION == 1 + struct agent_card_info_s info; + int okay = 0; + int rc; + int keyno, i; + PKT_secret_key *copied_sk = NULL; + PKT_secret_key *sk; + size_t n; + const char *s; + int allow_keyno[3]; + + assert (node->pkt->pkttype == PKT_SECRET_KEY + || node->pkt->pkttype == PKT_SECRET_SUBKEY); + sk = node->pkt->pkt.secret_key; + + if (get_info_for_key_operation (&info)) + return 0; + + show_card_key_info (&info); + + if (!is_RSA (sk->pubkey_algo) || nbits_from_sk (sk) != 1024 ) + { + tty_printf ("You may only store a 1024 bit RSA key on the card\n"); + tty_printf ("\n"); + goto leave; + } + + allow_keyno[0] = (!use || (use & (PUBKEY_USAGE_SIG))); + allow_keyno[1] = (!use || (use & (PUBKEY_USAGE_ENC))); + allow_keyno[2] = (!use || (use & (PUBKEY_USAGE_SIG|PUBKEY_USAGE_AUTH))); + + tty_printf (_("Please select where to store the key:\n")); + + if (allow_keyno[0]) + tty_printf (_(" (1) Signature key\n")); + if (allow_keyno[1]) + tty_printf (_(" (2) Encryption key\n")); + if (allow_keyno[2]) + tty_printf (_(" (3) Authentication key\n")); + + for (;;) + { + char *answer = cpr_get ("cardedit.genkeys.storekeytype", + _("Your selection? ")); + cpr_kill_prompt(); + if (*answer == CONTROL_D || !*answer) + { + xfree (answer); + goto leave; + } + keyno = *answer? atoi(answer): 0; + xfree(answer); + if (keyno >= 1 && keyno <= 3 && allow_keyno[keyno-1]) + break; /* Okay. */ + tty_printf(_("Invalid selection.\n")); + } + + if (replace_existing_key_p (&info, keyno)) + goto leave; + + /* Unprotect key. */ + switch (is_secret_key_protected (sk) ) + { + case 0: /* Not protected. */ + break; + case -1: + log_error (_("unknown key protection algorithm\n")); + goto leave; + default: + if (sk->protect.s2k.mode == 1001) + { + log_error (_("secret parts of key are not available\n")); + goto leave; + } + if (sk->protect.s2k.mode == 1002) + { + log_error (_("secret key already stored on a card\n")); + goto leave; + } + /* We better copy the key before we unprotect it. */ + copied_sk = sk = copy_secret_key (NULL, sk); + rc = check_secret_key (sk, 0); + if (rc) + goto leave; + } + + rc = save_unprotected_key_to_card (sk, keyno); + if (rc) + goto leave; + + /* Get back to the maybe protected original secret key. */ + if (copied_sk) + { + free_secret_key (copied_sk); + copied_sk = NULL; + } + sk = node->pkt->pkt.secret_key; + + /* Get rid of the secret key parameters and store the serial numer. */ + n = pubkey_get_nskey (sk->pubkey_algo); + for (i=pubkey_get_npkey (sk->pubkey_algo); i < n; i++) + { + mpi_free (sk->skey[i]); + sk->skey[i] = NULL; + } + i = pubkey_get_npkey (sk->pubkey_algo); + sk->skey[i] = mpi_set_opaque (NULL, xstrdup ("dummydata"), 10); + sk->is_protected = 1; + sk->protect.s2k.mode = 1002; + s = info.serialno; + for (sk->protect.ivlen=0; sk->protect.ivlen < 16 && *s && s[1]; + sk->protect.ivlen++, s += 2) + sk->protect.iv[sk->protect.ivlen] = xtoi_2 (s); + + okay = 1; + + leave: + if (copied_sk) + free_secret_key (copied_sk); + agent_release_card_info (&info); + return okay; +#else + return 0; +#endif +} + + + +/* Data used by the command parser. This needs to be outside of the + function scope to allow readline based command completion. */ +enum cmdids + { + cmdNOP = 0, + cmdQUIT, cmdADMIN, cmdHELP, cmdLIST, cmdDEBUG, cmdVERIFY, + cmdNAME, cmdURL, cmdFETCH, cmdLOGIN, cmdLANG, cmdSEX, cmdCAFPR, + cmdFORCESIG, cmdGENERATE, cmdPASSWD, cmdPRIVATEDO, + cmdINVCMD + }; + +static struct +{ + const char *name; + enum cmdids id; + int admin_only; + const char *desc; +} cmds[] = + { + { "quit" , cmdQUIT , 0, N_("quit this menu")}, + { "q" , cmdQUIT , 0, NULL }, + { "admin" , cmdADMIN , 0, N_("show admin commands")}, + { "help" , cmdHELP , 0, N_("show this help")}, + { "?" , cmdHELP , 0, NULL }, + { "list" , cmdLIST , 0, N_("list all available data")}, + { "l" , cmdLIST , 0, NULL }, + { "debug" , cmdDEBUG , 0, NULL }, + { "name" , cmdNAME , 1, N_("change card holder's name")}, + { "url" , cmdURL , 1, N_("change URL to retrieve key")}, + { "fetch" , cmdFETCH , 0, N_("fetch the key specified in the card URL")}, + { "login" , cmdLOGIN , 1, N_("change the login name")}, + { "lang" , cmdLANG , 1, N_("change the language preferences")}, + { "sex" , cmdSEX , 1, N_("change card holder's sex")}, + { "cafpr" , cmdCAFPR , 1, N_("change a CA fingerprint")}, + { "forcesig", cmdFORCESIG, 1, N_("toggle the signature force PIN flag")}, + { "generate", cmdGENERATE, 1, N_("generate new keys")}, + { "passwd" , cmdPASSWD, 0, N_("menu to change or unblock the PIN")}, + { "verify" , cmdVERIFY, 0, N_("verify the PIN and list all data")}, + /* Note, that we do not announce this command yet. */ + { "privatedo", cmdPRIVATEDO, 0, NULL }, + { NULL, cmdINVCMD, 0, NULL } + }; + + +#if GNUPG_MAJOR_VERSION == 1 && defined (HAVE_LIBREADLINE) + +/* These two functions are used by readline for command completion. */ + +static char * +command_generator(const char *text,int state) +{ + static int list_index,len; + const char *name; + + /* If this is a new word to complete, initialize now. This includes + saving the length of TEXT for efficiency, and initializing the + index variable to 0. */ + if(!state) + { + list_index=0; + len=strlen(text); + } + + /* Return the next partial match */ + while((name=cmds[list_index].name)) + { + /* Only complete commands that have help text */ + if(cmds[list_index++].desc && strncmp(name,text,len)==0) + return strdup(name); + } + + return NULL; +} + +static char ** +card_edit_completion(const char *text, int start, int end) +{ + /* If we are at the start of a line, we try and command-complete. + If not, just do nothing for now. */ + + if(start==0) + return rl_completion_matches(text,command_generator); + + rl_attempted_completion_over=1; + + return NULL; +} +#endif /* GNUPG_MAJOR_VERSION == 1 && HAVE_LIBREADLINE */ + +/* Menu to edit all user changeable values on an OpenPGP card. Only + Key creation is not handled here. */ +void +card_edit (STRLIST commands) +{ + enum cmdids cmd = cmdNOP; + int have_commands = !!commands; + int redisplay = 1; + char *answer = NULL; + int did_checkpin = 0, allow_admin=0; + char serialnobuf[50]; + + + if (opt.command_fd != -1) + ; + else if (opt.batch && !have_commands) + { + log_error(_("can't do this in batch mode\n")); + goto leave; + } + + for (;;) + { + int arg_number; + const char *arg_string = ""; + char *p; + int i; + int cmd_admin_only; + + tty_printf("\n"); + if (redisplay ) + { + if (opt.with_colons) + { + card_status (stdout, serialnobuf, DIM (serialnobuf)); + fflush (stdout); + } + else + { + card_status (NULL, serialnobuf, DIM (serialnobuf)); + tty_printf("\n"); + } + redisplay = 0; + } + + do + { + xfree (answer); + if (have_commands) + { + if (commands) + { + answer = xstrdup (commands->d); + commands = commands->next; + } + else if (opt.batch) + { + answer = xstrdup ("quit"); + } + else + have_commands = 0; + } + + if (!have_commands) + { +#if GNUPG_MAJOR_VERSION == 1 + tty_enable_completion (card_edit_completion); +#endif + answer = cpr_get_no_help("cardedit.prompt", _("Command> ")); + cpr_kill_prompt(); +#if GNUPG_MAJOR_VERSION == 1 + tty_disable_completion (); +#endif + } + trim_spaces(answer); + } + while ( *answer == '#' ); + + arg_number = 0; /* Yes, here is the init which egcc complains about */ + cmd_admin_only = 0; + if (!*answer) + cmd = cmdLIST; /* Default to the list command */ + else if (*answer == CONTROL_D) + cmd = cmdQUIT; + else + { + if ((p=strchr (answer,' '))) + { + *p++ = 0; + trim_spaces (answer); + trim_spaces (p); + arg_number = atoi(p); + arg_string = p; + } + + for (i=0; cmds[i].name; i++ ) + if (!ascii_strcasecmp (answer, cmds[i].name )) + break; + + cmd = cmds[i].id; + cmd_admin_only = cmds[i].admin_only; + } + + if (!allow_admin && cmd_admin_only) + { + tty_printf ("\n"); + tty_printf (_("Admin-only command\n")); + continue; + } + + switch (cmd) + { + case cmdHELP: + for (i=0; cmds[i].name; i++ ) + if(cmds[i].desc + && (!cmds[i].admin_only || (cmds[i].admin_only && allow_admin))) + tty_printf("%-10s %s\n", cmds[i].name, _(cmds[i].desc) ); + break; + + case cmdADMIN: + if ( !strcmp (arg_string, "on") ) + allow_admin = 1; + else if ( !strcmp (arg_string, "off") ) + allow_admin = 0; + else if ( !strcmp (arg_string, "verify") ) + { + /* Force verification of the Admin Command. However, + this is only done if the retry counter is at initial + state. */ + char *tmp = xmalloc (strlen (serialnobuf) + 6 + 1); + strcpy (stpcpy (tmp, serialnobuf), "[CHV3]"); + allow_admin = !agent_scd_checkpin (tmp); + xfree (tmp); + } + else /* Toggle. */ + allow_admin=!allow_admin; + if(allow_admin) + tty_printf(_("Admin commands are allowed\n")); + else + tty_printf(_("Admin commands are not allowed\n")); + break; + + case cmdVERIFY: + agent_scd_checkpin (serialnobuf); + redisplay = 1; + break; + + case cmdLIST: + redisplay = 1; + break; + + case cmdNAME: + change_name (); + break; + + case cmdURL: + change_url (); + break; + + case cmdFETCH: + fetch_url(); + break; + + case cmdLOGIN: + change_login (arg_string); + break; + + case cmdLANG: + change_lang (); + break; + + case cmdSEX: + change_sex (); + break; + + case cmdCAFPR: + if ( arg_number < 1 || arg_number > 3 ) + tty_printf ("usage: cafpr N\n" + " 1 <= N <= 3\n"); + else + change_cafpr (arg_number); + break; + + case cmdPRIVATEDO: + if ( arg_number < 1 || arg_number > 4 ) + tty_printf ("usage: privatedo N\n" + " 1 <= N <= 4\n"); + else + change_private_do (arg_string, arg_number); + break; + + case cmdFORCESIG: + toggle_forcesig (); + break; + + case cmdGENERATE: + generate_card_keys (serialnobuf); + break; + + case cmdPASSWD: + change_pin (0, allow_admin); + did_checkpin = 0; /* Need to reset it of course. */ + break; + + case cmdQUIT: + goto leave; + + case cmdNOP: + break; + + case cmdINVCMD: + default: + tty_printf ("\n"); + tty_printf (_("Invalid command (try \"help\")\n")); + break; + } /* End command switch. */ + } /* End of main menu loop. */ + + leave: + xfree (answer); +} + diff --git a/g10/cardglue.c b/g10/cardglue.c new file mode 100644 index 0000000..1bfb9e4 --- /dev/null +++ b/g10/cardglue.c @@ -0,0 +1,1432 @@ +/* cardglue.c - mainly dispatcher for card related functions. + * Copyright (C) 2003, 2004, 2005, 2006 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#include <config.h> +#ifndef ENABLE_CARD_SUPPORT +#error not configured for card support. +#endif +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <stdarg.h> +#include <assert.h> +#include "options.h" +#include "packet.h" +#include "errors.h" +#include "memory.h" +#include "util.h" +#include "main.h" +#include "status.h" +#include "ttyio.h" +#include "i18n.h" + +#include "cardglue.h" +#include "apdu.h" +#include "app-common.h" + + + +struct ctrl_ctx_s +{ + assuan_error_t (*status_cb)(void *opaque, const char *line); + void *status_cb_arg; +}; + + +struct pincb_parm_s +{ + const char *sn; +}; + + +struct writekey_parm_s +{ + assuan_context_t ctx; + const unsigned char *keydata; + size_t keydatalen; +}; + + + +static char *default_reader_port; +static app_t current_app; + + +/* Local prototypes. */ +static assuan_error_t learn_status_cb (void *opaque, const char *line); + + +/* To avoid cluttering the code with bunches of ifdefs we use a few + dummy functions instead and defines. */ +#ifndef ENABLE_AGENT_SUPPORT + +#define ASSUAN_LINELENGTH 100 + +static assuan_context_t +agent_open (int try, const char *orig_codeset) +{ + return NULL; +} + +void +agent_close (assuan_context_t ctx) +{ +} + +const char * +assuan_strerror (assuan_error_t err) +{ + return "no Assuan support"; +} + +assuan_error_t +assuan_transact (assuan_context_t ctx, + const char *command, + assuan_error_t (*data_cb)(void *, const void *, size_t), + void *data_cb_arg, + assuan_error_t (*inquire_cb)(void*, const char *), + void *inquire_cb_arg, + assuan_error_t (*status_cb)(void*, const char *), + void *status_cb_arg) +{ + return 100; /* ASSUAN_NOT_IMPLEMENTED */ +} +assuan_error_t +assuan_send_data (assuan_context_t ctx, const void *buffer, size_t length) +{ + return 100; /* ASSUAN_NOT_IMPLEMENTED */ +} +#endif /*!ENABLE_AGENT_SUPPORT*/ + + +/* Create a serialno/fpr string from the serial number and the secret + key. caller must free the returned string. There is no error + return. [Taken from 1.9's keyid.c]*/ +char * +serialno_and_fpr_from_sk (const unsigned char *sn, size_t snlen, + PKT_secret_key *sk) +{ + unsigned char fpr[MAX_FINGERPRINT_LEN]; + size_t fprlen; + char *buffer, *p; + int i; + + fingerprint_from_sk (sk, fpr, &fprlen); + buffer = p = xmalloc (snlen*2 + 1 + fprlen*2 + 1); + for (i=0; i < snlen; i++, p+=2) + sprintf (p, "%02X", sn[i]); + *p++ = '/'; + for (i=0; i < fprlen; i++, p+=2) + sprintf (p, "%02X", fpr[i]); + *p = 0; + return buffer; +} + + +/* 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; + + va_start (arg_ptr, keyword); + + p = buf; + n = 0; + valuelen = strlen (keyword); + for ( ; valuelen && n < DIM (buf)-2; n++, valuelen--, keyword++) + *p++ = *keyword; + + while ( (value = va_arg (arg_ptr, const unsigned char *)) ) + { + valuelen = va_arg (arg_ptr, size_t); + if (!valuelen) + continue; /* empty buffer */ + if (n) + { + *p++ = ' '; + n++; + } + for ( ; valuelen && n < DIM (buf)-2; n++, valuelen--, value++) + { + if (*value < ' ' || *value == '+') + { + sprintf (p, "%%%02X", *value); + p += 3; + } + else if (*value == ' ') + *p++ = '+'; + else + *p++ = *value; + } + } + *p = 0; + if (ctrl && ctrl->status_cb) + ctrl->status_cb (ctrl->status_cb_arg, buf); + + va_end (arg_ptr); +} + + +/* Replacement function of the Libgcrypt onewhich is used in gnupg + 1.9. Thus function computes the digest of ALGO from the data in + BUFFER of LENGTH. ALGO must be supported. */ +void +gcry_md_hash_buffer (int algo, void *digest, + const void *buffer, size_t length) +{ + MD_HANDLE h = md_open (algo, 0); + if (!h) + BUG(); + md_write (h, (byte *) buffer, length); + md_final (h); + memcpy (digest, md_read (h, algo), md_digest_length (algo)); + md_close (h); +} + + +/* This is a limited version of the one in 1.9 but it should be + sufficient here. */ +void +log_printf (const char *fmt, ...) +{ + va_list arg_ptr; + + va_start (arg_ptr, fmt); + vfprintf (log_stream (), fmt, arg_ptr); + va_end (arg_ptr); +} + + + +/* Print a hexdump of BUFFER. With TEXT of NULL print just the raw + dump, with TEXT just an empty string, print a trailing linefeed, + otherwise print an entire debug line. */ +void +log_printhex (const char *text, const void *buffer, size_t length) +{ + if (text && *text) + log_debug ("%s ", text); + if (length) + { + const unsigned char *p = buffer; + log_printf ("%02X", *p); + for (length--, p++; length--; p++) + log_printf (" %02X", *p); + } + if (text) + log_printf ("\n"); +} + + + +void +app_set_default_reader_port (const char *portstr) +{ + xfree (default_reader_port); + default_reader_port = portstr? xstrdup (portstr): NULL; +} + + +void +card_set_reader_port (const char *portstr) +{ + app_set_default_reader_port (portstr); +} + + +/* Retrieve the serial number and the time of the last update of the + card. The serial number is returned as a malloced string (hex + encoded) in SERIAL and the time of update is returned in STAMP. If + no update time is available the returned value is 0. Caller must + free SERIAL unless the function returns an error. */ +int +app_get_serial_and_stamp (app_t app, char **serial, time_t *stamp) +{ + unsigned char *buf, *p; + int i; + + if (!app || !serial || !stamp) + return gpg_error (GPG_ERR_INV_VALUE); + + *serial = NULL; + *stamp = 0; /* not available */ + + buf = xtrymalloc (app->serialnolen * 2 + 1); + if (!buf) + return gpg_error_from_errno (errno); + for (p=buf, i=0; i < app->serialnolen; p +=2, i++) + sprintf (p, "%02X", app->serialno[i]); + *p = 0; + *serial = buf; + return 0; +} + + + +/* Release the card info structure. */ +void +agent_release_card_info (struct agent_card_info_s *info) +{ + int i; + + if (!info) + return; + + xfree (info->serialno); info->serialno = NULL; + xfree (info->disp_name); info->disp_name = NULL; + xfree (info->disp_lang); info->disp_lang = NULL; + xfree (info->pubkey_url); info->pubkey_url = NULL; + xfree (info->login_data); info->login_data = NULL; + info->fpr1valid = info->fpr2valid = info->fpr3valid = 0; + info->cafpr1valid = info->cafpr2valid = info->cafpr3valid = 0; + for (i=0; i < 4; i++) + { + xfree (info->private_do[i]); + info->private_do[i] = NULL; + } +} + + +/* Print an error message for a failed assuan_transact and return a + gpg error code. No error is printed if RC is 0. */ +static gpg_error_t +test_transact (int rc, const char *command) +{ + if (!rc) + return 0; + log_error ("sending command `%s' to agent failed: %s\n", + command, assuan_strerror (rc)); + return gpg_error (GPG_ERR_CARD); +} + + +/* Try to open a card using an already running agent. Prepare a + proper application context and return it. */ +static app_t +open_card_via_agent (int *scd_available) +{ + assuan_context_t ctx; + app_t app; + struct agent_card_info_s info; + int rc; + + *scd_available = 0; + ctx = agent_open (1, NULL); + if (!ctx) + return NULL; + + /* Request the serialbnumber of the card. If we get + NOT_SUPPORTED or NO_SCDAEMON back, the gpg-agent either has + disabled scdaemon or it can't be used. We close the connection + in this case and use our own code. This may happen if just the + gpg-agent has been installed for the sake of passphrase + caching. */ + memset (&info, 0, sizeof info); + rc = assuan_transact (ctx, "SCD SERIALNO openpgp", + NULL, NULL, NULL, NULL, + learn_status_cb, &info); + if (rc) + { + if ((rc & 0xffff) == 60 || (rc & 0xffff) == 119) + ; /* No scdaemon available to gpg-agent. */ + else + { + write_status_text (STATUS_CARDCTRL, "4"); + log_info ("selecting openpgp failed: %s\n", assuan_strerror (rc)); + *scd_available = 1; + } + agent_release_card_info (&info); + agent_close (ctx); + return NULL; + } + + app = xcalloc (1, sizeof *app); + app->assuan_ctx = ctx; + + return app; +} + + + +/* Open the current card and select the openpgp application. Return + an APP context handle to be used for further procesing or NULL on + error or if no OpenPGP application exists.*/ +static app_t +open_card (void) +{ + int slot = -1; + int rc; + app_t app; + int did_shutdown = 0; + int retry_count = 0; + + /* First check whether we can contact a gpg-agent and divert all + operation to it. This is required because gpg as well as the + agent require exclusive access to the reader. */ + if (opt.use_agent) + { + int scd_available; + + app = open_card_via_agent (&scd_available); + if (app) + goto ready; /* Yes, there is a agent with a usable card, go that way. */ + if (scd_available) + return NULL; /* agent avilabale but card problem. */ + } + + + /* No agent or usable agent, thus we do it on our own. */ + card_close (); + + retry: + if (did_shutdown) + apdu_reset (slot); + else + { + slot = apdu_open_reader (default_reader_port); + if (slot == -1) + { + write_status_text (STATUS_CARDCTRL, "5"); + log_error (_("card reader not available\n")); + return NULL; + } + } + + app = xcalloc (1, sizeof *app); + app->slot = slot; + rc = app_select_openpgp (app); + if (opt.limit_card_insert_tries + && ++retry_count >= opt.limit_card_insert_tries) + ; + else if (rc && !opt.batch) + { + write_status_text (STATUS_CARDCTRL, "1"); + + did_shutdown = !!apdu_shutdown_reader (slot); + + if ( cpr_get_answer_okay_cancel ("cardctrl.insert_card.okay", + _("Please insert the card and hit return or enter 'c' to cancel: "), + 1) ) + { + if (!did_shutdown) + apdu_close_reader (slot); + xfree (app); + goto retry; + } + } + if (rc) + { + write_status_text (STATUS_CARDCTRL, "4"); + log_info (_("selecting openpgp failed: %s\n"), gpg_strerror (rc)); + apdu_close_reader (slot); + xfree (app); + return NULL; + } + + ready: + app->initialized = 1; + current_app = app; + if (is_status_enabled () ) + { + int i; + char *p, *buf; + + buf = xmalloc (5 + app->serialnolen * 2 + 1); + p = stpcpy (buf, "3 "); + for (i=0; i < app->serialnolen; p +=2, i++) + sprintf (p, "%02X", app->serialno[i]); + write_status_text (STATUS_CARDCTRL, buf); + xfree (buf); + } + + return app; +} + + +void +card_close (void) +{ + if (current_app) + { + app_t app = current_app; + current_app = NULL; + + if (app->assuan_ctx) + agent_close (app->assuan_ctx); + else + apdu_close_reader (app->slot); + xfree (app); + } +} + + +/* Format a cache ID from the serialnumber in SN and return it as an + allocated string. In case of an error NULL is returned. */ +static char * +format_cacheid (const char *sn) +{ + const char *s; + size_t snlen; + char *cacheid = NULL; + + /* The serialnumber we use for a card is "CARDSN:serialno". Where + serialno is the BCD string (i.e. hex string) with the full + number. The serial number expect here constsis of hexdigits + followed by other characters, we cut off these other + characters. */ + if (sn) + { + for (s=sn,snlen=0; hexdigitp (s); s++, snlen++) + ; + if (snlen == 32) + { + /* Yes, this looks indeed like an OpenPGP card S/N. */ + cacheid = xtrymalloc (7+snlen+1); + if (cacheid) + { + memcpy (cacheid, "CARDSN:", 7); + memcpy (cacheid+7, sn, snlen); + cacheid[7+snlen] = 0; + } + } + } + return cacheid; +} + + +/* If RC is not 0, write an appropriate status message. */ +static void +status_sc_op_failure (int rc) +{ + if (rc == G10ERR_CANCELED) + write_status_text (STATUS_SC_OP_FAILURE, "1"); + else if (rc == G10ERR_BAD_PASS) + write_status_text (STATUS_SC_OP_FAILURE, "2"); + else if (rc) + write_status (STATUS_SC_OP_FAILURE); +} + + +/* Check that the serial number of the current card (as described by + APP) matches SERIALNO. If there is no match and we are not in + batch mode, present a prompt to insert the desired card. The + function returnd 0 if the present card is okay, -1 if the user + selected to insert a new card or an error value. Note that the + card context will be closed in all cases except for 0 as return + value and if it was possible to merely shutdown the reader. */ +static int +check_card_serialno (app_t app, const char *serialno) +{ + const char *s; + int ask = 0; + int n; + + for (s = serialno, n=0; *s != '/' && hexdigitp (s); s++, n++) + ; + if (n != 32) + { + log_error ("invalid serial number in keyring detected\n"); + return gpg_error (GPG_ERR_INV_ID); + } + if (app->serialnolen != 16) + ask = 1; + for (s = serialno, n=0; !ask && n < 16; s += 2, n++) + if (app->serialno[n] != xtoi_2 (s)) + ask = 1; + if (ask) + { + char buf[5+32+1]; + int did_shutdown = 0; + + if (current_app && !apdu_shutdown_reader (current_app->slot)) + did_shutdown = 1; + else + card_close (); + + if (!opt.batch) + tty_printf (_("Please remove the current card and " + "insert the one with serial number:\n" + " %.*s\n"), 32, serialno); + + sprintf (buf, "1 %.32s", serialno); + write_status_text (STATUS_CARDCTRL, buf); + + if ( !opt.batch + && cpr_get_answer_okay_cancel ("cardctrl.change_card.okay", + _("Hit return when ready " + "or enter 'c' to cancel: "), + 1) ) + { + card_close (); + return -1; + } + if (did_shutdown) + apdu_reset (current_app->slot); + else + card_close (); + return gpg_error (GPG_ERR_INV_ID); + } + return 0; +} + + +/* Take a 20 byte hexencoded string and put it into the the provided + 20 byte buffer FPR in binary format. */ +static int +unhexify_fpr (const char *hexstr, unsigned char *fpr) +{ + const char *s; + int n; + + for (s=hexstr, n=0; hexdigitp (s); s++, n++) + ; + if (*s || (n != 40)) + return 0; /* no fingerprint (invalid or wrong length). */ + n /= 2; + for (s=hexstr, n=0; *s; s += 2, n++) + fpr[n] = xtoi_2 (s); + return 1; /* okay */ +} + +/* Take the serial number from LINE and return it verbatim in a newly + allocated string. We make sure that only hex characters are + returned. */ +static char * +store_serialno (const char *line) +{ + const char *s; + char *p; + + for (s=line; hexdigitp (s); s++) + ; + p = xmalloc (s + 1 - line); + memcpy (p, line, s-line); + p[s-line] = 0; + return p; +} + + + +static assuan_error_t +learn_status_cb (void *opaque, const char *line) +{ + struct agent_card_info_s *parm = opaque; + const char *keyword = line; + int keywordlen; + int i; + +/* log_debug ("got status line `%s'\n", line); */ + for (keywordlen=0; *line && !spacep (line); line++, keywordlen++) + ; + while (spacep (line)) + line++; + + if (keywordlen == 8 && !memcmp (keyword, "SERIALNO", keywordlen)) + { + xfree (parm->serialno); + parm->serialno = store_serialno (line); + } + else if (keywordlen == 9 && !memcmp (keyword, "DISP-NAME", keywordlen)) + { + xfree (parm->disp_name); + parm->disp_name = unescape_percent_string (line); + } + else if (keywordlen == 9 && !memcmp (keyword, "DISP-LANG", keywordlen)) + { + xfree (parm->disp_lang); + parm->disp_lang = unescape_percent_string (line); + } + else if (keywordlen == 8 && !memcmp (keyword, "DISP-SEX", keywordlen)) + { + parm->disp_sex = *line == '1'? 1 : *line == '2' ? 2: 0; + } + else if (keywordlen == 10 && !memcmp (keyword, "PUBKEY-URL", keywordlen)) + { + xfree (parm->pubkey_url); + parm->pubkey_url = unescape_percent_string (line); + } + else if (keywordlen == 10 && !memcmp (keyword, "LOGIN-DATA", keywordlen)) + { + xfree (parm->login_data); + parm->login_data = unescape_percent_string (line); + } + else if (keywordlen == 11 && !memcmp (keyword, "SIG-COUNTER", keywordlen)) + { + parm->sig_counter = strtoul (line, NULL, 0); + } + else if (keywordlen == 10 && !memcmp (keyword, "CHV-STATUS", keywordlen)) + { + char *p, *buf; + + buf = p = unescape_percent_string (line); + if (buf) + { + while (spacep (p)) + p++; + parm->chv1_cached = atoi (p); + while (*p && !spacep (p)) + p++; + while (spacep (p)) + p++; + for (i=0; *p && i < 3; i++) + { + parm->chvmaxlen[i] = atoi (p); + while (*p && !spacep (p)) + p++; + while (spacep (p)) + p++; + } + for (i=0; *p && i < 3; i++) + { + parm->chvretry[i] = atoi (p); + while (*p && !spacep (p)) + p++; + while (spacep (p)) + p++; + } + xfree (buf); + } + } + else if (keywordlen == 7 && !memcmp (keyword, "KEY-FPR", keywordlen)) + { + int no = atoi (line); + while (* line && !spacep (line)) + line++; + while (spacep (line)) + line++; + if (no == 1) + parm->fpr1valid = unhexify_fpr (line, parm->fpr1); + else if (no == 2) + parm->fpr2valid = unhexify_fpr (line, parm->fpr2); + else if (no == 3) + parm->fpr3valid = unhexify_fpr (line, parm->fpr3); + } + else if (keywordlen == 8 && !memcmp (keyword, "KEY-TIME", keywordlen)) + { + int no = atoi (line); + while (* line && !spacep (line)) + line++; + while (spacep (line)) + line++; + if (no == 1) + parm->fpr1time = strtoul (line, NULL, 10); + else if (no == 2) + parm->fpr2time = strtoul (line, NULL, 10); + else if (no == 3) + parm->fpr3time = strtoul (line, NULL, 10); + } + else if (keywordlen == 6 && !memcmp (keyword, "CA-FPR", keywordlen)) + { + int no = atoi (line); + while (*line && !spacep (line)) + line++; + while (spacep (line)) + line++; + if (no == 1) + parm->cafpr1valid = unhexify_fpr (line, parm->cafpr1); + else if (no == 2) + parm->cafpr2valid = unhexify_fpr (line, parm->cafpr2); + else if (no == 3) + parm->cafpr3valid = unhexify_fpr (line, parm->cafpr3); + } + else if (keywordlen == 12 && !memcmp (keyword, "PRIVATE-DO-", 11) + && strchr ("1234", keyword[11])) + { + int no = keyword[11] - '1'; + assert (no >= 0 && no <= 3); + xfree (parm->private_do[no]); + parm->private_do[no] = unescape_percent_string (line); + } + + return 0; +} + + +/* Return card info. */ +int +agent_learn (struct agent_card_info_s *info) +{ + app_t app; + int rc; + struct ctrl_ctx_s ctrl; + time_t stamp; + char *serial; + + app = current_app? current_app : open_card (); + if (!app) + return gpg_error (GPG_ERR_CARD); + + memset (info, 0, sizeof *info); + + if (app->assuan_ctx) + { + rc = assuan_transact (app->assuan_ctx, "SCD LEARN --force", + NULL, NULL, NULL, NULL, + learn_status_cb, info); + rc = test_transact (rc, "SCD LEARN"); + } + else + { + memset (&ctrl, 0, sizeof ctrl); + ctrl.status_cb = learn_status_cb; + ctrl.status_cb_arg = info; + + rc = app_get_serial_and_stamp (app, &serial, &stamp); + if (!rc) + { + send_status_info (&ctrl, "SERIALNO", + serial, strlen(serial), NULL, 0); + xfree (serial); + rc = app->fnc.learn_status (app, &ctrl); + } + } + + return rc; +} + + +/* Get an attribute from the card. Make sure info is initialized. */ +int +agent_scd_getattr (const char *name, struct agent_card_info_s *info) +{ + int rc; + app_t app; + struct ctrl_ctx_s ctrl; + + app = current_app? current_app : open_card (); + if (!app) + return gpg_error (GPG_ERR_CARD); + + if (app->assuan_ctx) + { + char line[ASSUAN_LINELENGTH]; + + /* We assume that NAME does not need escaping. */ + if (12 + strlen (name) > DIM(line)-1) + return gpg_error (GPG_ERR_CARD); + stpcpy (stpcpy (line, "SCD GETATTR "), name); + + rc = test_transact (assuan_transact (app->assuan_ctx, line, + NULL, NULL, NULL, NULL, + learn_status_cb, info), + "SCD GETATTR"); + } + else + { + ctrl.status_cb = learn_status_cb; + ctrl.status_cb_arg = info; + rc = app->fnc.getattr (app, &ctrl, name); + } + + return rc; +} + + + +static int +pin_cb (void *opaque, const char *info, char **retstr) +{ + struct pincb_parm_s *parm = opaque; + char *value; + int canceled; + int isadmin = 0; + int newpin = 0; + const char *again_text = NULL; + const char *ends, *s; + char *cacheid = NULL; + + *retstr = NULL; + /* log_debug ("asking for PIN '%s'\n", info); */ + + /* We use a special prefix to check whether the Admin PIN has been + requested. */ + if (info && *info =='|' && (ends=strchr (info+1, '|'))) + { + for (s=info+1; s < ends; s++) + { + if (*s == 'A') + isadmin = 1; + else if (*s == 'N') + newpin = 1; + } + info = ends+1; + } + else if (info && *info == '|') + log_debug ("pin_cb called without proper PIN info hack\n"); + + /* If we are not requesting a new PIN and we are not requesting an + AdminPIN, compute a string to be used as the cacheID for + gpg-agent. */ + if (!newpin && !isadmin && parm) + { + cacheid = format_cacheid (parm->sn); + } + else if (newpin && parm) + { + /* Make really sure that it is not cached anymore. */ + agent_clear_pin_cache (parm->sn); + } + + + again: + if (is_status_enabled()) + { + if (parm && parm->sn && *parm->sn) + { + char *buf = xmalloc ( 10 + strlen (parm->sn) + 1); + strcpy (stpcpy (buf, isadmin? "OPENPGP 3 ":"OPENPGP 1 "), parm->sn); + write_status_text (STATUS_NEED_PASSPHRASE_PIN, buf); + xfree (buf); + } + else + write_status_text (STATUS_NEED_PASSPHRASE_PIN, + isadmin? "OPENPGP 3" : "OPENPGP 1"); + } + + value = ask_passphrase (info, again_text, + newpin && isadmin? "passphrase.adminpin.new.ask" : + newpin? "passphrase.pin.new.ask" : + isadmin? "passphrase.adminpin.ask" : + "passphrase.pin.ask", + newpin && isadmin? _("Enter New Admin PIN: ") : + newpin? _("Enter New PIN: ") : + isadmin? _("Enter Admin PIN: ") + : _("Enter PIN: "), + cacheid, + &canceled); + xfree (cacheid); + cacheid = NULL; + again_text = NULL; + if (!value && canceled) + return G10ERR_CANCELED; + else if (!value) + return G10ERR_GENERAL; + + if (newpin) + { + char *value2; + + value2 = ask_passphrase (info, NULL, + "passphrase.pin.repeat", + _("Repeat this PIN: "), + NULL, + &canceled); + if (!value2 && canceled) + { + xfree (value); + return G10ERR_CANCELED; + } + else if (!value2) + { + xfree (value); + return G10ERR_GENERAL; + } + if (strcmp (value, value2)) + { + again_text = N_("PIN not correctly repeated; try again"); + xfree (value2); + xfree (value); + value = NULL; + goto again; + } + xfree (value2); + } + + *retstr = value; + return 0; +} + + + +/* Send a SETATTR command to the SCdaemon. */ +int +agent_scd_setattr (const char *name, + const unsigned char *value, size_t valuelen, + const char *serialno) +{ + app_t app; + int rc; + struct pincb_parm_s parm; + + memset (&parm, 0, sizeof parm); + parm.sn = serialno; + + app = current_app? current_app : open_card (); + if (!app) + return gpg_error (GPG_ERR_CARD); + + if (app->assuan_ctx) + { + char line[ASSUAN_LINELENGTH]; + char *p; + + /* We assume that NAME does not need escaping. */ + if (12 + strlen (name) > DIM(line)-1) + return gpg_error (GPG_ERR_CARD); + p = stpcpy (stpcpy (line, "SCD SETATTR "), name); + *p++ = ' '; + for (; valuelen; value++, valuelen--) + { + if (p >= line + DIM(line)-5 ) + return gpg_error (GPG_ERR_CARD); + if (*value < ' ' || *value == '+' || *value == '%') + { + sprintf (p, "%%%02X", *value); + p += 3; + } + else if (*value == ' ') + *p++ = '+'; + else + *p++ = *value; + } + *p = 0; + + rc = test_transact (assuan_transact (app->assuan_ctx, line, + NULL, NULL, NULL, NULL, NULL, NULL), + "SCD SETATTR"); + } + else + { + rc = app->fnc.setattr (app, name, pin_cb, &parm, value, valuelen); + } + + status_sc_op_failure (rc); + return rc; +} + + +/* Handle a KEYDATA inquiry. Note, we only send the data, + assuan_transact takes care of flushing and writing the end */ +static assuan_error_t +inq_writekey_parms (void *opaque, const char *keyword) +{ + struct writekey_parm_s *parm = opaque; + + return assuan_send_data (parm->ctx, parm->keydata, parm->keydatalen); +} + + +/* Send a WRITEKEY command to the SCdaemon. */ +int +agent_scd_writekey (int keyno, const char *serialno, + const unsigned char *keydata, size_t keydatalen) +{ + app_t app; + int rc; + char line[ASSUAN_LINELENGTH]; + struct pincb_parm_s parm; + + memset (&parm, 0, sizeof parm); + parm.sn = serialno; + + app = current_app? current_app : open_card (); + if (!app) + return gpg_error (GPG_ERR_CARD); + + if (app->assuan_ctx) + { + struct writekey_parm_s parms; + + snprintf (line, DIM(line)-1, "SCD WRITEKEY --force OPENPGP.%d", keyno); + line[DIM(line)-1] = 0; + parms.ctx = app->assuan_ctx; + parms.keydata = keydata; + parms.keydatalen = keydatalen; + rc = test_transact (assuan_transact (app->assuan_ctx, line, + NULL, NULL, + inq_writekey_parms, &parms, + NULL, NULL), + "SCD WRITEKEY"); + } + else + { + snprintf (line, DIM(line)-1, "OPENPGP.%d", keyno); + line[DIM(line)-1] = 0; + rc = app->fnc.writekey (app, NULL, line, 0x0001, + pin_cb, &parm, + keydata, keydatalen); + } + + status_sc_op_failure (rc); + return rc; +} + + + +static assuan_error_t +genkey_status_cb (void *opaque, const char *line) +{ + struct agent_card_genkey_s *parm = opaque; + const char *keyword = line; + int keywordlen; + +/* log_debug ("got status line `%s'\n", line); */ + for (keywordlen=0; *line && !spacep (line); line++, keywordlen++) + ; + while (spacep (line)) + line++; + + if (keywordlen == 7 && !memcmp (keyword, "KEY-FPR", keywordlen)) + { + parm->fprvalid = unhexify_fpr (line, parm->fpr); + } + if (keywordlen == 8 && !memcmp (keyword, "KEY-DATA", keywordlen)) + { + MPI a; + const char *name = line; + char *buf; + + while (*line && !spacep (line)) + line++; + while (spacep (line)) + line++; + + buf = xmalloc ( 2 + strlen (line) + 1); + strcpy (stpcpy (buf, "0x"), line); + a = mpi_alloc (300); + if( mpi_fromstr (a, buf) ) + log_error ("error parsing received key data\n"); + else if (*name == 'n' && spacep (name+1)) + parm->n = a; + else if (*name == 'e' && spacep (name+1)) + parm->e = a; + else + { + log_info ("unknown parameter name in received key data\n"); + mpi_free (a); + } + xfree (buf); + } + else if (keywordlen == 14 && !memcmp (keyword,"KEY-CREATED-AT", keywordlen)) + { + parm->created_at = (u32)strtoul (line, NULL, 10); + } + + return 0; +} + +/* Send a GENKEY command to the SCdaemon. */ +int +agent_scd_genkey (struct agent_card_genkey_s *info, int keyno, int force, + const char *serialno) +{ + app_t app; + char line[ASSUAN_LINELENGTH]; + struct ctrl_ctx_s ctrl; + int rc; + struct pincb_parm_s parm; + + memset (&parm, 0, sizeof parm); + parm.sn = serialno; + + app = current_app? current_app : open_card (); + if (!app) + return gpg_error (GPG_ERR_CARD); + + memset (info, 0, sizeof *info); + + if (app->assuan_ctx) + { + snprintf (line, DIM(line)-1, "SCD GENKEY %s%d", + force? "--force ":"", keyno); + line[DIM(line)-1] = 0; + rc = test_transact (assuan_transact (app->assuan_ctx, line, + NULL, NULL, NULL, NULL, + genkey_status_cb, info), + "SCD GENKEY"); + } + else + { + snprintf (line, DIM(line)-1, "%d", keyno); + ctrl.status_cb = genkey_status_cb; + ctrl.status_cb_arg = info; + rc = app->fnc.genkey (app, &ctrl, line, + force? 1:0, + pin_cb, &parm); + } + + status_sc_op_failure (rc); + return rc; +} + + +static assuan_error_t +membuf_data_cb (void *opaque, const void *buffer, size_t length) +{ + membuf_t *data = opaque; + + if (buffer) + put_membuf (data, buffer, length); + return 0; +} + + +/* Send a PKSIGN command to the SCdaemon. */ +int +agent_scd_pksign (const char *serialno, int hashalgo, + const unsigned char *indata, size_t indatalen, + unsigned char **r_buf, size_t *r_buflen) +{ + struct pincb_parm_s parm; + app_t app; + int rc; + + *r_buf = NULL; + *r_buflen = 0; + memset (&parm, 0, sizeof parm); + parm.sn = serialno; + retry: + app = current_app? current_app : open_card (); + if (!app) + return gpg_error (GPG_ERR_CARD); + + if (app->assuan_ctx) + { + char *p, line[ASSUAN_LINELENGTH]; + membuf_t data; + size_t len; + int i; + + if (indatalen*2 + 50 > DIM(line)) + return gpg_error (GPG_ERR_GENERAL); + + p = stpcpy (line, "SCD SETDATA "); + for (i=0; i < indatalen ; i++, p += 2 ) + sprintf (p, "%02X", indata[i]); + rc = test_transact (assuan_transact (app->assuan_ctx, line, + NULL, NULL, NULL, NULL, NULL, NULL), + "SCD SETDATA"); + if (!rc) + { + init_membuf (&data, 1024); + snprintf (line, DIM(line)-1, "SCD PKSIGN %s%s", + hashalgo == GCRY_MD_RMD160? "--hash=rmd160 ": "", + serialno); + line[DIM(line)-1] = 0; + rc = test_transact (assuan_transact (app->assuan_ctx, line, + membuf_data_cb, &data, + NULL, NULL, NULL, NULL), + "SCD PKSIGN"); + if (rc) + xfree (get_membuf (&data, &len)); + else + *r_buf = get_membuf (&data, r_buflen); + } + } + else + { + /* Check that the card's serialnumber is as required.*/ + rc = check_card_serialno (app, serialno); + if (rc == -1) + goto retry; + + if (!rc) + rc = app->fnc.sign (app, serialno, hashalgo, + pin_cb, &parm, + indata, indatalen, + r_buf, r_buflen); + } + + if (rc) + { + status_sc_op_failure (rc); + if (!app->assuan_ctx) + agent_clear_pin_cache (serialno); + } + return rc; +} + + +/* Send a PKDECRYPT command to the SCdaemon. */ +int +agent_scd_pkdecrypt (const char *serialno, + const unsigned char *indata, size_t indatalen, + unsigned char **r_buf, size_t *r_buflen) +{ + struct pincb_parm_s parm; + app_t app; + int rc; + + *r_buf = NULL; + *r_buflen = 0; + memset (&parm, 0, sizeof parm); + parm.sn = serialno; + retry: + app = current_app? current_app : open_card (); + if (!app) + return gpg_error (GPG_ERR_CARD); + + if (app->assuan_ctx) + { + char *p, line[ASSUAN_LINELENGTH]; + membuf_t data; + size_t len; + int i; + + if (indatalen*2 + 50 > DIM(line)) + return gpg_error (GPG_ERR_GENERAL); + + p = stpcpy (line, "SCD SETDATA "); + for (i=0; i < indatalen ; i++, p += 2 ) + sprintf (p, "%02X", indata[i]); + rc = test_transact (assuan_transact (app->assuan_ctx, line, + NULL, NULL, NULL, NULL, NULL, NULL), + "SCD SETDATA"); + if (!rc) + { + init_membuf (&data, 1024); + snprintf (line, DIM(line)-1, "SCD PKDECRYPT %s", serialno); + line[DIM(line)-1] = 0; + rc = test_transact (assuan_transact (app->assuan_ctx, line, + membuf_data_cb, &data, + NULL, NULL, NULL, NULL), + "SCD PKDECRYPT"); + if (rc) + xfree (get_membuf (&data, &len)); + else + *r_buf = get_membuf (&data, r_buflen); + } + } + else + { + /* Check that the card's serialnumber is as required.*/ + rc = check_card_serialno (app, serialno); + if (rc == -1) + goto retry; + + if (!rc) + rc = app->fnc.decipher (app, serialno, + pin_cb, &parm, + indata, indatalen, + r_buf, r_buflen); + } + + if (rc) + { + status_sc_op_failure (rc); + if (!app->assuan_ctx) + agent_clear_pin_cache (serialno); + } + return rc; +} + +/* Change the PIN of an OpenPGP card or reset the retry + counter. SERIALNO may be NULL or a hex string finally passed to the + passphrase callback. */ +int +agent_scd_change_pin (int chvno, const char *serialno) +{ + app_t app; + int reset = 0; + int rc; + struct pincb_parm_s parm; + + memset (&parm, 0, sizeof parm); + parm.sn = serialno; + + reset = (chvno >= 100); + chvno %= 100; + + app = current_app? current_app : open_card (); + if (!app) + return gpg_error (GPG_ERR_CARD); + + if (app->assuan_ctx) + { + char line[ASSUAN_LINELENGTH]; + + snprintf (line, DIM(line)-1, "SCD PASSWD%s %d", + reset? " --reset":"", chvno); + line[DIM(line)-1] = 0; + rc = test_transact (assuan_transact (app->assuan_ctx, line, + NULL, NULL, NULL, NULL, NULL, NULL), + "SCD PASSWD"); + } + else + { + char chvnostr[50]; + + sprintf (chvnostr, "%d", chvno); + rc = app->fnc.change_pin (app, NULL, chvnostr, reset, + pin_cb, &parm); + } + + status_sc_op_failure (rc); + return rc; +} + +/* Perform a CHECKPIN operation. SERIALNO should be the serial + number of the card - optionally followed by the fingerprint; + however the fingerprint is ignored here. */ +int +agent_scd_checkpin (const char *serialnobuf) +{ + app_t app; + int rc; + struct pincb_parm_s parm; + + memset (&parm, 0, sizeof parm); + parm.sn = serialnobuf; + + app = current_app? current_app : open_card (); + if (!app) + return gpg_error (GPG_ERR_CARD); + + if (app->assuan_ctx) + { + char line[ASSUAN_LINELENGTH]; + + if (15 + strlen (serialnobuf) > DIM(line)-1) + return gpg_error (GPG_ERR_CARD); + stpcpy (stpcpy (line, "SCD CHECKPIN "), serialnobuf); + rc = test_transact (assuan_transact (app->assuan_ctx, line, + NULL, NULL, NULL, NULL, NULL, NULL), + "SCD CHECKPIN"); + } + else + { + rc = app->fnc.check_pin (app, serialnobuf, pin_cb, &parm); + } + + status_sc_op_failure (rc); + return rc; +} + + + +void +agent_clear_pin_cache (const char *sn) +{ + char *cacheid = format_cacheid (sn); + if (cacheid) + { + passphrase_clear_cache (NULL, cacheid, 0); + xfree (cacheid); + } +} diff --git a/g10/cardglue.h b/g10/cardglue.h new file mode 100644 index 0000000..b1f2811 --- /dev/null +++ b/g10/cardglue.h @@ -0,0 +1,211 @@ +/* cardglue.h - Divert operations to the agent + * Copyright (C) 2003, 2004 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ +#ifndef GNUPG_G10_CARDGLUE_H +#define GNUPG_G10_CARDGLUE_H + +#ifdef ENABLE_CARD_SUPPORT +/* + Note, that most card related code has been taken from 1.9.x branch + and is maintained over there if at all possible. Thus, if you make + changes here, please check that a similar change has been commited + to the 1.9.x branch. +*/ + + +struct agent_card_info_s { + int error; /* private. */ + char *serialno; /* malloced hex string. */ + char *disp_name; /* malloced. */ + char *disp_lang; /* malloced. */ + int disp_sex; /* 0 = unspecified, 1 = male, 2 = female */ + char *pubkey_url; /* malloced. */ + char *login_data; /* malloced. */ + char *private_do[4]; /* malloced. */ + char cafpr1valid; + char cafpr2valid; + char cafpr3valid; + char cafpr1[20]; + char cafpr2[20]; + char cafpr3[20]; + char fpr1valid; + char fpr2valid; + char fpr3valid; + char fpr1[20]; + char fpr2[20]; + char fpr3[20]; + u32 fpr1time; + u32 fpr2time; + u32 fpr3time; + unsigned long sig_counter; + int chv1_cached; /* True if a PIN is not required for each + signing. Note that the gpg-agent might cache + it anyway. */ + int chvmaxlen[3]; /* Maximum allowed length of a CHV. */ + int chvretry[3]; /* Allowed retries for the CHV; 0 = blocked. */ +}; + +struct agent_card_genkey_s { + char fprvalid; + char fpr[20]; + u32 created_at; + MPI n; + MPI e; +}; + + +struct app_ctx_s; +struct ctrl_ctx_s; + +typedef struct app_ctx_s *APP; /* deprecated. */ +typedef struct app_ctx_s *app_t; +typedef struct ctrl_ctx_s *CTRL; /* deprecated. */ +typedef struct ctrl_ctx_s *ctrl_t; + + +#define GPG_ERR_GENERAL G10ERR_GENERAL +#define GPG_ERR_BAD_PIN G10ERR_BAD_PASS +#define GPG_ERR_BAD_KEY G10ERR_BAD_KEY +#define GPG_ERR_CARD G10ERR_GENERAL +#define GPG_ERR_EEXIST G10ERR_FILE_EXISTS +#define GPG_ERR_ENOMEM G10ERR_RESOURCE_LIMIT +#define GPG_ERR_GENERAL G10ERR_GENERAL +#define GPG_ERR_HARDWARE G10ERR_GENERAL +#define GPG_ERR_INV_CARD G10ERR_GENERAL +#define GPG_ERR_INV_ID G10ERR_GENERAL +#define GPG_ERR_INV_NAME G10ERR_GENERAL +#define GPG_ERR_INV_VALUE G10ERR_INV_ARG +#define GPG_ERR_INV_SEXP G10ERR_INV_ARG +#define GPG_ERR_NOT_SUPPORTED G10ERR_UNSUPPORTED +#define GPG_ERR_NO_OBJ G10ERR_GENERAL +#define GPG_ERR_PIN_BLOCKED G10ERR_PASSPHRASE +#define GPG_ERR_UNSUPPORTED_ALGORITHM G10ERR_PUBKEY_ALGO +#define GPG_ERR_USE_CONDITIONS G10ERR_GENERAL +#define GPG_ERR_WRONG_CARD G10ERR_GENERAL +#define GPG_ERR_WRONG_SECKEY G10ERR_WRONG_SECKEY +#define GPG_ERR_PIN_NOT_SYNCED G10ERR_GENERAL +#define GPG_ERR_NOT_FOUND G10ERR_GENERAL +#define GPG_ERR_BUG G10ERR_GENERAL +#define GPG_ERR_NOT_IMPLEMENTED G10ERR_GENERAL +#define GPG_ERR_BAD_BER G10ERR_GENERAL +#define GPG_ERR_EOF (-1) +#define GPG_ERR_CARD_NOT_PRESENT G10ERR_NO_CARD +#define GPG_ERR_CARD_RESET G10ERR_GENERAL +#define GPG_ERR_WRONG_PUBKEY_ALGO G10ERR_PUBKEY_ALGO +#define GPG_ERR_UNKNOWN_SEXP G10ERR_INV_ARG +#define GPG_ERR_DUP_VALUE G10ERR_INV_ARG +#define GPG_ERR_BAD_SECKEY G10ERR_BAD_SECKEY +#define GPG_ERR_TOO_LARGE G10ERR_GENERAL + +#define GPG_ERR_EBUSY G10ERR_GENERAL +#define GPG_ERR_ENOENT G10ERR_OPEN_FILE +#define GPG_ERR_EACCES G10ERR_UNSUPPORTED +#define GPG_ERR_EIO G10ERR_GENERAL +#define GPG_ERR_ENODEV G10ERR_GENERAL +#define GPG_ERR_CANCELED G10ERR_CANCELED + +typedef int gpg_error_t; +typedef int gpg_err_code_t; + +#define gpg_error(n) (n) +#define gpg_err_code(n) (n) +#define gpg_strerror(n) g10_errstr ((n)) +#define gpg_error_from_errno(n) (G10ERR_GENERAL) /*FIXME*/ +#define gpg_err_code_from_errno(n) (G10ERR_GENERAL) + +/* We are not using it in a library, so we even let xtrymalloc + abort. Because we won't never return from these malloc functions, + we also don't need the out_of_core function, we simply define it to + return -1 */ +#define xtrymalloc(n) xmalloc((n)) +#define xtrycalloc(n,m) xcalloc((n),(m)) +#define xtryrealloc(n,m) xrealloc((n),(m)) +#define xtrymalloc_secure(n) xmalloc_secure((n)) +#define out_of_core() (-1) + +#define gnupg_get_time() make_timestamp () + + +void card_set_reader_port (const char *portstr); + +char *serialno_and_fpr_from_sk (const unsigned char *sn, size_t snlen, + PKT_secret_key *sk); +void send_status_info (ctrl_t ctrl, const char *keyword, ...); +void gcry_md_hash_buffer (int algo, void *digest, + const void *buffer, size_t length); +void log_printf (const char *fmt, ...); +void log_printhex (const char *text, const void *buffer, size_t length); + + +#define GCRY_MD_SHA1 DIGEST_ALGO_SHA1 +#define GCRY_MD_RMD160 DIGEST_ALGO_RMD160 + +void card_close (void); + + +/* Release the card info structure. */ +void agent_release_card_info (struct agent_card_info_s *info); + +/* Return card info. */ +int agent_learn (struct agent_card_info_s *info); + +/* Check whether the secret key for the key identified by HEXKEYGRIP + is available. Return 0 for yes or an error code. */ +int agent_havekey (const char *hexkeygrip); + +/* Return card info. */ +int agent_scd_getattr (const char *name, struct agent_card_info_s *info); + +/* Send a SETATTR command to the SCdaemon. */ +int agent_scd_setattr (const char *name, + const unsigned char *value, size_t valuelen, + const char *serialno); + +/* Send a WRITEKEY command to the SCdaemon. */ +int agent_scd_writekey (int keyno, const char *serialno, + const unsigned char *keydata, size_t keydatalen); + +/* Send a GENKEY command to the SCdaemon. */ +int agent_scd_genkey (struct agent_card_genkey_s *info, int keyno, int force, + const char *serialno); + +/* Send a PKSIGN command to the SCdaemon. */ +int agent_scd_pksign (const char *keyid, int hashalgo, + const unsigned char *indata, size_t indatalen, + unsigned char **r_buf, size_t *r_buflen); + +/* Send a PKDECRYPT command to the SCdaemon. */ +int agent_scd_pkdecrypt (const char *serialno, + const unsigned char *indata, size_t indatalen, + unsigned char **r_buf, size_t *r_buflen); + +/* Change the PIN of an OpenPGP card or reset the retry counter. */ +int agent_scd_change_pin (int chvno, const char *serialno); + +/* Send a CHECKPIN command. */ +int agent_scd_checkpin (const char *serialnobuf); + +/* Clear a cached PIN. */ +void agent_clear_pin_cache (const char *sn); + + +#endif /*ENABLE_CARD_SUPPORT*/ +#endif /*GNUPG_G10_CARDGLUE_H*/ + diff --git a/g10/ccid-driver.c b/g10/ccid-driver.c new file mode 100644 index 0000000..da8edb0 --- /dev/null +++ b/g10/ccid-driver.c @@ -0,0 +1,2745 @@ +/* ccid-driver.c - USB ChipCardInterfaceDevices driver + * Copyright (C) 2003, 2004, 2005, 2006 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 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * 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. + * + * $Date: 2006-02-14 17:28:34 +0100 (Tue, 14 Feb 2006) $ + */ + + +/* 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 <usb.h> + +#include "ccid-driver.h" + +#define DRVNAME "ccid-driver: " + + +/* Depending on how this source is used we either define our error + output to go to stderr or to the jnlib based logging functions. We + use the latter when GNUPG_MAJOR_VERSION is defines or when both, + GNUPG_SCD_MAIN_HEADER and HAVE_JNLIB_LOGGING are defined. +*/ +#if defined(GNUPG_MAJOR_VERSION) \ + || (defined(GNUPG_SCD_MAIN_HEADER) && defined(HAVE_JNLIB_LOGGING)) + +#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. */ + + + +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]) + + +/* We need to know the vendor to do some hacks. */ +enum { + VENDOR_CHERRY = 0x046a, + VENDOR_SCM = 0x04e6, + VENDOR_OMNIKEY= 0x076b, + VENDOR_GEMPC = 0x08e6 +}; + +/* A list and a table with special transport descriptions. */ +enum { + TRANSPORT_USB = 0, /* Standard USB transport. */ + TRANSPORT_CM4040 = 1 /* As used by the Cardman 4040. */ +}; + +static struct +{ + char *name; /* Device name. */ + int type; + +} transports[] = { + { "/dev/cmx0", TRANSPORT_CM4040 }, + { "/dev/cmx1", TRANSPORT_CM4040 }, + { NULL }, +}; + + +/* Store information on the driver's state. A pointer to such a + structure is used as handle for most functions. */ +struct ccid_driver_s +{ + usb_dev_handle *idev; + char *rid; + int dev_fd; /* -1 for USB transport or file descriptor of the + transport device. */ + unsigned short id_vendor; + unsigned short id_product; + unsigned short bcd_device; + int ifc_no; + int ep_bulk_out; + int ep_bulk_in; + int ep_intr; + int seqno; + unsigned char t1_ns; + unsigned char t1_nr; + int nonnull_nad; + int auto_ifsd; + int max_ifsd; + int ifsd; + int powered_off; + int has_pinpad; + int apdu_level; /* Reader supports short APDU level exchange. */ +}; + + +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 = T=1 protocol tracing + */ + + +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); +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); + +/* 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) | (buf[3] << 24); +} + +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; +} + + +/* Pint an error message for a failed CCID command including a textual + error code. MSG is shall be the CCID message of at least 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); +} + + +/* Given a handle used for special transport prepare it for use. In + particular setup all information in way that resembles what + parse_cccid_descriptor does. */ +static void +prepare_special_transport (ccid_driver_t handle) +{ + assert (!handle->id_vendor); + + handle->nonnull_nad = 0; + handle->auto_ifsd = 0; + handle->max_ifsd = 32; + handle->ifsd = 0; + handle->has_pinpad = 0; + handle->apdu_level = 0; + switch (handle->id_product) + { + case TRANSPORT_CM4040: + DEBUGOUT ("setting up transport for CardMan 4040\n"); + handle->apdu_level = 1; + break; + + default: assert (!"transport not defined"); + } +} + +/* 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, + const unsigned char *buf, size_t buflen) +{ + unsigned int i; + unsigned int us; + int have_t1 = 0, have_tpdu=0, have_auto_conf = 0; + + + handle->nonnull_nad = 0; + handle->auto_ifsd = 0; + handle->max_ifsd = 32; + handle->ifsd = 0; + handle->has_pinpad = 0; + handle->apdu_level = 0; + DEBUGOUT_3 ("idVendor: %04X idProduct: %04X bcdDevice: %04X\n", + handle->id_vendor, handle->id_product, handle->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\n"); + have_auto_conf = 1; + } + if ((us & 0x0004)) + DEBUGOUT (" Auto activation on insert\n"); + if ((us & 0x0008)) + DEBUGOUT (" Auto voltage selection\n"); + if ((us & 0x0010)) + DEBUGOUT (" Auto clock change\n"); + if ((us & 0x0020)) + DEBUGOUT (" Auto baud rate change\n"); + if ((us & 0x0040)) + DEBUGOUT (" Auto parameter negotation made by CCID\n"); + else if ((us & 0x0080)) + DEBUGOUT (" Auto PPS made by CCID\n"); + else if ((us & (0x0040 | 0x0080))) + DEBUGOUT (" WARNING: conflicting negotation 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 = 1; + } + else if ((us & 0x00070000)) + DEBUGOUT (" WARNING: conflicting exchange levels\n"); + + us = convert_le_u32(buf+44); + DEBUGOUT_1 (" dwMaxCCIDMsgLen %5u\n", 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) || !have_auto_conf) + { + DEBUGOUT ("this drivers requires that the reader supports T=1, " + "TPDU or APDU level exchange and auto configuration - " + "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 + */ + if (handle->id_vendor == VENDOR_SCM + && handle->max_ifsd > 48 + && ( (handle->id_product == 0xe001 && handle->bcd_device < 0x0516) + ||(handle->id_product == 0x5111 && handle->bcd_device < 0x0620) + ||(handle->id_product == 0x5115 && handle->bcd_device < 0x0514) + ||(handle->id_product == 0xe003 && handle->bcd_device < 0x0504) + )) + { + DEBUGOUT ("enabling workaround for buggy SCM readers\n"); + handle->max_ifsd = 48; + } + + + return 0; +} + + +static char * +get_escaped_usb_string (usb_dev_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. */ + rc = usb_control_msg (idev, USB_ENDPOINT_IN, USB_REQ_GET_DESCRIPTOR, + (USB_DT_STRING << 8), 0, + (char*)buf, sizeof buf, 1000 /* ms timeout */); + if (rc < 4) + langid = 0x0409; /* English. */ + else + langid = (buf[3] << 8) | buf[2]; + + rc = usb_control_msg (idev, USB_ENDPOINT_IN, USB_REQ_GET_DESCRIPTOR, + (USB_DT_STRING << 8) + idx, langid, + (char*)buf, sizeof buf, 1000 /* ms timeout */); + if (rc < 2 || buf[1] != USB_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 (usb_dev_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 (struct usb_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++) + { + struct usb_endpoint_descriptor *ep = ifcdesc->endpoint + no; + if (ep->bDescriptorType != USB_DT_ENDPOINT) + ; + else if (mode == 2 + && ((ep->bmAttributes & USB_ENDPOINT_TYPE_MASK) + == USB_ENDPOINT_TYPE_INTERRUPT) + && (ep->bEndpointAddress & 0x80)) + return (ep->bEndpointAddress & 0x0f); + else if (((ep->bmAttributes & USB_ENDPOINT_TYPE_MASK) + == USB_ENDPOINT_TYPE_BULK) + && (ep->bEndpointAddress & 0x80) == want_bulk_in) + return (ep->bEndpointAddress & 0x0f); + } + /* Should never happen. */ + return mode == 2? 0x83 : mode == 1? 0x82 :1; +} + + +/* Helper for scan_or_find_devices. This function returns true if a + requested device has been found or the caller should stop scanning + for other reasons. */ +static int +scan_or_find_usb_device (int scan_mode, + int *readerno, int *count, char **rid_list, + const char *readerid, + struct usb_device *dev, + char **r_rid, + struct usb_device **r_dev, + usb_dev_handle **r_idev, + unsigned char **ifcdesc_extra, + size_t *ifcdesc_extra_len, + int *interface_number, + int *ep_bulk_out, int *ep_bulk_in, int *ep_intr) +{ + int cfg_no; + int ifc_no; + int set_no; + struct usb_config_descriptor *config; + struct usb_interface *interface; + struct usb_interface_descriptor *ifcdesc; + char *rid; + usb_dev_handle *idev; + + *r_idev = NULL; + + for (cfg_no=0; cfg_no < dev->descriptor.bNumConfigurations; cfg_no++) + { + config = dev->config + cfg_no; + if(!config) + continue; + + for (ifc_no=0; ifc_no < config->bNumInterfaces; ifc_no++) + { + interface = config->interface + ifc_no; + if (!interface) + continue; + + for (set_no=0; set_no < interface->num_altsetting; set_no++) + { + ifcdesc = (interface->altsetting + set_no); + /* The second condition is for older SCM SPR 532 who did + not know about the assigned CCID class. 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 + && dev->descriptor.idVendor == VENDOR_SCM + && dev->descriptor.idProduct == 0xe003))) + { + idev = usb_open (dev); + if (!idev) + { + DEBUGOUT_1 ("usb_open failed: %s\n", + strerror (errno)); + continue; /* with next setting. */ + } + + rid = make_reader_id (idev, + dev->descriptor.idVendor, + dev->descriptor.idProduct, + dev->descriptor.iSerialNumber); + if (rid) + { + if (scan_mode) + { + char *p; + + /* 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. */ + free (rid); + + rid = NULL; + ++*count; + } + else if (!*readerno + || (*readerno < 0 + && readerid + && !strcmp (readerid, rid))) + { + /* We found the requested reader. */ + if (ifcdesc_extra && ifcdesc_extra_len) + { + *ifcdesc_extra = malloc (ifcdesc + ->extralen); + if (!*ifcdesc_extra) + { + usb_close (idev); + free (rid); + return 1; /* Out of core. */ + } + memcpy (*ifcdesc_extra, ifcdesc->extra, + ifcdesc->extralen); + *ifcdesc_extra_len = ifcdesc->extralen; + } + + if (interface_number) + *interface_number = (ifcdesc->bInterfaceNumber); + + if (ep_bulk_out) + *ep_bulk_out = find_endpoint (ifcdesc, 0); + if (ep_bulk_in) + *ep_bulk_in = find_endpoint (ifcdesc, 1); + if (ep_intr) + *ep_intr = find_endpoint (ifcdesc, 2); + + if (r_dev) + *r_dev = dev; + if (r_rid) + { + *r_rid = rid; + rid = NULL; + } + else + free (rid); + + *r_idev = idev; + return 1; /* Found requested device. */ + } + else + { + /* This is not yet the reader we want. + fixme: We should avoid the extra usb_open + in this case. */ + if (*readerno >= 0) + --*readerno; + } + free (rid); + } + + usb_close (idev); + idev = NULL; + return 0; + } + } + } + } + + return 0; +} + +/* Combination function to either scan all CCID devices or to find and + open one specific device. + + The function returns 0 if a reader has been found or when a scan + returned without error. + + With READERNO = -1 and READERID is NULL, scan mode is used and + 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. Note that in this mode the function always returns NULL. + + With READERNO >= 0 or READERID is not NULL find mode is used. This + uses the same algorithm as the scan mode but stops and returns at + the entry number READERNO and return the handle for the the opened + USB device. If R_RID is not NULL it will receive the reader ID of + that device. If R_DEV is not NULL it will the device pointer of + that device. If IFCDESC_EXTRA is NOT NULL it will receive a + malloced copy of the interfaces "extra: data filed; + IFCDESC_EXTRA_LEN receive the length of this field. If there is + no reader with number READERNO or that reader is not usable by our + implementation NULL will be returned. The caller must close a + returned USB device handle and free (if not passed as NULL) the + returned reader ID info as well as the IFCDESC_EXTRA. On error + NULL will get stored at R_RID, R_DEV, IFCDESC_EXTRA and + IFCDESC_EXTRA_LEN. With READERID being -1 the function stops if + the READERID was found. + + If R_FD is not -1 on return the device is not using USB for + transport but the device associated with that file descriptor. In + this case INTERFACE will receive the transport type and the other + USB specific return values are not used; the return value is + (void*)(1). + + Note that the first entry of the returned reader ID list in scan mode + corresponds with a READERNO of 0 in find mode. +*/ +static int +scan_or_find_devices (int readerno, const char *readerid, + char **r_rid, + struct usb_device **r_dev, + unsigned char **ifcdesc_extra, + size_t *ifcdesc_extra_len, + int *interface_number, + int *ep_bulk_out, int *ep_bulk_in, int *ep_intr, + usb_dev_handle **r_idev, + int *r_fd) +{ + char *rid_list = NULL; + int count = 0; + struct usb_bus *busses, *bus; + struct usb_device *dev = NULL; + usb_dev_handle *idev = NULL; + int scan_mode = (readerno == -1 && !readerid); + int i; + + /* Set return values to a default. */ + if (r_rid) + *r_rid = NULL; + if (r_dev) + *r_dev = NULL; + if (ifcdesc_extra) + *ifcdesc_extra = NULL; + if (ifcdesc_extra_len) + *ifcdesc_extra_len = 0; + if (interface_number) + *interface_number = 0; + if (r_idev) + *r_idev = NULL; + if (r_fd) + *r_fd = -1; + + /* See whether we want scan or find mode. */ + if (scan_mode) + { + assert (r_rid); + } + + usb_find_busses(); + usb_find_devices(); + +#ifdef HAVE_USB_GET_BUSSES + busses = usb_get_busses(); +#else + busses = usb_busses; +#endif + + for (bus = busses; bus; bus = bus->next) + { + for (dev = bus->devices; dev; dev = dev->next) + { + if (scan_or_find_usb_device (scan_mode, &readerno, &count, &rid_list, + readerid, + dev, + r_rid, + r_dev, + &idev, + ifcdesc_extra, + ifcdesc_extra_len, + interface_number, + ep_bulk_out, ep_bulk_in, ep_intr)) + { + /* Found requested device or out of core. */ + if (!idev) + { + free (rid_list); + return -1; /* error */ + } + *r_idev = idev; + return 0; + } + } + } + + /* Now check whether there are any devices with special transport types. */ + for (i=0; transports[i].name; i++) + { + int fd; + char *rid, *p; + + fd = open (transports[i].name, O_RDWR); + if (fd == -1) + continue; + + rid = malloc (strlen (transports[i].name) + 30 + 10); + if (!rid) + { + close (fd); + free (rid_list); + return -1; /* Error. */ + } + sprintf (rid, "0000:%04X:%s:0", transports[i].type, transports[i].name); + if (scan_mode) + { + 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) + { + close (fd); + free (rid_list); + free (rid); + return -1; /* Error. */ + } + *p = 0; + if (rid_list) + { + strcat (p, rid_list); + free (rid_list); + } + strcat (p, rid); + strcat (p, "\n"); + rid_list = p; + ++count; + } + else if (!readerno || + (readerno < 0 && readerid && !strcmp (readerid, rid))) + { + /* Found requested device. */ + if (interface_number) + *interface_number = transports[i].type; + if (r_rid) + *r_rid = rid; + else + free (rid); + if (r_fd) + *r_fd = fd; + return 0; /* Okay, found device */ + } + else /* This is not yet the reader we want. */ + { + if (readerno >= 0) + --readerno; + } + free (rid); + close (fd); + } + + if (scan_mode) + { + *r_rid = rid_list; + return 0; + } + else + return -1; +} + + +/* 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, other values are not yet defined. */ +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) + { + usb_init (); + initialized_usb = 1; + } + + if (scan_or_find_devices (-1, NULL, &reader_list, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL)) + return NULL; /* Error. */ + return reader_list; +} + + +/* 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 (ccid_driver_t *handle, const char *readerid) +{ + int rc = 0; + struct usb_device *dev = NULL; + usb_dev_handle *idev = NULL; + int dev_fd = -1; + char *rid = NULL; + unsigned char *ifcdesc_extra = NULL; + size_t ifcdesc_extra_len; + int readerno; + int ifc_no, ep_bulk_out, ep_bulk_in, ep_intr; + + *handle = NULL; + + if (!initialized_usb) + { + usb_init (); + initialized_usb = 1; + } + + /* 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 (readerid && strchr (readerid, ':')) + readerno = -1; /* We want to use the readerid. */ + else if (readerid) + { + readerno = atoi (readerid); + if (readerno < 0) + { + DEBUGOUT ("no CCID readers found\n"); + rc = CCID_DRIVER_ERR_NO_READER; + goto leave; + } + } + else + readerno = 0; /* Default. */ + + if (scan_or_find_devices (readerno, readerid, &rid, &dev, + &ifcdesc_extra, &ifcdesc_extra_len, + &ifc_no, &ep_bulk_out, &ep_bulk_in, &ep_intr, + &idev, &dev_fd) ) + { + if (readerno == -1) + DEBUGOUT_1 ("no CCID reader with ID %s\n", readerid ); + else + DEBUGOUT_1 ("no CCID reader with number %d\n", readerno ); + rc = CCID_DRIVER_ERR_NO_READER; + goto leave; + } + + /* Okay, this is a CCID reader. */ + *handle = calloc (1, sizeof **handle); + if (!*handle) + { + DEBUGOUT ("out of memory\n"); + rc = CCID_DRIVER_ERR_OUT_OF_CORE; + goto leave; + } + (*handle)->rid = rid; + if (idev) /* Regular USB transport. */ + { + (*handle)->idev = idev; + (*handle)->dev_fd = -1; + (*handle)->id_vendor = dev->descriptor.idVendor; + (*handle)->id_product = dev->descriptor.idProduct; + (*handle)->bcd_device = dev->descriptor.bcdDevice; + (*handle)->ifc_no = ifc_no; + (*handle)->ep_bulk_out = ep_bulk_out; + (*handle)->ep_bulk_in = ep_bulk_in; + (*handle)->ep_intr = ep_intr; + } + else if (dev_fd != -1) /* Device transport. */ + { + (*handle)->idev = NULL; + (*handle)->dev_fd = dev_fd; + (*handle)->id_vendor = 0; /* Magic vendor for special transport. */ + (*handle)->id_product = ifc_no; /* Transport type */ + prepare_special_transport (*handle); + } + else + { + assert (!"no transport"); /* Bug. */ + } + + DEBUGOUT_2 ("using CCID reader %d (ID=%s)\n", readerno, rid ); + + if (idev) + { + if (parse_ccid_descriptor (*handle, ifcdesc_extra, ifcdesc_extra_len)) + { + DEBUGOUT ("device not supported\n"); + rc = CCID_DRIVER_ERR_NO_READER; + goto leave; + } + + rc = usb_claim_interface (idev, ifc_no); + if (rc) + { + DEBUGOUT_1 ("usb_claim_interface failed: %d\n", rc); + rc = CCID_DRIVER_ERR_CARD_IO_ERROR; + goto leave; + } + } + + leave: + free (ifcdesc_extra); + if (rc) + { + free (rid); + if (idev) + usb_close (idev); + if (dev_fd != -1) + close (dev_fd); + free (*handle); + *handle = NULL; + } + + return rc; +} + + +static void +do_close_reader (ccid_driver_t handle) +{ + int rc; + unsigned char msg[100]; + size_t msglen; + unsigned char seqno; + + if (!handle->powered_off) + { + 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); + if (!rc) + bulk_in (handle, msg, sizeof msg, &msglen, RDR_to_PC_SlotStatus, + seqno, 2000, 0); + handle->powered_off = 1; + } + if (handle->idev) + { + usb_release_interface (handle->idev, handle->ifc_no); + usb_close (handle->idev); + handle->idev = NULL; + } + if (handle->dev_fd != -1) + { + close (handle->dev_fd); + handle->dev_fd = -1; + } +} + + +/* Reset a reader on HANDLE. This is useful in case a reader has been + plugged of and inserted at a different port. By resetting the + handle, the same reader will be get used. Note, that on error the + handle won't get released. + + This does not return an ATR, so ccid_get_atr should be called right + after this one. +*/ +int +ccid_shutdown_reader (ccid_driver_t handle) +{ + int rc = 0; + struct usb_device *dev = NULL; + usb_dev_handle *idev = NULL; + unsigned char *ifcdesc_extra = NULL; + size_t ifcdesc_extra_len; + int ifc_no, ep_bulk_out, ep_bulk_in, ep_intr; + + if (!handle || !handle->rid) + return CCID_DRIVER_ERR_INV_VALUE; + + do_close_reader (handle); + + if (scan_or_find_devices (-1, handle->rid, NULL, &dev, + &ifcdesc_extra, &ifcdesc_extra_len, + &ifc_no, &ep_bulk_out, &ep_bulk_in, &ep_intr, + &idev, NULL) || !idev) + { + DEBUGOUT_1 ("no CCID reader with ID %s\n", handle->rid); + return CCID_DRIVER_ERR_NO_READER; + } + + if (idev) + { + handle->idev = idev; + handle->ifc_no = ifc_no; + handle->ep_bulk_out = ep_bulk_out; + handle->ep_bulk_in = ep_bulk_in; + handle->ep_intr = ep_intr; + + if (parse_ccid_descriptor (handle, ifcdesc_extra, ifcdesc_extra_len)) + { + DEBUGOUT ("device not supported\n"); + rc = CCID_DRIVER_ERR_NO_READER; + goto leave; + } + + rc = usb_claim_interface (idev, ifc_no); + if (rc) + { + DEBUGOUT_1 ("usb_claim_interface failed: %d\n", rc); + rc = CCID_DRIVER_ERR_CARD_IO_ERROR; + goto leave; + } + } + + leave: + free (ifcdesc_extra); + if (rc) + { + if (handle->idev) + usb_close (handle->idev); + handle->idev = NULL; + if (handle->dev_fd != -1) + close (handle->dev_fd); + handle->dev_fd = -1; + } + + return rc; + +} + + +/* Close the reader HANDLE. */ +int +ccid_close_reader (ccid_driver_t handle) +{ + if (!handle || (!handle->idev && handle->dev_fd == -1)) + return 0; + + do_close_reader (handle); + free (handle->rid); + free (handle); + return 0; +} + + +/* Return False if a card is present and powered. */ +int +ccid_check_card_presence (ccid_driver_t handle) +{ + + return -1; +} + + +/* Write NBYTES of BUF to file descriptor FD. */ +static int +writen (int fd, const void *buf, size_t nbytes) +{ + size_t nleft = nbytes; + int nwritten; + + while (nleft > 0) + { + nwritten = write (fd, buf, nleft); + if (nwritten < 0) + { + if (errno == EINTR) + nwritten = 0; + else + return -1; + } + nleft -= nwritten; + buf = (const char*)buf + nwritten; + } + + return 0; +} + + +/* 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 rc; + + if (handle->idev) + { + rc = usb_bulk_write (handle->idev, + handle->ep_bulk_out, + (char*)msg, msglen, + 1000 /* ms timeout */); + if (rc == msglen) + return 0; + if (rc == -1) + DEBUGOUT_1 ("usb_bulk_write error: %s\n", strerror (errno)); + else + DEBUGOUT_1 ("usb_bulk_write failed: %d\n", rc); + } + else + { + rc = writen (handle->dev_fd, msg, msglen); + if (!rc) + return 0; + DEBUGOUT_2 ("writen to %d failed: %s\n", + handle->dev_fd, strerror (errno)); + + } + return CCID_DRIVER_ERR_CARD_IO_ERROR; +} + + +/* 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. 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 i, rc; + size_t msglen; + + /* Fixme: The next line for the current Valgrind without support + for USB IOCTLs. */ + memset (buffer, 0, length); + retry: + if (handle->idev) + { + rc = usb_bulk_read (handle->idev, + handle->ep_bulk_in, + (char*)buffer, length, + timeout); + if (rc < 0) + { + DEBUGOUT_1 ("usb_bulk_read error: %s\n", strerror (errno)); + return CCID_DRIVER_ERR_CARD_IO_ERROR; + } + *nread = msglen = rc; + } + else + { + rc = read (handle->dev_fd, buffer, length); + if (rc < 0) + { + DEBUGOUT_2 ("read from %d failed: %s\n", + handle->dev_fd, strerror (errno)); + return CCID_DRIVER_ERR_CARD_IO_ERROR; + } + *nread = msglen = rc; + } + + + if (msglen < 10) + { + DEBUGOUT_1 ("bulk-in msg too short (%u)\n", (unsigned int)msglen); + return CCID_DRIVER_ERR_INV_VALUE; + } + if (buffer[0] != expected_type) + { + DEBUGOUT_1 ("unexpected bulk-in msg type (%02x)\n", buffer[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]); + return CCID_DRIVER_ERR_INV_VALUE; + } + + 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]); + goto retry; + } + + if (!no_debug) + { + DEBUGOUT_3 ("status: %02X error: %02X octet[9]: %02X\n" + " data:", buffer[7], buffer[8], buffer[9] ); + for (i=10; i < msglen; i++) + DEBUGOUT_CONT_1 (" %02X", buffer[i]); + DEBUGOUT_LF (); + } + 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: return CCID_DRIVER_ERR_CARD_INACTIVE; + case 2: return CCID_DRIVER_ERR_NO_CARD; + case 3: /* RFU */ break; + } + 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 i, 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); + + DEBUGOUT ("sending"); + for (i=0; i < msglen; i++) + DEBUGOUT_CONT_1 (" %02X", msg[i]); + DEBUGOUT_LF (); + rc = bulk_out (handle, msg, msglen); + 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); + *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]; + size_t msglen; + int i, j; + + if (handle->idev) + { + rc = usb_bulk_read (handle->idev, + handle->ep_intr, + (char*)msg, sizeof msg, + 0 /* ms timeout */ ); + if (rc < 0 && errno == ETIMEDOUT) + return 0; + } + else + return 0; + + if (rc < 0) + { + DEBUGOUT_1 ("usb_intr_read error: %s\n", strerror (errno)); + return CCID_DRIVER_ERR_CARD_IO_ERROR; + } + + msglen = rc; + rc = 0; + + 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 occured\n"); + } + else + { + DEBUGOUT_1 ("unknown intr-in msg of type %02X\n", msg[0]); + } + + return 0; +} + + +/* Note that this fucntion won't return the error codes NO_CARD or + CARD_INACTIVE */ +int +ccid_slot_status (ccid_driver_t handle, int *statusbits) +{ + int rc; + unsigned char msg[100]; + size_t msglen; + unsigned char seqno; + int retries = 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); + 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 && retries < 3) + { + if (!retries) + { + DEBUGOUT ("USB: CALLING USB_CLEAR_HALT\n"); + usb_clear_halt (handle->idev, handle->ep_bulk_in); + usb_clear_halt (handle->idev, handle->ep_bulk_out); + } + 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; +} + + +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 i; + int tried_iso = 0; + int got_param; + + /* First check whether a card is available. */ + rc = ccid_slot_status (handle, &statusbits); + if (rc) + return rc; + if (statusbits == 2) + return CCID_DRIVER_ERR_NO_CARD; + + + /* 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++; + msg[7] = 0; /* power select (0=auto, 1=5V, 2=3V, 3=1.8V) */ + msg[8] = 0; /* RFU */ + msg[9] = 0; /* RFU */ + set_msg_len (msg, 0); + msglen = 10; + + rc = bulk_out (handle, msg, msglen); + 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 (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; + } + + got_param = 0; + 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); + if (!rc) + rc = bulk_in (handle, msg, sizeof msg, &msglen, RDR_to_PC_Parameters, + seqno, 2000, 0); + if (rc) + DEBUGOUT ("GetParameters failed\n"); + else + { + DEBUGOUT ("GetParametes returned"); + for (i=0; i < msglen; i++) + DEBUGOUT_CONT_1 (" %02X", msg[i]); + DEBUGOUT_LF (); + if (msglen >= 10) + { + DEBUGOUT_1 (" protocol ..........: T=%d\n", msg[9]); + if (msglen == 17 && msg[9] == 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]); + got_param = 1; + } + } + } + + /* 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) + { + /* FIXME: Get those values from the ATR. */ + msg[10]= 0x01; /* Fi/Di */ + msg[11]= 0x10; /* LRC, direct convention. */ + msg[12]= 0; /* Extra guardtime. */ + msg[13]= 0x41; /* BWI/CWI */ + msg[14]= 0; /* No clock stoppping. */ + msg[15]= 254; /* IFSC */ + msg[16]= 0; /* Does not support non default NAD values. */ + } + set_msg_len (msg, 7); + msglen = 10 + 7; + + DEBUGOUT ("sending"); + for (i=0; i < msglen; i++) + DEBUGOUT_CONT_1 (" %02X", msg[i]); + DEBUGOUT_LF (); + + rc = bulk_out (handle, msg, msglen); + 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"); + + 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; + + DEBUGOUT ("sending"); + for (i=0; i < msglen; i++) + DEBUGOUT_CONT_1 (" %02X", msg[i]); + DEBUGOUT_LF (); + + 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); + 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]); + } + + 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; + } +} + + +/* 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_buflen, + unsigned char *resp, size_t maxresplen, + size_t *nresp) +{ + int rc; + unsigned char send_buffer[10+259], recv_buffer[10+259]; + const unsigned char *apdu; + size_t apdulen; + unsigned char *msg; + size_t msglen; + unsigned char seqno; + int i; + + msg = send_buffer; + + apdu = apdu_buf; + apdulen = apdu_buflen; + assert (apdulen); + + if (apdulen > 254) + return CCID_DRIVER_ERR_INV_VALUE; /* Invalid length. */ + + msg[0] = PC_to_RDR_XfrBlock; + msg[5] = 0; /* slot */ + msg[6] = seqno = handle->seqno++; + msg[7] = 4; /* bBWI */ + msg[8] = 0; /* RFU */ + msg[9] = 0; /* RFU */ + memcpy (msg+10, apdu, apdulen); + set_msg_len (msg, apdulen); + msglen = 10 + apdulen; + + DEBUGOUT ("sending"); + for (i=0; i < msglen; i++) + DEBUGOUT_CONT_1 (" %02X", msg[i]); + DEBUGOUT_LF (); + + rc = bulk_out (handle, msg, msglen); + if (rc) + return rc; + + msg = recv_buffer; + rc = bulk_in (handle, msg, sizeof recv_buffer, &msglen, + RDR_to_PC_DataBlock, seqno, 5000, 0); + if (rc) + return rc; + + apdu = msg + 10; + apdulen = msglen - 10; + + if (resp) + { + if (apdulen > maxresplen) + { + DEBUGOUT_2 ("provided buffer too short for received data " + "(%u/%u)\n", + (unsigned int)apdulen, (unsigned int)maxresplen); + return CCID_DRIVER_ERR_INV_VALUE; + } + + memcpy (resp, apdu, apdulen); + *nresp = apdulen; + } + + 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 = resyncronisation 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; + unsigned char send_buffer[10+259], recv_buffer[10+259]; + const unsigned char *apdu; + size_t apdulen; + unsigned char *msg, *tpdu, *p; + size_t msglen, tpdulen, last_tpdulen, n; + unsigned char seqno; + int i; + unsigned int edc; + int use_crc = 0; + size_t dummy_nresp; + int next_chunk = 1; + int sending = 1; + int retries = 0; + + if (!nresp) + nresp = &dummy_nresp; + *nresp = 0; + + /* Smarter readers allow to send APDUs directly; divert here. */ + if (handle->apdu_level) + 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; + for (;;) + { + if (next_chunk) + { + next_chunk = 0; + + apdu = apdu_buf; + apdulen = apdu_buflen; + assert (apdulen); + + /* Construct an I-Block. */ + if (apdulen > 254) + return CCID_DRIVER_ERR_INV_VALUE; /* Invalid length. */ + + tpdu = msg+10; + /* NAD: DAD=1, SAD=0 */ + tpdu[0] = handle->nonnull_nad? ((1 << 4) | 0): 0; + tpdu[1] = ((handle->t1_ns & 1) << 6); /* I-block */ + if (apdulen > 128 /* fixme: replace by ifsc */) + { + apdulen = 128; + apdu_buf += 128; + apdu_buflen -= 128; + 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; + } + + msg[0] = PC_to_RDR_XfrBlock; + msg[5] = 0; /* slot */ + msg[6] = seqno = handle->seqno++; + msg[7] = 4; /* bBWI */ + msg[8] = 0; /* RFU */ + msg[9] = 0; /* RFU */ + set_msg_len (msg, tpdulen); + msglen = 10 + tpdulen; + last_tpdulen = tpdulen; + + DEBUGOUT ("sending"); + for (i=0; i < msglen; i++) + DEBUGOUT_CONT_1 (" %02X", msg[i]); + DEBUGOUT_LF (); + + 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); + if (rc) + return rc; + + msg = recv_buffer; + rc = bulk_in (handle, msg, sizeof recv_buffer, &msglen, + RDR_to_PC_DataBlock, seqno, 5000, 0); + if (rc) + return rc; + + tpdu = msg + 10; + tpdulen = msglen - 10; + + if (tpdulen < 4) + { + usb_clear_halt (handle->idev, handle->ep_bulk_in); + 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. */ + retries = 0; + if (sending) + { /* last block sent was successful. */ + handle->t1_ns ^= 1; + sending = 0; + } + + if (!!(tpdu[1] & 0x40) != handle->t1_nr) + { /* Reponse does not match our sequence number. */ + msg = send_buffer; + tpdu = msg+10; + /* NAD: DAD=1, SAD=0 */ + tpdu[0] = handle->nonnull_nad? ((1 << 4) | 0): 0; + 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+10; + /* NAD: DAD=1, SAD=0 */ + tpdu[0] = handle->nonnull_nad? ((1 << 4) | 0): 0; + 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)) + { /* Error: repeat last block */ + if (++retries > 3) + { + DEBUGOUT ("3 failed retries\n"); + return CCID_DRIVER_ERR_CARD_IO_ERROR; + } + 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) == 3 && tpdu[2]) + { /* Wait time extension request. */ + unsigned char bwi = tpdu[3]; + msg = send_buffer; + tpdu = msg+10; + /* NAD: DAD=1, SAD=0 */ + tpdu[0] = handle->nonnull_nad? ((1 << 4) | 0): 0; + 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); + } + 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. PIN_PADLEN should be 0. + + 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, + int pin_mode, int pinlen_min, int pinlen_max, + int pin_padlen, + 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; + int i; + size_t dummy_nresp; + int testmode; + + 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)) + return CCID_DRIVER_ERR_NOT_SUPPORTED; /* Not yet by our code. */ + else + return CCID_DRIVER_ERR_NO_KEYPAD; + + if (pin_mode != 1) + return CCID_DRIVER_ERR_NOT_SUPPORTED; + + if (pin_padlen != 0) + return CCID_DRIVER_ERR_NOT_SUPPORTED; + + if (!pinlen_min) + pinlen_min = 1; + if (!pinlen_max) + pinlen_max = 25; + + /* Note that the 25 is the maximum value the SPR532 allows. */ + if (pinlen_min < 1 || pinlen_min > 25 + || pinlen_max < 1 || pinlen_max > 25 + || pinlen_min > pinlen_max) + return CCID_DRIVER_ERR_INV_VALUE; + + /* We have only tested this with an SCM reader so better don't risk + anything and do not allow the use with other readers. */ + if (handle->id_vendor != VENDOR_SCM) + return CCID_DRIVER_ERR_NOT_SUPPORTED; + + if (testmode) + return 0; /* Success */ + + msg = send_buffer; + if (handle->id_vendor == VENDOR_SCM) + { + DEBUGOUT ("sending escape sequence to switch to a case 1 APDU\n"); + rc = send_escape_cmd (handle, (const unsigned char*)"\x80\x02\x00", 3, + NULL, 0, NULL); + if (rc) + return rc; + } + + msg[0] = PC_to_RDR_Secure; + msg[5] = 0; /* slot */ + msg[6] = seqno = handle->seqno++; + msg[7] = 4; /* bBWI */ + msg[8] = 0; /* RFU */ + msg[9] = 0; /* RFU */ + msg[10] = 0; /* Perform PIN verification. */ + 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 product. Kudos to Martin Paljak for this + hint. */ + msg[13] = msg[14] = 0; + } + else + { + msg[13] = 0x00; /* bmPINBlockString: + 0 bits of pin length to insert. + 0 bytes of PIN block size. */ + msg[14] = 0x00; /* bmPINLengthFormat: + Units are bytes, position is 0. */ + } + msg[15] = pinlen_min; /* wPINMaxExtraDigit-Minimum. */ + msg[16] = pinlen_max; /* wPINMaxExtraDigit-Maximum. */ + msg[17] = 0x02; /* bEntryValidationCondition: + Validation key pressed */ + if (pinlen_min && pinlen_max && pinlen_min == pinlen_max) + msg[17] |= 0x01; /* Max size reached. */ + msg[18] = 0xff; /* bNumberMessage: Default. */ + msg[19] = 0x04; /* wLangId-High. */ + msg[20] = 0x09; /* wLangId-Low: English FIXME: use the first entry. */ + msg[21] = 0; /* bMsgIndex. */ + /* bTeoProlog follows: */ + msg[22] = handle->nonnull_nad? ((1 << 4) | 0): 0; + msg[23] = ((handle->t1_ns & 1) << 6); /* I-block */ + msg[24] = 4; /* apdulen. */ + /* APDU follows: */ + msg[25] = apdu_buf[0]; /* CLA */ + msg[26] = apdu_buf[1]; /* INS */ + msg[27] = apdu_buf[2]; /* P1 */ + msg[28] = apdu_buf[3]; /* P2 */ + msglen = 29; + set_msg_len (msg, msglen - 10); + + DEBUGOUT ("sending"); + for (i=0; i < msglen; i++) + DEBUGOUT_CONT_1 (" %02X", msg[i]); + DEBUGOUT_LF (); + + rc = bulk_out (handle, msg, msglen); + if (rc) + return rc; + + msg = recv_buffer; + rc = bulk_in (handle, msg, sizeof recv_buffer, &msglen, + RDR_to_PC_DataBlock, seqno, 5000, 0); + if (rc) + return rc; + + tpdu = msg + 10; + tpdulen = msglen - 10; + + if (tpdulen < 4) + { + usb_clear_halt (handle->idev, handle->ep_bulk_in); + 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) + { /* Reponse 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); + resp += n; + *nresp += n; + maxresplen -= 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) + { /* Reponse 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"; + 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) +{ + int rc; + ccid_driver_t ccid; + unsigned 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; + + 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 (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; + } + + rc = ccid_open_reader (&ccid, argc? *argv:NULL); + if (rc) + return 1; + + if (!no_poll) + ccid_poll (ccid); + fputs ("getting ATR ...\n", stderr); + rc = ccid_get_atr (ccid, NULL, 0, NULL); + if (rc) + { + print_error (rc); + return 1; + } + + if (!no_poll) + ccid_poll (ccid); + fputs ("getting slot status ...\n", stderr); + rc = ccid_slot_status (ccid, &slotstat); + if (rc) + { + print_error (rc); + 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}; + rc = ccid_transceive (ccid, + apdu, sizeof apdu, + result, sizeof result, &resultlen); + print_result (rc, result, resultlen); + } + + + if (!no_poll) + ccid_poll (ccid); + + fputs ("getting OpenPGP DO 0x65 ....\n", stderr); + { + static unsigned char apdu[] = { 0, 0xCA, 0, 0x65, 254 }; + rc = ccid_transceive (ccid, apdu, sizeof apdu, + result, sizeof result, &resultlen); + print_result (rc, result, resultlen); + } + + if (!no_pinpad) + { + } + + if (!no_pinpad) + { + static unsigned char apdu[] = { 0, 0x20, 0, 0x81 }; + + + if (ccid_transceive_secure (ccid, + apdu, sizeof apdu, + 1, 0, 0, 0, + NULL, 0, NULL)) + fputs ("can't verify using a PIN-Pad reader\n", stderr); + else + { + fputs ("verifying CHV1 using the PINPad ....\n", stderr); + + rc = ccid_transceive_secure (ccid, + apdu, sizeof apdu, + 1, 0, 0, 0, + result, sizeof result, &resultlen); + print_result (rc, 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'}; + rc = ccid_transceive (ccid, apdu, sizeof apdu, + result, sizeof result, &resultlen); + print_result (rc, result, resultlen); + } + } + + if (!rc) + { + fputs ("getting OpenPGP DO 0x5E ....\n", stderr); + { + static unsigned char apdu[] = { 0, 0xCA, 0, 0x5E, 254 }; + rc = ccid_transceive (ccid, apdu, sizeof apdu, + result, sizeof result, &resultlen); + print_result (rc, result, resultlen); + } + } + + ccid_close_reader (ccid); + + return 0; +} + +/* + * Local Variables: + * compile-command: "gcc -DTEST -Wall -I/usr/local/include -lusb -g ccid-driver.c" + * End: + */ +#endif /*TEST*/ +#endif /*HAVE_LIBUSB*/ diff --git a/g10/ccid-driver.h b/g10/ccid-driver.h new file mode 100644 index 0000000..54adffc --- /dev/null +++ b/g10/ccid-driver.h @@ -0,0 +1,108 @@ +/* ccid-driver.c - 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 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * 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: ccid-driver.h 3995 2006-02-06 16:34:20Z wk $ + */ + +#ifndef CCID_DRIVER_H +#define CCID_DRIVER_H + +/* 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_KEYPAD 0x1000e + +struct ccid_driver_s; +typedef struct ccid_driver_s *ccid_driver_t; + +int ccid_set_debug_level (int level); +char *ccid_get_reader_list (void); +int ccid_open_reader (ccid_driver_t *handle, const char *readerid); +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 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, + int pin_mode, + int pinlen_min, int pinlen_max, int pin_padlen, + 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); + + + +#endif /*CCID_DRIVER_H*/ + + + diff --git a/g10/cipher.c b/g10/cipher.c new file mode 100644 index 0000000..f7332ba --- /dev/null +++ b/g10/cipher.c @@ -0,0 +1,153 @@ +/* cipher.c - En-/De-ciphering filter + * Copyright (C) 1998, 1999, 2000, 2001 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <assert.h> + +#include "errors.h" +#include "iobuf.h" +#include "memory.h" +#include "util.h" +#include "filter.h" +#include "packet.h" +#include "options.h" +#include "main.h" +#include "status.h" + + +#define MIN_PARTIAL_SIZE 512 + + +static void +write_header( cipher_filter_context_t *cfx, IOBUF a ) +{ + PACKET pkt; + PKT_encrypted ed; + byte temp[18]; + unsigned blocksize; + unsigned nprefix; + + blocksize = cipher_get_blocksize( cfx->dek->algo ); + if( blocksize < 8 || blocksize > 16 ) + log_fatal("unsupported blocksize %u\n", blocksize ); + + memset( &ed, 0, sizeof ed ); + ed.len = cfx->datalen; + ed.extralen = blocksize+2; + ed.new_ctb = !ed.len && !RFC1991; + if( cfx->dek->use_mdc ) { + ed.mdc_method = DIGEST_ALGO_SHA1; + cfx->mdc_hash = md_open( DIGEST_ALGO_SHA1, 0 ); + if ( DBG_HASHING ) + md_start_debug( cfx->mdc_hash, "creatmdc" ); + } + + { + char buf[20]; + + sprintf (buf, "%d %d", ed.mdc_method, cfx->dek->algo); + write_status_text (STATUS_BEGIN_ENCRYPTION, buf); + } + + init_packet( &pkt ); + pkt.pkttype = cfx->dek->use_mdc? PKT_ENCRYPTED_MDC : PKT_ENCRYPTED; + pkt.pkt.encrypted = &ed; + if( build_packet( a, &pkt )) + log_bug("build_packet(ENCR_DATA) failed\n"); + nprefix = blocksize; + randomize_buffer( temp, nprefix, 1 ); + temp[nprefix] = temp[nprefix-2]; + temp[nprefix+1] = temp[nprefix-1]; + print_cipher_algo_note( cfx->dek->algo ); + cfx->cipher_hd = cipher_open( cfx->dek->algo, + cfx->dek->use_mdc? CIPHER_MODE_CFB + : CIPHER_MODE_AUTO_CFB, 1 ); +/* log_hexdump( "thekey", cfx->dek->key, cfx->dek->keylen );*/ + cipher_setkey( cfx->cipher_hd, cfx->dek->key, cfx->dek->keylen ); + cipher_setiv( cfx->cipher_hd, NULL, 0 ); +/* log_hexdump( "prefix", temp, nprefix+2 ); */ + if( cfx->mdc_hash ) /* hash the "IV" */ + md_write( cfx->mdc_hash, temp, nprefix+2 ); + cipher_encrypt( cfx->cipher_hd, temp, temp, nprefix+2); + cipher_sync( cfx->cipher_hd ); + iobuf_write(a, temp, nprefix+2); + cfx->header=1; +} + + + +/**************** + * This filter is used to en/de-cipher data with a conventional algorithm + */ +int +cipher_filter( void *opaque, int control, + IOBUF a, byte *buf, size_t *ret_len) +{ + size_t size = *ret_len; + cipher_filter_context_t *cfx = opaque; + int rc=0; + + if( control == IOBUFCTRL_UNDERFLOW ) { /* decrypt */ + rc = -1; /* not yet used */ + } + else if( control == IOBUFCTRL_FLUSH ) { /* encrypt */ + assert(a); + if( !cfx->header ) { + write_header( cfx, a ); + } + if( cfx->mdc_hash ) + md_write( cfx->mdc_hash, buf, size ); + cipher_encrypt( cfx->cipher_hd, buf, buf, size); + if( iobuf_write( a, buf, size ) ) + rc = G10ERR_WRITE_FILE; + } + else if( control == IOBUFCTRL_FREE ) { + if( cfx->mdc_hash ) { + byte *hash; + int hashlen = md_digest_length( md_get_algo( cfx->mdc_hash ) ); + byte temp[22]; + + assert( hashlen == 20 ); + /* we must hash the prefix of the MDC packet here */ + temp[0] = 0xd3; + temp[1] = 0x14; + md_putc( cfx->mdc_hash, temp[0] ); + md_putc( cfx->mdc_hash, temp[1] ); + + md_final( cfx->mdc_hash ); + hash = md_read( cfx->mdc_hash, 0 ); + memcpy(temp+2, hash, 20); + cipher_encrypt( cfx->cipher_hd, temp, temp, 22 ); + md_close( cfx->mdc_hash ); cfx->mdc_hash = NULL; + if( iobuf_write( a, temp, 22 ) ) + log_error("writing MDC packet failed\n" ); + } + cipher_close(cfx->cipher_hd); + } + else if( control == IOBUFCTRL_DESC ) { + *(char**)buf = "cipher_filter"; + } + return rc; +} diff --git a/g10/compress-bz2.c b/g10/compress-bz2.c new file mode 100644 index 0000000..7a8075a --- /dev/null +++ b/g10/compress-bz2.c @@ -0,0 +1,242 @@ +/* compress.c - bzip2 compress filter + * Copyright (C) 2003, 2004 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#include <config.h> +#include <string.h> +#include <stdio.h> /* Early versions of bzlib (1.0) require stdio.h */ +#include <bzlib.h> + +#include "util.h" +#include "memory.h" +#include "packet.h" +#include "filter.h" +#include "main.h" +#include "options.h" + +/* Note that the code in compress.c is nearly identical to the code + here, so if you fix a bug here, look there to see if a matching bug + needs to be fixed. I tried to have one set of functions that could + do ZIP, ZLIB, and BZIP2, but it became dangerously unreadable with + #ifdefs and if(algo) -dshaw */ + +static void +init_compress( compress_filter_context_t *zfx, bz_stream *bzs ) +{ + int rc; + int level; + + if( opt.bz2_compress_level >= 1 && opt.bz2_compress_level <= 9 ) + level = opt.bz2_compress_level; + else if( opt.bz2_compress_level == -1 ) + level = 6; /* no particular reason, but it seems reasonable */ + else + { + log_error("invalid compression level; using default level\n"); + level = 6; + } + + if((rc=BZ2_bzCompressInit(bzs,level,0,0))!=BZ_OK) + log_fatal("bz2lib problem: %d\n",rc); + + zfx->outbufsize = 8192; + zfx->outbuf = xmalloc( zfx->outbufsize ); +} + +static int +do_compress(compress_filter_context_t *zfx, bz_stream *bzs, int flush, IOBUF a) +{ + int zrc; + unsigned n; + + do + { + bzs->next_out = zfx->outbuf; + bzs->avail_out = zfx->outbufsize; + if( DBG_FILTER ) + log_debug("enter bzCompress: avail_in=%u, avail_out=%u, flush=%d\n", + (unsigned)bzs->avail_in, (unsigned)bzs->avail_out, flush ); + zrc = BZ2_bzCompress( bzs, flush ); + if( zrc == BZ_STREAM_END && flush == BZ_FINISH ) + ; + else if( zrc != BZ_RUN_OK && zrc != BZ_FINISH_OK ) + log_fatal("bz2lib deflate problem: rc=%d\n", zrc ); + + n = zfx->outbufsize - bzs->avail_out; + if( DBG_FILTER ) + log_debug("leave bzCompress:" + " avail_in=%u, avail_out=%u, n=%u, zrc=%d\n", + (unsigned)bzs->avail_in, (unsigned)bzs->avail_out, + (unsigned)n, zrc ); + + if( iobuf_write( a, zfx->outbuf, n ) ) + { + log_debug("bzCompress: iobuf_write failed\n"); + return G10ERR_WRITE_FILE; + } + } + while( bzs->avail_in || (flush == BZ_FINISH && zrc != BZ_STREAM_END) ); + + return 0; +} + +static void +init_uncompress( compress_filter_context_t *zfx, bz_stream *bzs ) +{ + int rc; + + if((rc=BZ2_bzDecompressInit(bzs,0,opt.bz2_decompress_lowmem))!=BZ_OK) + log_fatal("bz2lib problem: %d\n",rc); + + zfx->inbufsize = 2048; + zfx->inbuf = xmalloc( zfx->inbufsize ); + bzs->avail_in = 0; +} + +static int +do_uncompress( compress_filter_context_t *zfx, bz_stream *bzs, + IOBUF a, size_t *ret_len ) +{ + int zrc; + int rc=0; + size_t n; + int nread, count; + int refill = !bzs->avail_in; + + if( DBG_FILTER ) + log_debug("begin bzDecompress: avail_in=%u, avail_out=%u, inbuf=%u\n", + (unsigned)bzs->avail_in, (unsigned)bzs->avail_out, + (unsigned)zfx->inbufsize ); + do + { + if( bzs->avail_in < zfx->inbufsize && refill ) + { + n = bzs->avail_in; + if( !n ) + bzs->next_in = zfx->inbuf; + count = zfx->inbufsize - n; + nread = iobuf_read( a, zfx->inbuf + n, count ); + if( nread == -1 ) nread = 0; + n += nread; + bzs->avail_in = n; + } + + refill = 1; + + if( DBG_FILTER ) + log_debug("enter bzDecompress: avail_in=%u, avail_out=%u\n", + (unsigned)bzs->avail_in, (unsigned)bzs->avail_out); + + zrc=BZ2_bzDecompress(bzs); + if( DBG_FILTER ) + log_debug("leave bzDecompress: avail_in=%u, avail_out=%u, zrc=%d\n", + (unsigned)bzs->avail_in, (unsigned)bzs->avail_out, zrc); + if( zrc == BZ_STREAM_END ) + rc = -1; /* eof */ + else if( zrc != BZ_OK && zrc != BZ_PARAM_ERROR ) + log_fatal("bz2lib inflate problem: rc=%d\n", zrc ); + } + while( bzs->avail_out && zrc != BZ_STREAM_END && zrc != BZ_PARAM_ERROR ); + + /* I'm not completely happy with the two uses of BZ_PARAM_ERROR + here. The corresponding zlib function is Z_BUF_ERROR, which + covers a narrower scope than BZ_PARAM_ERROR. -dshaw */ + + *ret_len = zfx->outbufsize - bzs->avail_out; + if( DBG_FILTER ) + log_debug("do_uncompress: returning %u bytes\n", (unsigned)*ret_len ); + return rc; +} + +int +compress_filter_bz2( void *opaque, int control, + IOBUF a, byte *buf, size_t *ret_len) +{ + size_t size = *ret_len; + compress_filter_context_t *zfx = opaque; + bz_stream *bzs = zfx->opaque; + int rc=0; + + if( control == IOBUFCTRL_UNDERFLOW ) + { + if( !zfx->status ) + { + bzs = zfx->opaque = xmalloc_clear( sizeof *bzs ); + init_uncompress( zfx, bzs ); + zfx->status = 1; + } + + bzs->next_out = buf; + bzs->avail_out = size; + zfx->outbufsize = size; /* needed only for calculation */ + rc = do_uncompress( zfx, bzs, a, ret_len ); + } + else if( control == IOBUFCTRL_FLUSH ) + { + if( !zfx->status ) + { + PACKET pkt; + PKT_compressed cd; + + if( zfx->algo != COMPRESS_ALGO_BZIP2 ) + BUG(); + memset( &cd, 0, sizeof cd ); + cd.len = 0; + cd.algorithm = zfx->algo; + init_packet( &pkt ); + pkt.pkttype = PKT_COMPRESSED; + pkt.pkt.compressed = &cd; + if( build_packet( a, &pkt )) + log_bug("build_packet(PKT_COMPRESSED) failed\n"); + bzs = zfx->opaque = xmalloc_clear( sizeof *bzs ); + init_compress( zfx, bzs ); + zfx->status = 2; + } + + bzs->next_in = buf; + bzs->avail_in = size; + rc = do_compress( zfx, bzs, BZ_RUN, a ); + } + else if( control == IOBUFCTRL_FREE ) + { + if( zfx->status == 1 ) + { + BZ2_bzDecompressEnd(bzs); + xfree(bzs); + zfx->opaque = NULL; + xfree(zfx->outbuf); zfx->outbuf = NULL; + } + else if( zfx->status == 2 ) + { + bzs->next_in = buf; + bzs->avail_in = 0; + do_compress( zfx, bzs, BZ_FINISH, a ); + BZ2_bzCompressEnd(bzs); + xfree(bzs); + zfx->opaque = NULL; + xfree(zfx->outbuf); zfx->outbuf = NULL; + } + if (zfx->release) + zfx->release (zfx); + } + else if( control == IOBUFCTRL_DESC ) + *(char**)buf = "compress_filter"; + return rc; +} diff --git a/g10/compress.c b/g10/compress.c new file mode 100644 index 0000000..0cee5ae --- /dev/null +++ b/g10/compress.c @@ -0,0 +1,365 @@ +/* compress.c - compress filter + * Copyright (C) 1998, 1999, 2000, 2001, 2002, + * 2003 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +/* Note that the code in compress-bz2.c is nearly identical to the + code here, so if you fix a bug here, look there to see if a + matching bug needs to be fixed. I tried to have one set of + functions that could do ZIP, ZLIB, and BZIP2, but it became + dangerously unreadable with #ifdefs and if(algo) -dshaw */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <assert.h> +#include <errno.h> +#include <zlib.h> +#if defined(__riscos__) && defined(USE_ZLIBRISCOS) +# include "zlib-riscos.h" +#endif + +#include "util.h" +#include "memory.h" +#include "packet.h" +#include "filter.h" +#include "main.h" +#include "options.h" + +int compress_filter_bz2( void *opaque, int control, + IOBUF a, byte *buf, size_t *ret_len); + +static void +init_compress( compress_filter_context_t *zfx, z_stream *zs ) +{ + int rc; + int level; + +#if defined(__riscos__) && defined(USE_ZLIBRISCOS) + static int zlib_initialized = 0; + + if (!zlib_initialized) + zlib_initialized = riscos_load_module("ZLib", zlib_path, 1); +#endif + + if( opt.compress_level >= 1 && opt.compress_level <= 9 ) + level = opt.compress_level; + else if( opt.compress_level == -1 ) + level = Z_DEFAULT_COMPRESSION; + else { + log_error("invalid compression level; using default level\n"); + level = Z_DEFAULT_COMPRESSION; + } + + if( (rc = zfx->algo == 1? deflateInit2( zs, level, Z_DEFLATED, + -13, 8, Z_DEFAULT_STRATEGY) + : deflateInit( zs, level ) + ) != Z_OK ) { + log_fatal("zlib problem: %s\n", zs->msg? zs->msg : + rc == Z_MEM_ERROR ? "out of core" : + rc == Z_VERSION_ERROR ? "invalid lib version" : + "unknown error" ); + } + + zfx->outbufsize = 8192; + zfx->outbuf = xmalloc( zfx->outbufsize ); +} + +static int +do_compress( compress_filter_context_t *zfx, z_stream *zs, int flush, IOBUF a ) +{ + int zrc; + unsigned n; + + do { +#ifndef __riscos__ + zs->next_out = zfx->outbuf; +#else /* __riscos__ */ + zs->next_out = (Bytef *) zfx->outbuf; +#endif /* __riscos__ */ + zs->avail_out = zfx->outbufsize; + if( DBG_FILTER ) + log_debug("enter deflate: avail_in=%u, avail_out=%u, flush=%d\n", + (unsigned)zs->avail_in, (unsigned)zs->avail_out, flush ); + zrc = deflate( zs, flush ); + if( zrc == Z_STREAM_END && flush == Z_FINISH ) + ; + else if( zrc != Z_OK ) { + if( zs->msg ) + log_fatal("zlib deflate problem: %s\n", zs->msg ); + else + log_fatal("zlib deflate problem: rc=%d\n", zrc ); + } + n = zfx->outbufsize - zs->avail_out; + if( DBG_FILTER ) + log_debug("leave deflate: " + "avail_in=%u, avail_out=%u, n=%u, zrc=%d\n", + (unsigned)zs->avail_in, (unsigned)zs->avail_out, + (unsigned)n, zrc ); + + if( iobuf_write( a, zfx->outbuf, n ) ) { + log_debug("deflate: iobuf_write failed\n"); + return G10ERR_WRITE_FILE; + } + } while( zs->avail_in || (flush == Z_FINISH && zrc != Z_STREAM_END) ); + return 0; +} + +static void +init_uncompress( compress_filter_context_t *zfx, z_stream *zs ) +{ + int rc; + + /**************** + * PGP uses a windowsize of 13 bits. Using a negative value for + * it forces zlib not to expect a zlib header. This is a + * undocumented feature Peter Gutmann told me about. + * + * We must use 15 bits for the inflator because CryptoEx uses 15 + * bits thus the output would get scrambled w/o error indication + * if we would use 13 bits. For the uncompressing this does not + * matter at all. + */ + if( (rc = zfx->algo == 1? inflateInit2( zs, -15) + : inflateInit( zs )) != Z_OK ) { + log_fatal("zlib problem: %s\n", zs->msg? zs->msg : + rc == Z_MEM_ERROR ? "out of core" : + rc == Z_VERSION_ERROR ? "invalid lib version" : + "unknown error" ); + } + + zfx->inbufsize = 2048; + zfx->inbuf = xmalloc( zfx->inbufsize ); + zs->avail_in = 0; +} + +static int +do_uncompress( compress_filter_context_t *zfx, z_stream *zs, + IOBUF a, size_t *ret_len ) +{ + int zrc; + int rc=0; + size_t n; + int nread, count; + int refill = !zs->avail_in; + + if( DBG_FILTER ) + log_debug("begin inflate: avail_in=%u, avail_out=%u, inbuf=%u\n", + (unsigned)zs->avail_in, (unsigned)zs->avail_out, + (unsigned)zfx->inbufsize ); + do { + if( zs->avail_in < zfx->inbufsize && refill ) { + n = zs->avail_in; + if( !n ) +#ifndef __riscos__ + zs->next_in = zfx->inbuf; +#else /* __riscos__ */ + zs->next_in = (Bytef *) zfx->inbuf; +#endif /* __riscos__ */ + count = zfx->inbufsize - n; + nread = iobuf_read( a, zfx->inbuf + n, count ); + if( nread == -1 ) nread = 0; + n += nread; + /* If we use the undocumented feature to suppress + * the zlib header, we have to give inflate an + * extra dummy byte to read */ + if( nread < count && zfx->algo == 1 ) { + *(zfx->inbuf + n) = 0xFF; /* is it really needed ? */ + zfx->algo1hack = 1; + n++; + } + zs->avail_in = n; + } + refill = 1; + if( DBG_FILTER ) + log_debug("enter inflate: avail_in=%u, avail_out=%u\n", + (unsigned)zs->avail_in, (unsigned)zs->avail_out); +#ifdef Z_SYNC_FLUSH + zrc = inflate( zs, Z_SYNC_FLUSH ); +#else + zrc = inflate( zs, Z_PARTIAL_FLUSH ); +#endif + if( DBG_FILTER ) + log_debug("leave inflate: avail_in=%u, avail_out=%u, zrc=%d\n", + (unsigned)zs->avail_in, (unsigned)zs->avail_out, zrc); + if( zrc == Z_STREAM_END ) + rc = -1; /* eof */ + else if( zrc != Z_OK && zrc != Z_BUF_ERROR ) { + if( zs->msg ) + log_fatal("zlib inflate problem: %s\n", zs->msg ); + else + log_fatal("zlib inflate problem: rc=%d\n", zrc ); + } + } while( zs->avail_out && zrc != Z_STREAM_END && zrc != Z_BUF_ERROR ); + *ret_len = zfx->outbufsize - zs->avail_out; + if( DBG_FILTER ) + log_debug("do_uncompress: returning %u bytes\n", (unsigned)*ret_len ); + return rc; +} + +static int +compress_filter( void *opaque, int control, + IOBUF a, byte *buf, size_t *ret_len) +{ + size_t size = *ret_len; + compress_filter_context_t *zfx = opaque; + z_stream *zs = zfx->opaque; + int rc=0; + + if( control == IOBUFCTRL_UNDERFLOW ) { + if( !zfx->status ) { + zs = zfx->opaque = xmalloc_clear( sizeof *zs ); + init_uncompress( zfx, zs ); + zfx->status = 1; + } + +#ifndef __riscos__ + zs->next_out = buf; +#else /* __riscos__ */ + zs->next_out = (Bytef *) buf; +#endif /* __riscos__ */ + zs->avail_out = size; + zfx->outbufsize = size; /* needed only for calculation */ + rc = do_uncompress( zfx, zs, a, ret_len ); + } + else if( control == IOBUFCTRL_FLUSH ) { + if( !zfx->status ) { + PACKET pkt; + PKT_compressed cd; + if(zfx->algo != COMPRESS_ALGO_ZIP + && zfx->algo != COMPRESS_ALGO_ZLIB) + BUG(); + memset( &cd, 0, sizeof cd ); + cd.len = 0; + cd.algorithm = zfx->algo; + init_packet( &pkt ); + pkt.pkttype = PKT_COMPRESSED; + pkt.pkt.compressed = &cd; + if( build_packet( a, &pkt )) + log_bug("build_packet(PKT_COMPRESSED) failed\n"); + zs = zfx->opaque = xmalloc_clear( sizeof *zs ); + init_compress( zfx, zs ); + zfx->status = 2; + } + +#ifndef __riscos__ + zs->next_in = buf; +#else /* __riscos__ */ + zs->next_in = (Bytef *) buf; +#endif /* __riscos__ */ + zs->avail_in = size; + rc = do_compress( zfx, zs, Z_NO_FLUSH, a ); + } + else if( control == IOBUFCTRL_FREE ) { + if( zfx->status == 1 ) { + inflateEnd(zs); + xfree(zs); + zfx->opaque = NULL; + xfree(zfx->outbuf); zfx->outbuf = NULL; + } + else if( zfx->status == 2 ) { +#ifndef __riscos__ + zs->next_in = buf; +#else /* __riscos__ */ + zs->next_in = (Bytef *) buf; +#endif /* __riscos__ */ + zs->avail_in = 0; + do_compress( zfx, zs, Z_FINISH, a ); + deflateEnd(zs); + xfree(zs); + zfx->opaque = NULL; + xfree(zfx->outbuf); zfx->outbuf = NULL; + } + if (zfx->release) + zfx->release (zfx); + } + else if( control == IOBUFCTRL_DESC ) + *(char**)buf = "compress_filter"; + return rc; +} + + +static void +release_context (compress_filter_context_t *ctx) +{ + xfree (ctx); +} + +/**************** + * Handle a compressed packet + */ +int +handle_compressed( void *procctx, PKT_compressed *cd, + int (*callback)(IOBUF, void *), void *passthru ) +{ + compress_filter_context_t *cfx; + int rc; + + if(check_compress_algo(cd->algorithm)) + return G10ERR_COMPR_ALGO; + cfx = xmalloc_clear (sizeof *cfx); + cfx->release = release_context; + cfx->algo = cd->algorithm; + push_compress_filter(cd->buf,cfx,cd->algorithm); + if( callback ) + rc = callback(cd->buf, passthru ); + else + rc = proc_packets(procctx, cd->buf); + cd->buf = NULL; + return rc; +} + +void +push_compress_filter(IOBUF out,compress_filter_context_t *zfx,int algo) +{ + push_compress_filter2(out,zfx,algo,0); +} + +void +push_compress_filter2(IOBUF out,compress_filter_context_t *zfx, + int algo,int rel) +{ + if(algo>=0) + zfx->algo=algo; + else + zfx->algo=DEFAULT_COMPRESS_ALGO; + + switch(zfx->algo) + { + case COMPRESS_ALGO_NONE: + break; + + case COMPRESS_ALGO_ZIP: + case COMPRESS_ALGO_ZLIB: + iobuf_push_filter2(out,compress_filter,zfx,rel); + break; + +#ifdef HAVE_BZIP2 + case COMPRESS_ALGO_BZIP2: + iobuf_push_filter2(out,compress_filter_bz2,zfx,rel); + break; +#endif + + default: + BUG(); + } +} diff --git a/g10/dearmor.c b/g10/dearmor.c new file mode 100644 index 0000000..8d7c60a --- /dev/null +++ b/g10/dearmor.c @@ -0,0 +1,137 @@ +/* dearmor.c - Armor utility + * Copyright (C) 1998, 1999, 2000, 2001 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <assert.h> + +#include "errors.h" +#include "iobuf.h" +#include "memory.h" +#include "util.h" +#include "filter.h" +#include "packet.h" +#include "options.h" +#include "main.h" +#include "i18n.h" + +/**************** + * Take an armor file and write it out without armor + */ +int +dearmor_file( const char *fname ) +{ + armor_filter_context_t afx; + IOBUF inp = NULL, out = NULL; + int rc = 0; + int c; + + memset( &afx, 0, sizeof afx); + + /* prepare iobufs */ + inp = iobuf_open(fname); + if (inp && is_secured_file (iobuf_get_fd (inp))) + { + iobuf_close (inp); + inp = NULL; + errno = EPERM; + } + if (!inp) { + log_error(_("can't open `%s': %s\n"), fname? fname: "[stdin]", + strerror(errno) ); + rc = G10ERR_OPEN_FILE; + goto leave; + } + + iobuf_push_filter( inp, armor_filter, &afx ); + + if( (rc = open_outfile( fname, 0, &out )) ) + goto leave; + + + + while( (c = iobuf_get(inp)) != -1 ) + iobuf_put( out, c ); + + + leave: + if( rc ) + iobuf_cancel(out); + else + iobuf_close(out); + iobuf_close(inp); + return rc; +} + + +/**************** + * Take file and write it out with armor + */ +int +enarmor_file( const char *fname ) +{ + armor_filter_context_t afx; + IOBUF inp = NULL, out = NULL; + int rc = 0; + int c; + + memset( &afx, 0, sizeof afx); + + /* prepare iobufs */ + inp = iobuf_open(fname); + if (inp && is_secured_file (iobuf_get_fd (inp))) + { + iobuf_close (inp); + inp = NULL; + errno = EPERM; + } + if (!inp) { + log_error(_("can't open `%s': %s\n"), fname? fname: "[stdin]", + strerror(errno) ); + rc = G10ERR_OPEN_FILE; + goto leave; + } + + + if( (rc = open_outfile( fname, 1, &out )) ) + goto leave; + + afx.what = 4; + afx.hdrlines = "Comment: Use \"gpg --dearmor\" for unpacking\n"; + iobuf_push_filter( out, armor_filter, &afx ); + + while( (c = iobuf_get(inp)) != -1 ) + iobuf_put( out, c ); + + + leave: + if( rc ) + iobuf_cancel(out); + else + iobuf_close(out); + iobuf_close(inp); + return rc; +} + + diff --git a/g10/decrypt.c b/g10/decrypt.c new file mode 100644 index 0000000..171e265 --- /dev/null +++ b/g10/decrypt.c @@ -0,0 +1,191 @@ +/* decrypt.c - verify signed data + * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003 + * 2004 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <assert.h> + +#include "options.h" +#include "packet.h" +#include "errors.h" +#include "iobuf.h" +#include "keydb.h" +#include "memory.h" +#include "util.h" +#include "main.h" +#include "status.h" +#include "i18n.h" + + + +/**************** + * Assume that the input is an encrypted message and decrypt + * (and if signed, verify the signature on) it. + * This command differs from the default operation, as it never + * writes to the filename which is included in the file and it + * rejects files which don't begin with an encrypted message. + */ + +int +decrypt_message( const char *filename ) +{ + IOBUF fp; + armor_filter_context_t afx; + progress_filter_context_t pfx; + int rc; + int no_out=0; + + /* open the message file */ + fp = iobuf_open(filename); + if (fp && is_secured_file (iobuf_get_fd (fp))) + { + iobuf_close (fp); + fp = NULL; + errno = EPERM; + } + if( !fp ) { + log_error(_("can't open `%s'\n"), print_fname_stdin(filename)); + return G10ERR_OPEN_FILE; + } + + handle_progress (&pfx, fp, filename); + + if( !opt.no_armor ) { + if( use_armor_filter( fp ) ) { + memset( &afx, 0, sizeof afx); + iobuf_push_filter( fp, armor_filter, &afx ); + } + } + + if( !opt.outfile ) { + no_out = 1; + opt.outfile = "-"; + } + rc = proc_encryption_packets( NULL, fp ); + if( no_out ) + opt.outfile = NULL; + iobuf_close(fp); + return rc; +} + +void +decrypt_messages(int nfiles, char *files[]) +{ + IOBUF fp; + armor_filter_context_t afx; + progress_filter_context_t pfx; + char *p, *output = NULL; + int rc=0,use_stdin=0; + unsigned int lno=0; + + if (opt.outfile) + { + log_error(_("--output doesn't work for this command\n")); + return; + + } + + if(!nfiles) + use_stdin=1; + + for(;;) + { + char line[2048]; + char *filename=NULL; + + if(use_stdin) + { + if(fgets(line, DIM(line), stdin)) + { + lno++; + if (!*line || line[strlen(line)-1] != '\n') + log_error("input line %u too long or missing LF\n", lno); + else + { + line[strlen(line)-1] = '\0'; + filename=line; + } + } + } + else + { + if(nfiles) + { + filename=*files; + nfiles--; + files++; + } + } + + if(filename==NULL) + break; + + print_file_status(STATUS_FILE_START, filename, 3); + output = make_outfile_name(filename); + if (!output) + goto next_file; + fp = iobuf_open(filename); + if (fp) + iobuf_ioctl (fp,3,1,NULL); /* disable fd caching */ + if (fp && is_secured_file (iobuf_get_fd (fp))) + { + iobuf_close (fp); + fp = NULL; + errno = EPERM; + } + if (!fp) + { + log_error(_("can't open `%s'\n"), print_fname_stdin(filename)); + goto next_file; + } + + handle_progress (&pfx, fp, filename); + + if (!opt.no_armor) + { + if (use_armor_filter(fp)) + { + memset(&afx, 0, sizeof afx); + iobuf_push_filter(fp, armor_filter, &afx); + } + } + rc = proc_packets(NULL, fp); + iobuf_close(fp); + if (rc) + log_error("%s: decryption failed: %s\n", print_fname_stdin(filename), + g10_errstr(rc)); + p = get_last_passphrase(); + set_next_passphrase(p); + xfree (p); + + next_file: + /* Note that we emit file_done even after an error. */ + write_status( STATUS_FILE_DONE ); + iobuf_ioctl( NULL, 2, 0, NULL); /* Invalidate entire cache. */ + xfree(output); + } + + set_next_passphrase(NULL); +} diff --git a/g10/delkey.c b/g10/delkey.c new file mode 100644 index 0000000..354851d --- /dev/null +++ b/g10/delkey.c @@ -0,0 +1,221 @@ +/* delkey.c - delete keys + * Copyright (C) 1998, 1999, 2000, 2001, 2002, 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 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <assert.h> +#include <ctype.h> + +#include "options.h" +#include "packet.h" +#include "errors.h" +#include "iobuf.h" +#include "keydb.h" +#include "memory.h" +#include "util.h" +#include "main.h" +#include "trustdb.h" +#include "filter.h" +#include "ttyio.h" +#include "status.h" +#include "i18n.h" + + +/**************** + * Delete a public or secret key from a keyring. + * r_sec_avail will be set if a secret key is available and the public + * key can't be deleted for that reason. + */ +static int +do_delete_key( const char *username, int secret, int force, int *r_sec_avail ) +{ + int rc = 0; + KBNODE keyblock = NULL; + KBNODE node; + KEYDB_HANDLE hd = keydb_new (secret); + PKT_public_key *pk = NULL; + PKT_secret_key *sk = NULL; + u32 keyid[2]; + int okay=0; + int yes; + KEYDB_SEARCH_DESC desc; + int exactmatch; + + *r_sec_avail = 0; + + /* search the userid */ + classify_user_id (username, &desc); + exactmatch = (desc.mode == KEYDB_SEARCH_MODE_FPR + || desc.mode == KEYDB_SEARCH_MODE_FPR16 + || desc.mode == KEYDB_SEARCH_MODE_FPR20); + rc = desc.mode? keydb_search (hd, &desc, 1):G10ERR_INV_USER_ID; + if (rc) { + log_error (_("key \"%s\" not found: %s\n"), username, g10_errstr (rc)); + write_status_text( STATUS_DELETE_PROBLEM, "1" ); + goto leave; + } + + /* read the keyblock */ + rc = keydb_get_keyblock (hd, &keyblock ); + if (rc) { + log_error (_("error reading keyblock: %s\n"), g10_errstr(rc) ); + goto leave; + } + + /* get the keyid from the keyblock */ + node = find_kbnode( keyblock, secret? PKT_SECRET_KEY:PKT_PUBLIC_KEY ); + if( !node ) { + log_error("Oops; key not found anymore!\n"); + rc = G10ERR_GENERAL; + goto leave; + } + + if( secret ) + { + sk = node->pkt->pkt.secret_key; + keyid_from_sk( sk, keyid ); + } + else + { + /* public */ + pk = node->pkt->pkt.public_key; + keyid_from_pk( pk, keyid ); + + if(!force) + { + rc = seckey_available( keyid ); + if( !rc ) + { + *r_sec_avail = 1; + rc = -1; + goto leave; + } + else if( rc != G10ERR_NO_SECKEY ) + log_error("%s: get secret key: %s\n", username, g10_errstr(rc) ); + else + rc = 0; + } + } + + if( rc ) + rc = 0; + else if (opt.batch && exactmatch) + okay++; + else if( opt.batch && secret ) + { + log_error(_("can't do this in batch mode\n")); + log_info (_("(unless you specify the key by fingerprint)\n")); + } + else if( opt.batch && opt.answer_yes ) + okay++; + else if( opt.batch ) + { + log_error(_("can't do this in batch mode without \"--yes\"\n")); + log_info (_("(unless you specify the key by fingerprint)\n")); + } + else { + if( secret ) + print_seckey_info( sk ); + else + print_pubkey_info(NULL, pk ); + tty_printf( "\n" ); + + yes = cpr_get_answer_is_yes( secret? "delete_key.secret.okay" + : "delete_key.okay", + _("Delete this key from the keyring? (y/N) ")); + if( !cpr_enabled() && secret && yes ) { + /* I think it is not required to check a passphrase; if + * the user is so stupid as to let others access his secret keyring + * (and has no backup) - it is up him to read some very + * basic texts about security. + */ + yes = cpr_get_answer_is_yes("delete_key.secret.okay", + _("This is a secret key! - really delete? (y/N) ")); + } + if( yes ) + okay++; + } + + + if( okay ) { + rc = keydb_delete_keyblock (hd); + if (rc) { + log_error (_("deleting keyblock failed: %s\n"), g10_errstr(rc) ); + goto leave; + } + + /* Note that the ownertrust being cleared will trigger a + revalidation_mark(). This makes sense - only deleting keys + that have ownertrust set should trigger this. */ + + if (!secret && pk && clear_ownertrusts (pk)) { + if (opt.verbose) + log_info (_("ownertrust information cleared\n")); + } + } + + leave: + keydb_release (hd); + release_kbnode (keyblock); + return rc; +} + +/**************** + * Delete a public or secret key from a keyring. + */ +int +delete_keys( STRLIST names, int secret, int allow_both ) +{ + int rc, avail, force=(!allow_both && !secret && opt.expert); + + /* Force allows us to delete a public key even if a secret key + exists. */ + + for(;names;names=names->next) { + rc = do_delete_key (names->d, secret, force, &avail ); + if ( rc && avail ) { + if ( allow_both ) { + rc = do_delete_key (names->d, 1, 0, &avail ); + if ( !rc ) + rc = do_delete_key (names->d, 0, 0, &avail ); + } + else { + log_error(_( + "there is a secret key for public key \"%s\"!\n"),names->d); + log_info(_( + "use option \"--delete-secret-keys\" to delete it first.\n")); + write_status_text( STATUS_DELETE_PROBLEM, "2" ); + return rc; + } + } + + if(rc) { + log_error("%s: delete key failed: %s\n", names->d, g10_errstr(rc) ); + return rc; + } + } + + return 0; +} diff --git a/g10/encode.c b/g10/encode.c new file mode 100644 index 0000000..251ea3e --- /dev/null +++ b/g10/encode.c @@ -0,0 +1,877 @@ +/* encode.c - encode data + * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, + * 2006 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <assert.h> + +#include "options.h" +#include "packet.h" +#include "errors.h" +#include "iobuf.h" +#include "keydb.h" +#include "memory.h" +#include "util.h" +#include "main.h" +#include "filter.h" +#include "trustdb.h" +#include "i18n.h" +#include "status.h" + +static int encode_simple( const char *filename, int mode, int use_seskey ); +static int write_pubkey_enc_from_list( PK_LIST pk_list, DEK *dek, IOBUF out ); + +/**************** + * Encode FILENAME with only the symmetric cipher. Take input from + * stdin if FILENAME is NULL. + */ +int +encode_symmetric( const char *filename ) +{ + return encode_simple( filename, 1, 0 ); +} + +/**************** + * Encode FILENAME as a literal data packet only. Take input from + * stdin if FILENAME is NULL. + */ +int +encode_store( const char *filename ) +{ + return encode_simple( filename, 0, 0 ); +} + +static void +encode_seskey( DEK *dek, DEK **seskey, byte *enckey ) +{ + CIPHER_HANDLE hd; + byte buf[33]; + + assert ( dek->keylen <= 32 ); + if(!*seskey) + { + *seskey=xmalloc_clear(sizeof(DEK)); + (*seskey)->keylen=dek->keylen; + (*seskey)->algo=dek->algo; + make_session_key(*seskey); + /*log_hexdump( "thekey", c->key, c->keylen );*/ + } + + buf[0] = (*seskey)->algo; + memcpy( buf + 1, (*seskey)->key, (*seskey)->keylen ); + + hd = cipher_open( dek->algo, CIPHER_MODE_CFB, 1 ); + cipher_setkey( hd, dek->key, dek->keylen ); + cipher_setiv( hd, NULL, 0 ); + cipher_encrypt( hd, buf, buf, (*seskey)->keylen + 1 ); + cipher_close( hd ); + + memcpy( enckey, buf, (*seskey)->keylen + 1 ); + wipememory( buf, sizeof buf ); /* burn key */ +} + +/* We try very hard to use a MDC */ +static int +use_mdc(PK_LIST pk_list,int algo) +{ + /* RFC-1991 and 2440 don't have MDC */ + if(RFC1991 || RFC2440) + return 0; + + /* --force-mdc overrides --disable-mdc */ + if(opt.force_mdc) + return 1; + + if(opt.disable_mdc) + return 0; + + /* Do the keys really support MDC? */ + + if(select_mdc_from_pklist(pk_list)) + return 1; + + /* The keys don't support MDC, so now we do a bit of a hack - if any + of the AESes or TWOFISH are in the prefs, we assume that the user + can handle a MDC. This is valid for PGP 7, which can handle MDCs + though it will not generate them. 2440bis allows this, by the + way. */ + + if(select_algo_from_prefs(pk_list,PREFTYPE_SYM, + CIPHER_ALGO_AES,NULL)==CIPHER_ALGO_AES) + return 1; + + if(select_algo_from_prefs(pk_list,PREFTYPE_SYM, + CIPHER_ALGO_AES192,NULL)==CIPHER_ALGO_AES192) + return 1; + + if(select_algo_from_prefs(pk_list,PREFTYPE_SYM, + CIPHER_ALGO_AES256,NULL)==CIPHER_ALGO_AES256) + return 1; + + if(select_algo_from_prefs(pk_list,PREFTYPE_SYM, + CIPHER_ALGO_TWOFISH,NULL)==CIPHER_ALGO_TWOFISH) + return 1; + + /* Last try. Use MDC for the modern ciphers. */ + + if(cipher_get_blocksize(algo)!=8) + return 1; + + return 0; /* No MDC */ +} + +/* We don't want to use use_seskey yet because older gnupg versions + can't handle it, and there isn't really any point unless we're + making a message that can be decrypted by a public key or + passphrase. */ +static int +encode_simple( const char *filename, int mode, int use_seskey ) +{ + IOBUF inp, out; + PACKET pkt; + PKT_plaintext *pt = NULL; + STRING2KEY *s2k = NULL; + byte enckey[33]; + int rc = 0; + int seskeylen = 0; + u32 filesize; + cipher_filter_context_t cfx; + armor_filter_context_t afx; + compress_filter_context_t zfx; + text_filter_context_t tfx; + progress_filter_context_t pfx; + int do_compress = !RFC1991 && default_compress_algo(); + + memset( &cfx, 0, sizeof cfx); + memset( &afx, 0, sizeof afx); + memset( &zfx, 0, sizeof zfx); + memset( &tfx, 0, sizeof tfx); + init_packet(&pkt); + + /* prepare iobufs */ + inp = iobuf_open(filename); + if (inp) + iobuf_ioctl (inp,3,1,NULL); /* disable fd caching */ + if (inp && is_secured_file (iobuf_get_fd (inp))) + { + iobuf_close (inp); + inp = NULL; + errno = EPERM; + } + if( !inp ) { + log_error(_("can't open `%s': %s\n"), filename? filename: "[stdin]", + strerror(errno) ); + return G10ERR_OPEN_FILE; + } + + handle_progress (&pfx, inp, filename); + + if( opt.textmode ) + iobuf_push_filter( inp, text_filter, &tfx ); + + /* Due the the fact that we use don't use an IV to encrypt the + session key we can't use the new mode with RFC1991 because + it has no S2K salt. RFC1991 always uses simple S2K. */ + if ( RFC1991 && use_seskey ) + use_seskey = 0; + + cfx.dek = NULL; + if( mode ) { + s2k = xmalloc_clear( sizeof *s2k ); + s2k->mode = RFC1991? 0:opt.s2k_mode; + s2k->hash_algo=S2K_DIGEST_ALGO; + cfx.dek = passphrase_to_dek( NULL, 0, + default_cipher_algo(), s2k, 2, + NULL, NULL); + if( !cfx.dek || !cfx.dek->keylen ) { + rc = G10ERR_PASSPHRASE; + xfree(cfx.dek); + xfree(s2k); + iobuf_close(inp); + log_error(_("error creating passphrase: %s\n"), g10_errstr(rc) ); + return rc; + } + if (use_seskey && s2k->mode != 1 && s2k->mode != 3) { + use_seskey = 0; + log_info (_("can't use a symmetric ESK packet " + "due to the S2K mode\n")); + } + + if ( use_seskey ) + { + DEK *dek = NULL; + seskeylen = cipher_get_keylen( default_cipher_algo() ) / 8; + encode_seskey( cfx.dek, &dek, enckey ); + xfree( cfx.dek ); cfx.dek = dek; + } + + if(opt.verbose) + log_info(_("using cipher %s\n"), + cipher_algo_to_string(cfx.dek->algo)); + + cfx.dek->use_mdc=use_mdc(NULL,cfx.dek->algo); + } + + if (do_compress && cfx.dek && cfx.dek->use_mdc + && is_file_compressed(filename, &rc)) + { + if (opt.verbose) + log_info(_("`%s' already compressed\n"), filename); + do_compress = 0; + } + + if( rc || (rc = open_outfile( filename, opt.armor? 1:0, &out )) ) { + iobuf_cancel(inp); + xfree(cfx.dek); + xfree(s2k); + return rc; + } + + if( opt.armor ) + iobuf_push_filter( out, armor_filter, &afx ); + + if( s2k && !RFC1991 ) { + PKT_symkey_enc *enc = xmalloc_clear( sizeof *enc + seskeylen + 1 ); + enc->version = 4; + enc->cipher_algo = cfx.dek->algo; + enc->s2k = *s2k; + if ( use_seskey && seskeylen ) { + enc->seskeylen = seskeylen + 1; /* algo id */ + memcpy( enc->seskey, enckey, seskeylen + 1 ); + } + pkt.pkttype = PKT_SYMKEY_ENC; + pkt.pkt.symkey_enc = enc; + if( (rc = build_packet( out, &pkt )) ) + log_error("build symkey packet failed: %s\n", g10_errstr(rc) ); + xfree(enc); + } + + if (!opt.no_literal) + pt=setup_plaintext_name(filename,inp); + + /* Note that PGP 5 has problems decrypting symmetrically encrypted + data if the file length is in the inner packet. It works when + only partial length headers are use. In the past, we always + used partial body length here, but since PGP 2, PGP 6, and PGP + 7 need the file length, and nobody should be using PGP 5 + nowadays anyway, this is now set to the file length. Note also + that this only applies to the RFC-1991 style symmetric + messages, and not the RFC-2440 style. PGP 6 and 7 work with + either partial length or fixed length with the new style + messages. */ + + if ( !iobuf_is_pipe_filename (filename) && *filename && !opt.textmode ) + { + off_t tmpsize; + int overflow; + + if ( !(tmpsize = iobuf_get_filelength(inp, &overflow)) + && !overflow ) + log_info(_("WARNING: `%s' is an empty file\n"), filename ); + /* We can't encode the length of very large files because + OpenPGP uses only 32 bit for file sizes. So if the the + size of a file is larger than 2^32 minus some bytes for + packet headers, we switch to partial length encoding. */ + if ( tmpsize < (IOBUF_FILELENGTH_LIMIT - 65536) ) + filesize = tmpsize; + else + filesize = 0; + } + else + filesize = opt.set_filesize ? opt.set_filesize : 0; /* stdin */ + + if (!opt.no_literal) { + pt->timestamp = make_timestamp(); + pt->mode = opt.textmode? 't' : 'b'; + pt->len = filesize; + pt->new_ctb = !pt->len && !RFC1991; + pt->buf = inp; + pkt.pkttype = PKT_PLAINTEXT; + pkt.pkt.plaintext = pt; + cfx.datalen = filesize && !do_compress ? calc_packet_length( &pkt ) : 0; + } + else + { + cfx.datalen = filesize && !do_compress ? filesize : 0; + pkt.pkttype = 0; + pkt.pkt.generic = NULL; + } + + /* register the cipher filter */ + if( mode ) + iobuf_push_filter( out, cipher_filter, &cfx ); + /* register the compress filter */ + if( do_compress ) + { + if (cfx.dek && cfx.dek->use_mdc) + zfx.new_ctb = 1; + push_compress_filter(out,&zfx,default_compress_algo()); + } + + /* do the work */ + if (!opt.no_literal) { + if( (rc = build_packet( out, &pkt )) ) + log_error("build_packet failed: %s\n", g10_errstr(rc) ); + } + else { + /* user requested not to create a literal packet, + * so we copy the plain data */ + byte copy_buffer[4096]; + int bytes_copied; + while ((bytes_copied = iobuf_read(inp, copy_buffer, 4096)) != -1) + if (iobuf_write(out, copy_buffer, bytes_copied) == -1) { + rc = G10ERR_WRITE_FILE; + log_error("copying input to output failed: %s\n", g10_errstr(rc) ); + break; + } + wipememory(copy_buffer, 4096); /* burn buffer */ + } + + /* finish the stuff */ + iobuf_close(inp); + if (rc) + iobuf_cancel(out); + else { + iobuf_close(out); /* fixme: check returncode */ + if (mode) + write_status( STATUS_END_ENCRYPTION ); + } + if (pt) + pt->buf = NULL; + free_packet(&pkt); + xfree(cfx.dek); + xfree(s2k); + return rc; +} + +int +setup_symkey(STRING2KEY **symkey_s2k,DEK **symkey_dek) +{ + *symkey_s2k=xmalloc_clear(sizeof(STRING2KEY)); + (*symkey_s2k)->mode = opt.s2k_mode; + (*symkey_s2k)->hash_algo = S2K_DIGEST_ALGO; + + *symkey_dek=passphrase_to_dek(NULL,0,opt.s2k_cipher_algo, + *symkey_s2k,2,NULL,NULL); + if(!*symkey_dek || !(*symkey_dek)->keylen) + { + xfree(*symkey_dek); + xfree(*symkey_s2k); + return G10ERR_PASSPHRASE; + } + + return 0; +} + +static int +write_symkey_enc(STRING2KEY *symkey_s2k,DEK *symkey_dek,DEK *dek,IOBUF out) +{ + int rc,seskeylen=cipher_get_keylen(dek->algo)/8; + + PKT_symkey_enc *enc; + byte enckey[33]; + PACKET pkt; + + enc=xmalloc_clear(sizeof(PKT_symkey_enc)+seskeylen+1); + encode_seskey(symkey_dek,&dek,enckey); + + enc->version = 4; + enc->cipher_algo = opt.s2k_cipher_algo; + enc->s2k = *symkey_s2k; + enc->seskeylen = seskeylen + 1; /* algo id */ + memcpy( enc->seskey, enckey, seskeylen + 1 ); + + pkt.pkttype = PKT_SYMKEY_ENC; + pkt.pkt.symkey_enc = enc; + + if((rc=build_packet(out,&pkt))) + log_error("build symkey_enc packet failed: %s\n",g10_errstr(rc)); + + xfree(enc); + return rc; +} + +/**************** + * Encrypt the file with the given userids (or ask if none + * is supplied). + */ +int +encode_crypt( const char *filename, STRLIST remusr, int use_symkey ) +{ + IOBUF inp = NULL, out = NULL; + PACKET pkt; + PKT_plaintext *pt = NULL; + DEK *symkey_dek = NULL; + STRING2KEY *symkey_s2k = NULL; + int rc = 0, rc2 = 0; + u32 filesize; + cipher_filter_context_t cfx; + armor_filter_context_t afx; + compress_filter_context_t zfx; + text_filter_context_t tfx; + progress_filter_context_t pfx; + PK_LIST pk_list,work_list; + int do_compress = opt.compress_algo && !RFC1991; + + memset( &cfx, 0, sizeof cfx); + memset( &afx, 0, sizeof afx); + memset( &zfx, 0, sizeof zfx); + memset( &tfx, 0, sizeof tfx); + init_packet(&pkt); + + if(use_symkey + && (rc=setup_symkey(&symkey_s2k,&symkey_dek))) + return rc; + + if( (rc=build_pk_list( remusr, &pk_list, PUBKEY_USAGE_ENC)) ) + return rc; + + if(PGP2) { + for(work_list=pk_list; work_list; work_list=work_list->next) + if(!(is_RSA(work_list->pk->pubkey_algo) && + nbits_from_pk(work_list->pk)<=2048)) + { + log_info(_("you can only encrypt to RSA keys of 2048 bits or " + "less in --pgp2 mode\n")); + compliance_failure(); + break; + } + } + + /* prepare iobufs */ + inp = iobuf_open(filename); + if (inp) + iobuf_ioctl (inp,3,1,NULL); /* disable fd caching */ + if (inp && is_secured_file (iobuf_get_fd (inp))) + { + iobuf_close (inp); + inp = NULL; + errno = EPERM; + } + if( !inp ) { + log_error(_("can't open `%s': %s\n"), filename? filename: "[stdin]", + strerror(errno) ); + rc = G10ERR_OPEN_FILE; + goto leave; + } + else if( opt.verbose ) + log_info(_("reading from `%s'\n"), filename? filename: "[stdin]"); + + handle_progress (&pfx, inp, filename); + + if( opt.textmode ) + iobuf_push_filter( inp, text_filter, &tfx ); + + if( (rc = open_outfile( filename, opt.armor? 1:0, &out )) ) + goto leave; + + if( opt.armor ) + iobuf_push_filter( out, armor_filter, &afx ); + + /* create a session key */ + cfx.dek = xmalloc_secure_clear (sizeof *cfx.dek); + if( !opt.def_cipher_algo ) { /* try to get it from the prefs */ + cfx.dek->algo = select_algo_from_prefs(pk_list,PREFTYPE_SYM,-1,NULL); + /* The only way select_algo_from_prefs can fail here is when + mixing v3 and v4 keys, as v4 keys have an implicit + preference entry for 3DES, and the pk_list cannot be empty. + In this case, use 3DES anyway as it's the safest choice - + perhaps the v3 key is being used in an OpenPGP + implementation and we know that the implementation behind + any v4 key can handle 3DES. */ + if( cfx.dek->algo == -1 ) { + cfx.dek->algo = CIPHER_ALGO_3DES; + + if( PGP2 ) { + log_info(_("unable to use the IDEA cipher for all of the keys " + "you are encrypting to.\n")); + compliance_failure(); + } + } + } + else { + if(!opt.expert && + select_algo_from_prefs(pk_list,PREFTYPE_SYM, + opt.def_cipher_algo,NULL)!=opt.def_cipher_algo) + log_info(_("WARNING: forcing symmetric cipher %s (%d)" + " violates recipient preferences\n"), + cipher_algo_to_string(opt.def_cipher_algo), + opt.def_cipher_algo); + + cfx.dek->algo = opt.def_cipher_algo; + } + + cfx.dek->use_mdc=use_mdc(pk_list,cfx.dek->algo); + + /* Only do the is-file-already-compressed check if we are using a + MDC. This forces compressed files to be re-compressed if we do + not have a MDC to give some protection against chosen + ciphertext attacks. */ + + if (do_compress && cfx.dek->use_mdc && is_file_compressed(filename, &rc2) ) + { + if (opt.verbose) + log_info(_("`%s' already compressed\n"), filename); + do_compress = 0; + } + if (rc2) + { + rc = rc2; + goto leave; + } + + make_session_key( cfx.dek ); + if( DBG_CIPHER ) + log_hexdump("DEK is: ", cfx.dek->key, cfx.dek->keylen ); + + rc = write_pubkey_enc_from_list( pk_list, cfx.dek, out ); + if( rc ) + goto leave; + + /* We put the passphrase (if any) after any public keys as this + seems to be the most useful on the recipient side - there is no + point in prompting a user for a passphrase if they have the + secret key needed to decrypt. */ + if(use_symkey && (rc=write_symkey_enc(symkey_s2k,symkey_dek,cfx.dek,out))) + goto leave; + + if (!opt.no_literal) { + /* setup the inner packet */ + if( filename || opt.set_filename ) { + char *s = make_basename( opt.set_filename ? opt.set_filename + : filename, + iobuf_get_real_fname( inp ) ); + pt = xmalloc( sizeof *pt + strlen(s) - 1 ); + pt->namelen = strlen(s); + memcpy(pt->name, s, pt->namelen ); + xfree(s); + } + else { /* no filename */ + pt = xmalloc( sizeof *pt - 1 ); + pt->namelen = 0; + } + } + + if (!iobuf_is_pipe_filename (filename) && *filename && !opt.textmode ) + { + off_t tmpsize; + int overflow; + + if ( !(tmpsize = iobuf_get_filelength(inp, &overflow)) + && !overflow ) + log_info(_("WARNING: `%s' is an empty file\n"), filename ); + /* We can't encode the length of very large files because + OpenPGP uses only 32 bit for file sizes. So if the the + size of a file is larger than 2^32 minus some bytes for + packet headers, we switch to partial length encoding. */ + if (tmpsize < (IOBUF_FILELENGTH_LIMIT - 65536) ) + filesize = tmpsize; + else + filesize = 0; + } + else + filesize = opt.set_filesize ? opt.set_filesize : 0; /* stdin */ + + if (!opt.no_literal) { + pt->timestamp = make_timestamp(); + pt->mode = opt.textmode ? 't' : 'b'; + pt->len = filesize; + pt->new_ctb = !pt->len && !RFC1991; + pt->buf = inp; + pkt.pkttype = PKT_PLAINTEXT; + pkt.pkt.plaintext = pt; + cfx.datalen = filesize && !do_compress? calc_packet_length( &pkt ) : 0; + } + else + cfx.datalen = filesize && !do_compress ? filesize : 0; + + /* register the cipher filter */ + iobuf_push_filter( out, cipher_filter, &cfx ); + + /* register the compress filter */ + if( do_compress ) { + int compr_algo = opt.compress_algo; + + if(compr_algo==-1) + { + if((compr_algo= + select_algo_from_prefs(pk_list,PREFTYPE_ZIP,-1,NULL))==-1) + compr_algo=DEFAULT_COMPRESS_ALGO; + /* Theoretically impossible to get here since uncompressed + is implicit. */ + } + else if(!opt.expert && + select_algo_from_prefs(pk_list,PREFTYPE_ZIP, + compr_algo,NULL)!=compr_algo) + log_info(_("WARNING: forcing compression algorithm %s (%d)" + " violates recipient preferences\n"), + compress_algo_to_string(compr_algo),compr_algo); + + /* algo 0 means no compression */ + if( compr_algo ) + { + if (cfx.dek && cfx.dek->use_mdc) + zfx.new_ctb = 1; + push_compress_filter(out,&zfx,compr_algo); + } + } + + /* do the work */ + if (!opt.no_literal) { + if( (rc = build_packet( out, &pkt )) ) + log_error("build_packet failed: %s\n", g10_errstr(rc) ); + } + else { + /* user requested not to create a literal packet, so we copy + the plain data */ + byte copy_buffer[4096]; + int bytes_copied; + while ((bytes_copied = iobuf_read(inp, copy_buffer, 4096)) != -1) + if (iobuf_write(out, copy_buffer, bytes_copied) == -1) { + rc = G10ERR_WRITE_FILE; + log_error("copying input to output failed: %s\n", + g10_errstr(rc) ); + break; + } + wipememory(copy_buffer, 4096); /* burn buffer */ + } + + /* finish the stuff */ + leave: + iobuf_close(inp); + if( rc ) + iobuf_cancel(out); + else { + iobuf_close(out); /* fixme: check returncode */ + write_status( STATUS_END_ENCRYPTION ); + } + if( pt ) + pt->buf = NULL; + free_packet(&pkt); + xfree(cfx.dek); + xfree(symkey_dek); + xfree(symkey_s2k); + release_pk_list( pk_list ); + return rc; +} + + + + +/**************** + * Filter to do a complete public key encryption. + */ +int +encrypt_filter( void *opaque, int control, + IOBUF a, byte *buf, size_t *ret_len) +{ + size_t size = *ret_len; + encrypt_filter_context_t *efx = opaque; + int rc=0; + + if( control == IOBUFCTRL_UNDERFLOW ) { /* decrypt */ + BUG(); /* not used */ + } + else if( control == IOBUFCTRL_FLUSH ) { /* encrypt */ + if( !efx->header_okay ) { + efx->cfx.dek = xmalloc_secure_clear( sizeof *efx->cfx.dek ); + + if( !opt.def_cipher_algo ) { /* try to get it from the prefs */ + efx->cfx.dek->algo = + select_algo_from_prefs(efx->pk_list,PREFTYPE_SYM,-1,NULL); + if( efx->cfx.dek->algo == -1 ) { + /* because 3DES is implicitly in the prefs, this can only + * happen if we do not have any public keys in the list */ + efx->cfx.dek->algo = DEFAULT_CIPHER_ALGO; + } + } + else { + if(!opt.expert && + select_algo_from_prefs(efx->pk_list,PREFTYPE_SYM, + opt.def_cipher_algo, + NULL)!=opt.def_cipher_algo) + log_info(_("forcing symmetric cipher %s (%d) " + "violates recipient preferences\n"), + cipher_algo_to_string(opt.def_cipher_algo), + opt.def_cipher_algo); + + efx->cfx.dek->algo = opt.def_cipher_algo; + } + + efx->cfx.dek->use_mdc = use_mdc(efx->pk_list,efx->cfx.dek->algo); + + make_session_key( efx->cfx.dek ); + if( DBG_CIPHER ) + log_hexdump("DEK is: ", + efx->cfx.dek->key, efx->cfx.dek->keylen ); + + rc = write_pubkey_enc_from_list( efx->pk_list, efx->cfx.dek, a ); + if( rc ) + return rc; + + if(efx->symkey_s2k && efx->symkey_dek) + { + rc=write_symkey_enc(efx->symkey_s2k,efx->symkey_dek, + efx->cfx.dek,a); + if(rc) + return rc; + } + + iobuf_push_filter( a, cipher_filter, &efx->cfx ); + + efx->header_okay = 1; + } + rc = iobuf_write( a, buf, size ); + + } + else if( control == IOBUFCTRL_FREE ) + { + xfree(efx->symkey_dek); + xfree(efx->symkey_s2k); + } + else if( control == IOBUFCTRL_DESC ) { + *(char**)buf = "encrypt_filter"; + } + return rc; +} + + +/**************** + * Write pubkey-enc packets from the list of PKs to OUT. + */ +static int +write_pubkey_enc_from_list( PK_LIST pk_list, DEK *dek, IOBUF out ) +{ + PACKET pkt; + PKT_public_key *pk; + PKT_pubkey_enc *enc; + int rc; + + for( ; pk_list; pk_list = pk_list->next ) { + MPI frame; + + pk = pk_list->pk; + + print_pubkey_algo_note( pk->pubkey_algo ); + enc = xmalloc_clear( sizeof *enc ); + enc->pubkey_algo = pk->pubkey_algo; + keyid_from_pk( pk, enc->keyid ); + enc->throw_keyid = (opt.throw_keyid || (pk_list->flags&1)); + + if(opt.throw_keyid && (PGP2 || PGP6 || PGP7 || PGP8)) + { + log_info(_("you may not use %s while in %s mode\n"), + "--throw-keyid",compliance_option_string()); + compliance_failure(); + } + + /* Okay, what's going on: We have the session key somewhere in + * the structure DEK and want to encode this session key in + * an integer value of n bits. pubkey_nbits gives us the + * number of bits we have to use. We then encode the session + * key in some way and we get it back in the big intger value + * FRAME. Then we use FRAME, the public key PK->PKEY and the + * algorithm number PK->PUBKEY_ALGO and pass it to pubkey_encrypt + * which returns the encrypted value in the array ENC->DATA. + * This array has a size which depends on the used algorithm + * (e.g. 2 for Elgamal). We don't need frame anymore because we + * have everything now in enc->data which is the passed to + * build_packet() + */ + frame = encode_session_key( dek, pubkey_nbits( pk->pubkey_algo, + pk->pkey ) ); + rc = pubkey_encrypt( pk->pubkey_algo, enc->data, frame, pk->pkey ); + mpi_free( frame ); + if( rc ) + log_error("pubkey_encrypt failed: %s\n", g10_errstr(rc) ); + else { + if( opt.verbose ) { + char *ustr = get_user_id_string_native (enc->keyid); + log_info(_("%s/%s encrypted for: \"%s\"\n"), + pubkey_algo_to_string(enc->pubkey_algo), + cipher_algo_to_string(dek->algo), ustr ); + xfree(ustr); + } + /* and write it */ + init_packet(&pkt); + pkt.pkttype = PKT_PUBKEY_ENC; + pkt.pkt.pubkey_enc = enc; + rc = build_packet( out, &pkt ); + if( rc ) + log_error("build_packet(pubkey_enc) failed: %s\n", g10_errstr(rc)); + } + free_pubkey_enc(enc); + if( rc ) + return rc; + } + return 0; +} + +void +encode_crypt_files(int nfiles, char **files, STRLIST remusr) +{ + int rc = 0; + + if (opt.outfile) + { + log_error(_("--output doesn't work for this command\n")); + return; + } + + if (!nfiles) + { + char line[2048]; + unsigned int lno = 0; + while ( fgets(line, DIM(line), stdin) ) + { + lno++; + if (!*line || line[strlen(line)-1] != '\n') + { + log_error("input line %u too long or missing LF\n", lno); + return; + } + line[strlen(line)-1] = '\0'; + print_file_status(STATUS_FILE_START, line, 2); + if ( (rc = encode_crypt(line, remusr, 0)) ) + log_error("encryption of `%s' failed: %s\n", + print_fname_stdin(line), g10_errstr(rc) ); + write_status( STATUS_FILE_DONE ); + iobuf_ioctl( NULL, 2, 0, NULL); /* Invalidate entire cache. */ + } + } + else + { + while (nfiles--) + { + print_file_status(STATUS_FILE_START, *files, 2); + if ( (rc = encode_crypt(*files, remusr, 0)) ) + log_error("encryption of `%s' failed: %s\n", + print_fname_stdin(*files), g10_errstr(rc) ); + write_status( STATUS_FILE_DONE ); + iobuf_ioctl( NULL, 2, 0, NULL); /* Invalidate entire cache. */ + files++; + } + } +} diff --git a/g10/encr-data.c b/g10/encr-data.c new file mode 100644 index 0000000..acb090a --- /dev/null +++ b/g10/encr-data.c @@ -0,0 +1,320 @@ +/* encr-data.c - process an encrypted data packet + * Copyright (C) 1998, 1999, 2000, 2001, 2005, + * 2006 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include "util.h" +#include "memory.h" +#include "packet.h" +#include "mpi.h" +#include "cipher.h" +#include "options.h" +#include "i18n.h" + + +static int mdc_decode_filter( void *opaque, int control, IOBUF a, + byte *buf, size_t *ret_len); +static int decode_filter( void *opaque, int control, IOBUF a, + byte *buf, size_t *ret_len); + +typedef struct { + CIPHER_HANDLE cipher_hd; + MD_HANDLE mdc_hash; + char defer[22]; + int defer_filled; + int eof_seen; + int refcount; +} *decode_filter_ctx_t; + + +/* Helper to release the decode context. */ +static void +release_dfx_context (decode_filter_ctx_t dfx) +{ + if (!dfx) + return; + + assert (dfx->refcount); + if ( !--dfx->refcount ) + { + cipher_close (dfx->cipher_hd); + dfx->cipher_hd = NULL; + md_close (dfx->mdc_hash); + dfx->mdc_hash = NULL; + xfree (dfx); + } +} + + +/**************** + * Decrypt the data, specified by ED with the key DEK. + */ +int +decrypt_data( void *procctx, PKT_encrypted *ed, DEK *dek ) +{ + decode_filter_ctx_t dfx; + byte *p; + int rc=0, c, i; + byte temp[32]; + unsigned blocksize; + unsigned nprefix; + + + dfx = xcalloc (1, sizeof *dfx); + dfx->refcount = 1; + + if( opt.verbose && !dek->algo_info_printed ) { + const char *s = cipher_algo_to_string( dek->algo ); + if( s ) + log_info(_("%s encrypted data\n"), s ); + else + log_info(_("encrypted with unknown algorithm %d\n"), dek->algo ); + dek->algo_info_printed = 1; + } + if( (rc=check_cipher_algo(dek->algo)) ) + goto leave; + blocksize = cipher_get_blocksize(dek->algo); + if( !blocksize || blocksize > 16 ) + log_fatal("unsupported blocksize %u\n", blocksize ); + nprefix = blocksize; + if( ed->len && ed->len < (nprefix+2) ) + BUG(); + + if( ed->mdc_method ) { + dfx->mdc_hash = md_open ( ed->mdc_method, 0 ); + if ( DBG_HASHING ) + md_start_debug (dfx->mdc_hash, "checkmdc"); + } + dfx->cipher_hd = cipher_open ( dek->algo, + ed->mdc_method? CIPHER_MODE_CFB + : CIPHER_MODE_AUTO_CFB, 1 ); + /* log_hexdump( "thekey", dek->key, dek->keylen );*/ + rc = cipher_setkey ( dfx->cipher_hd, dek->key, dek->keylen ); + if( rc == G10ERR_WEAK_KEY ) + { + log_info(_("WARNING: message was encrypted with" + " a weak key in the symmetric cipher.\n")); + rc=0; + } + else if( rc ) + { + log_error("key setup failed: %s\n", g10_errstr(rc) ); + goto leave; + + } + if (!ed->buf) { + log_error(_("problem handling encrypted packet\n")); + goto leave; + } + + cipher_setiv ( dfx->cipher_hd, NULL, 0 ); + + if( ed->len ) { + for(i=0; i < (nprefix+2) && ed->len; i++, ed->len-- ) { + if( (c=iobuf_get(ed->buf)) == -1 ) + break; + else + temp[i] = c; + } + } + else { + for(i=0; i < (nprefix+2); i++ ) + if( (c=iobuf_get(ed->buf)) == -1 ) + break; + else + temp[i] = c; + } + cipher_decrypt ( dfx->cipher_hd, temp, temp, nprefix+2); + cipher_sync ( dfx->cipher_hd ); + p = temp; +/* log_hexdump( "prefix", temp, nprefix+2 ); */ + if(dek->symmetric + && (p[nprefix-2] != p[nprefix] || p[nprefix-1] != p[nprefix+1]) ) + { + rc = G10ERR_BAD_KEY; + goto leave; + } + + if ( dfx->mdc_hash ) + md_write ( dfx->mdc_hash, temp, nprefix+2 ); + + dfx->refcount++; + if ( ed->mdc_method ) + iobuf_push_filter( ed->buf, mdc_decode_filter, dfx ); + else + iobuf_push_filter( ed->buf, decode_filter, dfx ); + + proc_packets( procctx, ed->buf ); + ed->buf = NULL; + if( ed->mdc_method && dfx->eof_seen == 2 ) + rc = G10ERR_INVALID_PACKET; + else if( ed->mdc_method ) { /* check the mdc */ + /* We used to let parse-packet.c handle the MDC packet but + this turned out to be a problem with compressed packets: + With old style packets there is no length information + available and the decompressor uses an implicit end. + However we can't know this implicit end beforehand (:-) and + thus may feed the decompressor with more bytes than + actually needed. It would be possible to unread the extra + bytes but due to our weird iobuf system any unread is non + reliable due to filters already popped off. The easy and + sane solution is to care about the MDC packet only here and + never pass it to the packet parser. Fortunatley the + OpenPGP spec requires a strict format for the MDC packet so + that we know that 22 bytes are appended. */ + int datalen = md_digest_length( ed->mdc_method ); + + assert (dfx->cipher_hd); + assert (dfx->mdc_hash); + cipher_decrypt ( dfx->cipher_hd, dfx->defer, dfx->defer, 22); + md_write ( dfx->mdc_hash, dfx->defer, 2); + md_final ( dfx->mdc_hash ); + if (dfx->defer[0] != '\xd3' || dfx->defer[1] != '\x14' ) { + log_error("mdc_packet with invalid encoding\n"); + rc = G10ERR_INVALID_PACKET; + } + else if ( datalen != 20 + || memcmp(md_read( dfx->mdc_hash, 0 ), dfx->defer+2, datalen) ) + rc = G10ERR_BAD_SIGN; + /*log_hexdump("MDC calculated:",md_read( dfx->mdc_hash, 0), datalen);*/ + /*log_hexdump("MDC message :", dfx->defer, 20);*/ + } + + + leave: + release_dfx_context (dfx); + return rc; +} + + + +/* I think we should merge this with cipher_filter */ +static int +mdc_decode_filter( void *opaque, int control, IOBUF a, + byte *buf, size_t *ret_len) +{ + decode_filter_ctx_t dfx = opaque; + size_t n, size = *ret_len; + int rc = 0; + int c; + + if( control == IOBUFCTRL_UNDERFLOW && dfx->eof_seen ) { + *ret_len = 0; + rc = -1; + } + else if( control == IOBUFCTRL_UNDERFLOW ) { + assert(a); + assert( size > 44 ); + + /* get at least 20 bytes and put it somewhere ahead in the buffer */ + for(n=22; n < 44 ; n++ ) { + if( (c = iobuf_get(a)) == -1 ) + break; + buf[n] = c; + } + if( n == 44 ) { + /* we have enough stuff - flush the deferred stuff */ + /* (we have asserted that the buffer is large enough) */ + if( !dfx->defer_filled ) { /* the first time */ + memcpy(buf, buf+22, 22 ); + n = 22; + } + else { + memcpy(buf, dfx->defer, 22 ); + } + /* now fill up */ + for(; n < size; n++ ) { + if( (c = iobuf_get(a)) == -1 ) + break; + buf[n] = c; + } + /* Move the last 22 bytes back to the defer buffer. */ + /* (okay, we are wasting 22 bytes of supplied buffer) */ + n -= 22; + memcpy( dfx->defer, buf+n, 22 ); + dfx->defer_filled = 1; + } + else if( !dfx->defer_filled ) { /* eof seen buf empty defer */ + /* this is bad because there is an incomplete hash */ + n -= 22; + memcpy(buf, buf+22, n ); + dfx->eof_seen = 2; /* eof with incomplete hash */ + } + else { /* eof seen */ + memcpy (buf, dfx->defer, 22 ); + n -= 22; + memcpy( dfx->defer, buf+n, 22 ); + dfx->eof_seen = 1; /* normal eof */ + } + + if( n ) { + if (dfx->cipher_hd) + cipher_decrypt( dfx->cipher_hd, buf, buf, n); + if (dfx->mdc_hash) + md_write( dfx->mdc_hash, buf, n ); + } + else { + assert( dfx->eof_seen ); + rc = -1; /* eof */ + } + *ret_len = n; + } + else if ( control == IOBUFCTRL_FREE ) { + release_dfx_context (dfx); + } + else if( control == IOBUFCTRL_DESC ) { + *(char**)buf = "mdc_decode_filter"; + } + return rc; +} + +static int +decode_filter( void *opaque, int control, IOBUF a, byte *buf, size_t *ret_len) +{ + decode_filter_ctx_t fc = opaque; + size_t n, size = *ret_len; + int rc = 0; + + if( control == IOBUFCTRL_UNDERFLOW ) { + assert(a); + n = iobuf_read( a, buf, size ); + if( n == -1 ) n = 0; + if( n ) { + if (fc->cipher_hd) + cipher_decrypt( fc->cipher_hd, buf, buf, n); + } + else + rc = -1; /* eof */ + *ret_len = n; + } + else if ( control == IOBUFCTRL_FREE ) { + release_dfx_context (fc); + } + else if( control == IOBUFCTRL_DESC ) { + *(char**)buf = "decode_filter"; + } + return rc; +} + diff --git a/g10/exec.c b/g10/exec.c new file mode 100644 index 0000000..d99bb5e --- /dev/null +++ b/g10/exec.c @@ -0,0 +1,626 @@ +/* exec.c - generic call-a-program code + * Copyright (C) 2001, 2002, 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 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#include <config.h> +#include <stdlib.h> +#include <stdarg.h> +#include <stdio.h> +#include <sys/stat.h> +#include <sys/types.h> +#ifndef EXEC_TEMPFILE_ONLY +#include <sys/wait.h> +#endif +#ifdef HAVE_DOSISH_SYSTEM +#include <windows.h> +#endif +#include <fcntl.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include "options.h" +#include "memory.h" +#include "i18n.h" +#include "iobuf.h" +#include "util.h" +#include "exec.h" + +#ifdef NO_EXEC +int exec_write(struct exec_info **info,const char *program, + const char *args_in,const char *name,int writeonly,int binary) +{ + log_error(_("no remote program execution supported\n")); + return G10ERR_GENERAL; +} + +int exec_read(struct exec_info *info) { return G10ERR_GENERAL; } +int exec_finish(struct exec_info *info) { return G10ERR_GENERAL; } +int set_exec_path(const char *path) { return G10ERR_GENERAL; } + +#else /* ! NO_EXEC */ + +#ifndef HAVE_MKDTEMP +char *mkdtemp(char *template); +#endif + +#if defined (_WIN32) +/* This is a nicer system() for windows that waits for programs to + return before returning control to the caller. I hate helpful + computers. */ +static int win_system(const char *command) +{ + PROCESS_INFORMATION pi; + STARTUPINFO si; + char *string; + + /* We must use a copy of the command as CreateProcess modifies this + argument. */ + string=xstrdup(command); + + memset(&pi,0,sizeof(pi)); + memset(&si,0,sizeof(si)); + si.cb=sizeof(si); + + if(!CreateProcess(NULL,string,NULL,NULL,FALSE,0,NULL,NULL,&si,&pi)) + return -1; + + /* Wait for the child to exit */ + WaitForSingleObject(pi.hProcess,INFINITE); + + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + xfree(string); + + return 0; +} +#endif + +/* Replaces current $PATH */ +int set_exec_path(const char *path) +{ + char *p; + + p=xmalloc(5+strlen(path)+1); + strcpy(p,"PATH="); + strcat(p,path); + + if(DBG_EXTPROG) + log_debug("set_exec_path: %s\n",p); + + /* Notice that path is never freed. That is intentional due to the + way putenv() works. This leaks a few bytes if we call + set_exec_path multiple times. */ + + if(putenv(p)!=0) + return G10ERR_GENERAL; + else + return 0; +} + +/* Makes a temp directory and filenames */ +static int make_tempdir(struct exec_info *info) +{ + char *tmp=opt.temp_dir,*namein=info->name,*nameout; + + if(!namein) + namein=info->flags.binary?"tempin" EXTSEP_S "bin":"tempin" EXTSEP_S "txt"; + + nameout=info->flags.binary?"tempout" EXTSEP_S "bin":"tempout" EXTSEP_S "txt"; + + /* Make up the temp dir and files in case we need them */ + + if(tmp==NULL) + { +#if defined (_WIN32) + int err; + + tmp=xmalloc(MAX_PATH); + err=GetTempPath(MAX_PATH,tmp); + if(err==0 || err>MAX_PATH) + strcpy(tmp,"c:\\windows\\temp"); + else + { + int len=strlen(tmp); + + /* GetTempPath may return with \ on the end */ + while(len>0 && tmp[len-1]=='\\') + { + tmp[len-1]='\0'; + len--; + } + } +#else /* More unixish systems */ + tmp=getenv("TMPDIR"); + if(tmp==NULL) + { + tmp=getenv("TMP"); + if(tmp==NULL) + { +#ifdef __riscos__ + tmp="<Wimp$ScrapDir>.GnuPG"; + mkdir(tmp,0700); /* Error checks occur later on */ +#else + tmp="/tmp"; +#endif + } + } +#endif + } + + info->tempdir=xmalloc(strlen(tmp)+strlen(DIRSEP_S)+10+1); + + sprintf(info->tempdir,"%s" DIRSEP_S "gpg-XXXXXX",tmp); + +#if defined (_WIN32) + xfree(tmp); +#endif + + if(mkdtemp(info->tempdir)==NULL) + log_error(_("can't create directory `%s': %s\n"), + info->tempdir,strerror(errno)); + else + { + info->flags.madedir=1; + + info->tempfile_in=xmalloc(strlen(info->tempdir)+ + strlen(DIRSEP_S)+strlen(namein)+1); + sprintf(info->tempfile_in,"%s" DIRSEP_S "%s",info->tempdir,namein); + + if(!info->flags.writeonly) + { + info->tempfile_out=xmalloc(strlen(info->tempdir)+ + strlen(DIRSEP_S)+strlen(nameout)+1); + sprintf(info->tempfile_out,"%s" DIRSEP_S "%s",info->tempdir,nameout); + } + } + + return info->flags.madedir?0:G10ERR_GENERAL; +} + +/* Expands %i and %o in the args to the full temp files within the + temp directory. */ +static int expand_args(struct exec_info *info,const char *args_in) +{ + const char *ch=args_in; + unsigned int size,len; + + info->flags.use_temp_files=0; + info->flags.keep_temp_files=0; + + if(DBG_EXTPROG) + log_debug("expanding string \"%s\"\n",args_in); + + size=100; + info->command=xmalloc(size); + len=0; + info->command[0]='\0'; + + while(*ch!='\0') + { + if(*ch=='%') + { + char *append=NULL; + + ch++; + + switch(*ch) + { + case 'O': + info->flags.keep_temp_files=1; + /* fall through */ + + case 'o': /* out */ + if(!info->flags.madedir) + { + if(make_tempdir(info)) + goto fail; + } + append=info->tempfile_out; + info->flags.use_temp_files=1; + break; + + case 'I': + info->flags.keep_temp_files=1; + /* fall through */ + + case 'i': /* in */ + if(!info->flags.madedir) + { + if(make_tempdir(info)) + goto fail; + } + append=info->tempfile_in; + info->flags.use_temp_files=1; + break; + + case '%': + append="%"; + break; + } + + if(append) + { + size_t applen=strlen(append); + + if(applen+len>size-1) + { + if(applen<100) + applen=100; + + size+=applen; + info->command=xrealloc(info->command,size); + } + + strcat(info->command,append); + len+=strlen(append); + } + } + else + { + if(len==size-1) /* leave room for the \0 */ + { + size+=100; + info->command=xrealloc(info->command,size); + } + + info->command[len++]=*ch; + info->command[len]='\0'; + } + + ch++; + } + + if(DBG_EXTPROG) + log_debug("args expanded to \"%s\", use %u, keep %u\n",info->command, + info->flags.use_temp_files,info->flags.keep_temp_files); + + return 0; + + fail: + + xfree(info->command); + info->command=NULL; + + return G10ERR_GENERAL; +} + +/* Either handles the tempfile creation, or the fork/exec. If it + returns ok, then info->tochild is a FILE * that can be written to. + The rules are: if there are no args, then it's a fork/exec/pipe. + If there are args, but no tempfiles, then it's a fork/exec/pipe via + shell -c. If there are tempfiles, then it's a system. */ + +int exec_write(struct exec_info **info,const char *program, + const char *args_in,const char *name,int writeonly,int binary) +{ + int ret=G10ERR_GENERAL; + + if(opt.exec_disable && !opt.no_perm_warn) + { + log_info(_("external program calls are disabled due to unsafe " + "options file permissions\n")); + + return ret; + } + +#if defined(HAVE_GETUID) && defined(HAVE_GETEUID) + /* There should be no way to get to this spot while still carrying + setuid privs. Just in case, bomb out if we are. */ + if(getuid()!=geteuid()) + BUG(); +#endif + + if(program==NULL && args_in==NULL) + BUG(); + + *info=xmalloc_clear(sizeof(struct exec_info)); + + if(name) + (*info)->name=xstrdup(name); + (*info)->flags.binary=binary; + (*info)->flags.writeonly=writeonly; + + /* Expand the args, if any */ + if(args_in && expand_args(*info,args_in)) + goto fail; + +#ifdef EXEC_TEMPFILE_ONLY + if(!(*info)->flags.use_temp_files) + { + log_error(_("this platform requires temporary files when calling" + " external programs\n")); + goto fail; + } + +#else /* !EXEC_TEMPFILE_ONLY */ + + /* If there are no args, or there are args, but no temp files, we + can use fork/exec/pipe */ + if(args_in==NULL || (*info)->flags.use_temp_files==0) + { + int to[2],from[2]; + + if(pipe(to)==-1) + goto fail; + + if(pipe(from)==-1) + { + close(to[0]); + close(to[1]); + goto fail; + } + + if(((*info)->child=fork())==-1) + { + close(to[0]); + close(to[1]); + close(from[0]); + close(from[1]); + goto fail; + } + + if((*info)->child==0) + { + char *shell=getenv("SHELL"); + + if(shell==NULL) + shell="/bin/sh"; + + /* I'm the child */ + + /* If the program isn't going to respond back, they get to + keep their stdout/stderr */ + if(!(*info)->flags.writeonly) + { + /* implied close of STDERR */ + if(dup2(STDOUT_FILENO,STDERR_FILENO)==-1) + _exit(1); + + /* implied close of STDOUT */ + close(from[0]); + if(dup2(from[1],STDOUT_FILENO)==-1) + _exit(1); + } + + /* implied close of STDIN */ + close(to[1]); + if(dup2(to[0],STDIN_FILENO)==-1) + _exit(1); + + if(args_in==NULL) + { + if(DBG_EXTPROG) + log_debug("execlp: %s\n",program); + + execlp(program,program,(void *)NULL); + } + else + { + if(DBG_EXTPROG) + log_debug("execlp: %s -c %s\n",shell,(*info)->command); + + execlp(shell,shell,"-c",(*info)->command,(void *)NULL); + } + + /* If we get this far the exec failed. Clean up and return. */ + + if(args_in==NULL) + log_error(_("unable to execute program `%s': %s\n"), + program,strerror(errno)); + else + log_error(_("unable to execute shell `%s': %s\n"), + shell,strerror(errno)); + + /* This mimics the POSIX sh behavior - 127 means "not found" + from the shell. */ + if(errno==ENOENT) + _exit(127); + + _exit(1); + } + + /* I'm the parent */ + + close(to[0]); + + (*info)->tochild=fdopen(to[1],binary?"wb":"w"); + if((*info)->tochild==NULL) + { + close(to[1]); + ret=G10ERR_WRITE_FILE; + goto fail; + } + + close(from[1]); + + (*info)->fromchild=iobuf_fdopen(from[0],"r"); + if((*info)->fromchild==NULL) + { + close(from[0]); + ret=G10ERR_READ_FILE; + goto fail; + } + + /* fd iobufs are cached?! */ + iobuf_ioctl((*info)->fromchild,3,1,NULL); + + return 0; + } +#endif /* !EXEC_TEMPFILE_ONLY */ + + if(DBG_EXTPROG) + log_debug("using temp file `%s'\n",(*info)->tempfile_in); + + /* It's not fork/exec/pipe, so create a temp file */ + if( is_secured_filename ((*info)->tempfile_in) ) + { + (*info)->tochild = NULL; + errno = EPERM; + } + else + (*info)->tochild=fopen((*info)->tempfile_in,binary?"wb":"w"); + if((*info)->tochild==NULL) + { + log_error(_("can't create `%s': %s\n"), + (*info)->tempfile_in,strerror(errno)); + ret=G10ERR_WRITE_FILE; + goto fail; + } + + ret=0; + + fail: + return ret; +} + +int exec_read(struct exec_info *info) +{ + int ret=G10ERR_GENERAL; + + fclose(info->tochild); + info->tochild=NULL; + + if(info->flags.use_temp_files) + { + if(DBG_EXTPROG) + log_debug("system() command is %s\n",info->command); + +#if defined (_WIN32) + info->progreturn=win_system(info->command); +#else + info->progreturn=system(info->command); +#endif + + if(info->progreturn==-1) + { + log_error(_("system error while calling external program: %s\n"), + strerror(errno)); + info->progreturn=127; + goto fail; + } + +#if defined(WIFEXITED) && defined(WEXITSTATUS) + if(WIFEXITED(info->progreturn)) + info->progreturn=WEXITSTATUS(info->progreturn); + else + { + log_error(_("unnatural exit of external program\n")); + info->progreturn=127; + goto fail; + } +#else + /* If we don't have the macros, do the best we can. */ + info->progreturn = (info->progreturn & 0xff00) >> 8; +#endif + + /* 127 is the magic value returned from system() to indicate + that the shell could not be executed, or from /bin/sh to + indicate that the program could not be executed. */ + + if(info->progreturn==127) + { + log_error(_("unable to execute external program\n")); + goto fail; + } + + if(!info->flags.writeonly) + { + info->fromchild=iobuf_open(info->tempfile_out); + if (info->fromchild + && is_secured_file (iobuf_get_fd (info->fromchild))) + { + iobuf_close (info->fromchild); + info->fromchild = NULL; + errno = EPERM; + } + if(info->fromchild==NULL) + { + log_error(_("unable to read external program response: %s\n"), + strerror(errno)); + ret=G10ERR_READ_FILE; + goto fail; + } + + /* Do not cache this iobuf on close */ + iobuf_ioctl(info->fromchild,3,1,NULL); + } + } + + ret=0; + + fail: + return ret; +} + +int exec_finish(struct exec_info *info) +{ + int ret=info->progreturn; + + if(info->fromchild) + iobuf_close(info->fromchild); + + if(info->tochild) + fclose(info->tochild); + +#ifndef EXEC_TEMPFILE_ONLY + if(info->child>0) + { + if(waitpid(info->child,&info->progreturn,0)!=0 && + WIFEXITED(info->progreturn)) + ret=WEXITSTATUS(info->progreturn); + else + { + log_error(_("unnatural exit of external program\n")); + ret=127; + } + } +#endif + + if(info->flags.madedir && !info->flags.keep_temp_files) + { + if(info->tempfile_in) + { + if(unlink(info->tempfile_in)==-1) + log_info(_("WARNING: unable to remove tempfile (%s) `%s': %s\n"), + "in",info->tempfile_in,strerror(errno)); + } + + if(info->tempfile_out) + { + if(unlink(info->tempfile_out)==-1) + log_info(_("WARNING: unable to remove tempfile (%s) `%s': %s\n"), + "out",info->tempfile_out,strerror(errno)); + } + + if(rmdir(info->tempdir)==-1) + log_info(_("WARNING: unable to remove temp directory `%s': %s\n"), + info->tempdir,strerror(errno)); + } + + xfree(info->command); + xfree(info->name); + xfree(info->tempdir); + xfree(info->tempfile_in); + xfree(info->tempfile_out); + xfree(info); + + return ret; +} +#endif /* ! NO_EXEC */ diff --git a/g10/exec.h b/g10/exec.h new file mode 100644 index 0000000..b4a6f0e --- /dev/null +++ b/g10/exec.h @@ -0,0 +1,52 @@ +/* exec.h + * Copyright (C) 2001, 2002, 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 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#ifndef _EXEC_H_ +#define _EXEC_H_ + +#include <unistd.h> +#include <stdio.h> +#include "iobuf.h" + +struct exec_info +{ + int progreturn; + struct + { + unsigned int binary:1; + unsigned int writeonly:1; + unsigned int madedir:1; + unsigned int use_temp_files:1; + unsigned int keep_temp_files:1; + } flags; + pid_t child; + FILE *tochild; + IOBUF fromchild; + char *command,*name,*tempdir,*tempfile_in,*tempfile_out; +}; + +int exec_write(struct exec_info **info,const char *program, + const char *args_in,const char *name,int writeonly,int binary); +int exec_read(struct exec_info *info); +int exec_finish(struct exec_info *info); +int set_exec_path(const char *path); + +#endif /* !_EXEC_H_ */ diff --git a/g10/export.c b/g10/export.c new file mode 100644 index 0000000..626b7d0 --- /dev/null +++ b/g10/export.c @@ -0,0 +1,600 @@ +/* export.c + * Copyright (C) 1998, 1999, 2000, 2001, 2002, 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 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <assert.h> + +#include "options.h" +#include "packet.h" +#include "errors.h" +#include "keydb.h" +#include "memory.h" +#include "util.h" +#include "main.h" +#include "i18n.h" +#include "trustdb.h" + + +/* An object to keep track of subkeys. */ +struct subkey_list_s +{ + struct subkey_list_s *next; + u32 kid[2]; +}; +typedef struct subkey_list_s *subkey_list_t; + + +static int do_export( STRLIST users, int secret, unsigned int options ); +static int do_export_stream( IOBUF out, STRLIST users, int secret, + KBNODE *keyblock_out, unsigned int options, + int *any ); + +int +parse_export_options(char *str,unsigned int *options,int noisy) +{ + struct parse_options export_opts[]= + { + {"export-local-sigs",EXPORT_LOCAL_SIGS,NULL, + N_("export signatures that are marked as local-only")}, + {"export-attributes",EXPORT_ATTRIBUTES,NULL, + N_("export attribute user IDs (generally photo IDs)")}, + {"export-sensitive-revkeys",EXPORT_SENSITIVE_REVKEYS,NULL, + N_("export revocation keys marked as \"sensitive\"")}, + {"export-reset-subkey-passwd",EXPORT_RESET_SUBKEY_PASSWD,NULL, + N_("remove the passphrase from exported subkeys")}, + {"export-clean",EXPORT_CLEAN,NULL, + N_("remove unusable parts from key during export")}, + {"export-minimal",EXPORT_MINIMAL|EXPORT_CLEAN,NULL, + N_("remove as much as possible from key during export")}, + /* Aliases for backward compatibility */ + {"include-local-sigs",EXPORT_LOCAL_SIGS,NULL,NULL}, + {"include-attributes",EXPORT_ATTRIBUTES,NULL,NULL}, + {"include-sensitive-revkeys",EXPORT_SENSITIVE_REVKEYS,NULL,NULL}, + /* dummy */ + {"export-unusable-sigs",0,NULL,NULL}, + {"export-clean-sigs",0,NULL,NULL}, + {"export-clean-uids",0,NULL,NULL}, + {NULL,0,NULL,NULL} + /* add tags for include revoked and disabled? */ + }; + + return parse_options(str,options,export_opts,noisy); +} + + +/**************** + * Export the public keys (to standard out or --output). + * Depending on opt.armor the output is armored. + * options are defined in main.h. + * If USERS is NULL, the complete ring will be exported. */ +int +export_pubkeys( STRLIST users, unsigned int options ) +{ + return do_export( users, 0, options ); +} + +/**************** + * Export to an already opened stream; return -1 if no keys have + * been exported + */ +int +export_pubkeys_stream( IOBUF out, STRLIST users, + KBNODE *keyblock_out, unsigned int options ) +{ + int any, rc; + + rc = do_export_stream( out, users, 0, keyblock_out, options, &any ); + if( !rc && !any ) + rc = -1; + return rc; +} + +int +export_seckeys( STRLIST users ) +{ + return do_export( users, 1, 0 ); +} + +int +export_secsubkeys( STRLIST users ) +{ + return do_export( users, 2, 0 ); +} + +static int +do_export( STRLIST users, int secret, unsigned int options ) +{ + IOBUF out = NULL; + int any, rc; + armor_filter_context_t afx; + compress_filter_context_t zfx; + + memset( &afx, 0, sizeof afx); + memset( &zfx, 0, sizeof zfx); + + rc = open_outfile( NULL, 0, &out ); + if( rc ) + return rc; + + if( opt.armor ) { + afx.what = secret?5:1; + iobuf_push_filter( out, armor_filter, &afx ); + } + if( opt.compress_keys ) + push_compress_filter(out,&zfx,default_compress_algo()); + + rc = do_export_stream( out, users, secret, NULL, options, &any ); + if( rc || !any ) + iobuf_cancel(out); + else + iobuf_close(out); + return rc; +} + + + +/* Release an entire subkey list. */ +static void +release_subkey_list (subkey_list_t list) +{ + while (list) + { + subkey_list_t tmp = list->next;; + xfree (list); + list = tmp; + } +} + + +/* Returns true if NODE is a subkey and contained in LIST. */ +static int +subkey_in_list_p (subkey_list_t list, KBNODE node) +{ + if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY + || node->pkt->pkttype == PKT_SECRET_SUBKEY ) + { + u32 kid[2]; + + if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY) + keyid_from_pk (node->pkt->pkt.public_key, kid); + else + keyid_from_sk (node->pkt->pkt.secret_key, kid); + + for (; list; list = list->next) + if (list->kid[0] == kid[0] && list->kid[1] == kid[1]) + return 1; + } + return 0; +} + +/* Allocate a new subkey list item from NODE. */ +static subkey_list_t +new_subkey_list_item (KBNODE node) +{ + subkey_list_t list = xcalloc (1, sizeof *list); + + if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY) + keyid_from_pk (node->pkt->pkt.public_key, list->kid); + else if (node->pkt->pkttype == PKT_SECRET_SUBKEY) + keyid_from_sk (node->pkt->pkt.secret_key, list->kid); + + return list; +} + + +/* Helper function to check whether the subkey at NODE actually + matches the description at DESC. The function returns true if the + key under question has been specified by an exact specification + (keyID or fingerprint) and does match the one at NODE. It is + assumed that the packet at NODE is either a public or secret + subkey. */ +static int +exact_subkey_match_p (KEYDB_SEARCH_DESC *desc, KBNODE node) +{ + u32 kid[2]; + byte fpr[MAX_FINGERPRINT_LEN]; + size_t fprlen; + int result = 0; + + switch(desc->mode) + { + case KEYDB_SEARCH_MODE_SHORT_KID: + case KEYDB_SEARCH_MODE_LONG_KID: + if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY) + keyid_from_pk (node->pkt->pkt.public_key, kid); + else + keyid_from_sk (node->pkt->pkt.secret_key, kid); + break; + + case KEYDB_SEARCH_MODE_FPR16: + case KEYDB_SEARCH_MODE_FPR20: + case KEYDB_SEARCH_MODE_FPR: + if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY) + fingerprint_from_pk (node->pkt->pkt.public_key, fpr,&fprlen); + else + fingerprint_from_sk (node->pkt->pkt.secret_key, fpr,&fprlen); + break; + + default: + break; + } + + switch(desc->mode) + { + case KEYDB_SEARCH_MODE_SHORT_KID: + if (desc->u.kid[1] == kid[1]) + result = 1; + break; + + case KEYDB_SEARCH_MODE_LONG_KID: + if (desc->u.kid[0] == kid[0] && desc->u.kid[1] == kid[1]) + result = 1; + break; + + case KEYDB_SEARCH_MODE_FPR16: + if (!memcmp (desc->u.fpr, fpr, 16)) + result = 1; + break; + + case KEYDB_SEARCH_MODE_FPR20: + case KEYDB_SEARCH_MODE_FPR: + if (!memcmp (desc->u.fpr, fpr, 20)) + result = 1; + break; + + default: + break; + } + + return result; +} + + +/* If keyblock_out is non-NULL, AND the exit code is zero, then it + contains a pointer to the first keyblock found and exported. No + other keyblocks are exported. The caller must free it. */ +static int +do_export_stream( IOBUF out, STRLIST users, int secret, + KBNODE *keyblock_out, unsigned int options, int *any ) +{ + int rc = 0; + PACKET pkt; + KBNODE keyblock = NULL; + KBNODE kbctx, node; + size_t ndesc, descindex; + KEYDB_SEARCH_DESC *desc = NULL; + subkey_list_t subkey_list = NULL; /* Track alreay processed subkeys. */ + KEYDB_HANDLE kdbhd; + STRLIST sl; + + *any = 0; + init_packet( &pkt ); + kdbhd = keydb_new (secret); + + if (!users) { + ndesc = 1; + desc = xcalloc ( ndesc, sizeof *desc ); + desc[0].mode = KEYDB_SEARCH_MODE_FIRST; + } + else { + for (ndesc=0, sl=users; sl; sl = sl->next, ndesc++) + ; + desc = xmalloc ( ndesc * sizeof *desc); + + for (ndesc=0, sl=users; sl; sl = sl->next) { + if (classify_user_id (sl->d, desc+ndesc)) + ndesc++; + else + log_error (_("key \"%s\" not found: %s\n"), + sl->d, g10_errstr (G10ERR_INV_USER_ID)); + } + + /* It would be nice to see which of the given users did + actually match one in the keyring. To implement this we + need to have a found flag for each entry in desc and to set + this we must check all those entries after a match to mark + all matched one - currently we stop at the first match. To + do this we need an extra flag to enable this feature so */ + } + +#ifdef ENABLE_SELINUX_HACKS + if (secret) { + log_error (_("exporting secret keys not allowed\n")); + rc = G10ERR_GENERAL; + goto leave; + } +#endif + + while (!(rc = keydb_search2 (kdbhd, desc, ndesc, &descindex))) { + int sha1_warned=0,skip_until_subkey=0; + u32 sk_keyid[2]; + + if (!users) + desc[0].mode = KEYDB_SEARCH_MODE_NEXT; + + /* Read the keyblock. */ + rc = keydb_get_keyblock (kdbhd, &keyblock ); + if( rc ) { + log_error (_("error reading keyblock: %s\n"), g10_errstr(rc) ); + goto leave; + } + + if((node=find_kbnode(keyblock,PKT_SECRET_KEY))) + { + PKT_secret_key *sk=node->pkt->pkt.secret_key; + + keyid_from_sk(sk,sk_keyid); + + /* We can't apply GNU mode 1001 on an unprotected key. */ + if( secret == 2 && !sk->is_protected ) + { + log_info(_("key %s: not protected - skipped\n"), + keystr(sk_keyid)); + continue; + } + + /* No v3 keys with GNU mode 1001. */ + if( secret == 2 && sk->version == 3 ) + { + log_info(_("key %s: PGP 2.x style key - skipped\n"), + keystr(sk_keyid)); + continue; + } + + /* It does not make sense to export a key with a primary + key on card using a non-key stub. We simply skip those + keys when used with --export-secret-subkeys. */ + if (secret == 2 && sk->is_protected + && sk->protect.s2k.mode == 1002 ) + { + log_info(_("key %s: key material on-card - skipped\n"), + keystr(sk_keyid)); + continue; + } + } + else + { + /* It's a public key export, so do the cleaning if + requested. Note that both export-clean and + export-minimal only apply to UID sigs (0x10, 0x11, + 0x12, and 0x13). A designated revocation is never + stripped, even with export-minimal set. */ + + if(options&EXPORT_CLEAN) + clean_key(keyblock,opt.verbose,options&EXPORT_MINIMAL,NULL,NULL); + } + + /* And write it. */ + for( kbctx=NULL; (node = walk_kbnode( keyblock, &kbctx, 0 )); ) { + if( skip_until_subkey ) + { + if(node->pkt->pkttype==PKT_PUBLIC_SUBKEY + || node->pkt->pkttype==PKT_SECRET_SUBKEY) + skip_until_subkey=0; + else + continue; + } + + /* We used to use comment packets, but not any longer. In + case we still have comments on a key, strip them here + before we call build_packet(). */ + if( node->pkt->pkttype == PKT_COMMENT ) + continue; + + /* Make sure that ring_trust packets never get exported. */ + if (node->pkt->pkttype == PKT_RING_TRUST) + continue; + + /* If exact is set, then we only export what was requested + (plus the primary key, if the user didn't specifically + request it). */ + if(desc[descindex].exact + && (node->pkt->pkttype==PKT_PUBLIC_SUBKEY + || node->pkt->pkttype==PKT_SECRET_SUBKEY)) + { + if (!exact_subkey_match_p (desc+descindex, node)) + { + /* Before skipping this subkey, check whether any + other description wants an exact match on a + subkey and include that subkey into the output + too. Need to add this subkey to a list so that + it won't get processed a second time. + + So the first step here is to check that list and + skip in any case if the key is in that list. + + We need this whole mess because the import + function is not able to merge secret keys and + thus it is useless to output them as two + separate keys and have import merge them. */ + if (subkey_in_list_p (subkey_list, node)) + skip_until_subkey = 1; /* Already processed this one. */ + else + { + size_t j; + + for (j=0; j < ndesc; j++) + if (j != descindex && desc[j].exact + && exact_subkey_match_p (desc+j, node)) + break; + if (!(j < ndesc)) + skip_until_subkey = 1; /* No other one matching. */ + } + } + + if(skip_until_subkey) + continue; + + /* Mark this one as processed. */ + { + subkey_list_t tmp = new_subkey_list_item (node); + tmp->next = subkey_list; + subkey_list = tmp; + } + } + + if(node->pkt->pkttype==PKT_SIGNATURE) + { + /* do not export packets which are marked as not + exportable */ + if(!(options&EXPORT_LOCAL_SIGS) + && !node->pkt->pkt.signature->flags.exportable) + continue; /* not exportable */ + + /* Do not export packets with a "sensitive" revocation + key unless the user wants us to. Note that we do + export these when issuing the actual revocation + (see revoke.c). */ + if(!(options&EXPORT_SENSITIVE_REVKEYS) + && node->pkt->pkt.signature->revkey) + { + int i; + + for(i=0;i<node->pkt->pkt.signature->numrevkeys;i++) + if(node->pkt->pkt.signature->revkey[i]->class & 0x40) + break; + + if(i<node->pkt->pkt.signature->numrevkeys) + continue; + } + } + + /* Don't export attribs? */ + if( !(options&EXPORT_ATTRIBUTES) && + node->pkt->pkttype == PKT_USER_ID && + node->pkt->pkt.user_id->attrib_data ) { + /* Skip until we get to something that is not an attrib + or a signature on an attrib */ + while(kbctx->next && kbctx->next->pkt->pkttype==PKT_SIGNATURE) { + kbctx=kbctx->next; + } + + continue; + } + + if( secret == 2 && node->pkt->pkttype == PKT_SECRET_KEY ) + { + /* We don't want to export the secret parts of the + * primary key, this is done by using GNU protection mode 1001 + */ + int save_mode = node->pkt->pkt.secret_key->protect.s2k.mode; + node->pkt->pkt.secret_key->protect.s2k.mode = 1001; + rc = build_packet( out, node->pkt ); + node->pkt->pkt.secret_key->protect.s2k.mode = save_mode; + } + else if (secret == 2 && node->pkt->pkttype == PKT_SECRET_SUBKEY + && (opt.export_options&EXPORT_RESET_SUBKEY_PASSWD)) + { + /* If the subkey is protected reset the passphrase to + export an unprotected subkey. This feature is + useful in cases of a subkey copied to an unattended + machine where a passphrase is not required. */ + PKT_secret_key *sk_save, *sk; + + sk_save = node->pkt->pkt.secret_key; + sk = copy_secret_key (NULL, sk_save); + node->pkt->pkt.secret_key = sk; + + log_info (_("about to export an unprotected subkey\n")); + switch (is_secret_key_protected (sk)) + { + case -1: + rc = G10ERR_PUBKEY_ALGO; + break; + case 0: + break; + default: + if (sk->protect.s2k.mode == 1001) + ; /* No secret parts. */ + else if( sk->protect.s2k.mode == 1002 ) + ; /* Card key stub. */ + else + { + rc = check_secret_key( sk, 0 ); + } + break; + } + if (rc) + { + node->pkt->pkt.secret_key = sk_save; + free_secret_key (sk); + log_error (_("failed to unprotect the subkey: %s\n"), + g10_errstr (rc)); + goto leave; + } + + rc = build_packet (out, node->pkt); + + node->pkt->pkt.secret_key = sk_save; + free_secret_key (sk); + } + else + { + /* Warn the user if the secret key or any of the secret + subkeys are protected with SHA1 and we have + simple_sk_checksum set. */ + if(!sha1_warned && opt.simple_sk_checksum && + (node->pkt->pkttype==PKT_SECRET_KEY || + node->pkt->pkttype==PKT_SECRET_SUBKEY) && + node->pkt->pkt.secret_key->protect.sha1chk) + { + /* I hope this warning doesn't confuse people. */ + log_info(_("WARNING: secret key %s does not have a " + "simple SK checksum\n"),keystr(sk_keyid)); + + sha1_warned=1; + } + + rc = build_packet( out, node->pkt ); + } + + if( rc ) { + log_error("build_packet(%d) failed: %s\n", + node->pkt->pkttype, g10_errstr(rc) ); + rc = G10ERR_WRITE_FILE; + goto leave; + } + } + ++*any; + if(keyblock_out) + { + *keyblock_out=keyblock; + break; + } + } + if( rc == -1 ) + rc = 0; + + leave: + release_subkey_list (subkey_list); + xfree(desc); + keydb_release (kdbhd); + if(rc || keyblock_out==NULL) + release_kbnode( keyblock ); + if( !*any ) + log_info(_("WARNING: nothing exported\n")); + return rc; +} diff --git a/g10/filter.h b/g10/filter.h new file mode 100644 index 0000000..c9fe872 --- /dev/null +++ b/g10/filter.h @@ -0,0 +1,167 @@ +/* filter.h + * Copyright (C) 1998, 1999, 2000, 2001, 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 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ +#ifndef G10_FILTER_H +#define G10_FILTER_H + +#include "types.h" +#include "cipher.h" + +typedef struct { + MD_HANDLE md; /* catch all */ + MD_HANDLE md2; /* if we want to calculate an alternate hash */ + size_t maxbuf_size; +} md_filter_context_t; + +typedef struct { + int refcount; /* Reference counter. If 0 this structure + is not allocated on the heap. */ + + /* these fields may be initialized */ + int what; /* what kind of armor headers to write */ + int only_keyblocks; /* skip all headers but ".... key block" */ + const char *hdrlines; /* write these headerlines */ + + /* these fileds must be initialized to zero */ + int no_openpgp_data; /* output flag: "No valid OpenPGP data found" */ + + /* the following fields must be initialized to zero */ + int inp_checked; /* set if the input has been checked */ + int inp_bypass; /* set if the input is not armored */ + int in_cleartext; /* clear text message */ + int not_dash_escaped; /* clear text is not dash escaped */ + int hashes; /* detected hash algorithms */ + int faked; /* we are faking a literal data packet */ + int truncated; /* number of truncated lines */ + int qp_detected; + int pgp2mode; + byte eol[3]; /* The end of line characters as a + zero-terminated string. Defaults + (eol[0]=='\0') to whatever the local + platform uses. */ + + byte *buffer; /* malloced buffer */ + unsigned buffer_size; /* and size of this buffer */ + unsigned buffer_len; /* used length of the buffer */ + unsigned buffer_pos; /* read position */ + + byte radbuf[4]; + int idx, idx2; + u32 crc; + + int status; /* an internal state flag */ + int cancel; + int any_data; /* any valid armored data seen */ + int pending_lf; /* used together with faked */ +} armor_filter_context_t; + +struct unarmor_pump_s; +typedef struct unarmor_pump_s *UnarmorPump; + + +struct compress_filter_context_s { + int status; + void *opaque; /* (used for z_stream) */ + byte *inbuf; + unsigned inbufsize; + byte *outbuf; + unsigned outbufsize; + int algo; /* compress algo */ + int algo1hack; + int new_ctb; + void (*release)(struct compress_filter_context_s*); +}; +typedef struct compress_filter_context_s compress_filter_context_t; + + +typedef struct { + DEK *dek; + u32 datalen; + CIPHER_HANDLE cipher_hd; + int header; + MD_HANDLE mdc_hash; + byte enchash[20]; + int create_mdc; /* flag will be set by the cipher filter */ +} cipher_filter_context_t; + + + +typedef struct { + byte *buffer; /* malloced buffer */ + unsigned buffer_size; /* and size of this buffer */ + unsigned buffer_len; /* used length of the buffer */ + unsigned buffer_pos; /* read position */ + int truncated; /* number of truncated lines */ + int not_dash_escaped; + int escape_from; + MD_HANDLE md; + int pending_lf; + int pending_esc; +} text_filter_context_t; + + +typedef struct { + char *what; /* description */ + u32 last_time; /* last time reported */ + unsigned long last; /* last amount reported */ + unsigned long offset; /* current amount */ + unsigned long total; /* total amount */ +} progress_filter_context_t; + +/* encrypt_filter_context_t defined in main.h */ + +/*-- mdfilter.c --*/ +int md_filter( void *opaque, int control, IOBUF a, byte *buf, size_t *ret_len); +void free_md_filter_context( md_filter_context_t *mfx ); + +/*-- armor.c --*/ +armor_filter_context_t *new_armor_context (void); +void release_armor_context (armor_filter_context_t *afx); +int push_armor_filter (armor_filter_context_t *afx, IOBUF iobuf); +int use_armor_filter( IOBUF a ); +int armor_filter( void *opaque, int control, + IOBUF chain, byte *buf, size_t *ret_len); +UnarmorPump unarmor_pump_new (void); +void unarmor_pump_release (UnarmorPump x); +int unarmor_pump (UnarmorPump x, int c); + +/*-- compress.c --*/ +void push_compress_filter(IOBUF out,compress_filter_context_t *zfx,int algo); +void push_compress_filter2(IOBUF out,compress_filter_context_t *zfx, + int algo,int rel); + +/*-- cipher.c --*/ +int cipher_filter( void *opaque, int control, + IOBUF chain, byte *buf, size_t *ret_len); + +/*-- textfilter.c --*/ +int text_filter( void *opaque, int control, + IOBUF chain, byte *buf, size_t *ret_len); +int copy_clearsig_text( IOBUF out, IOBUF inp, MD_HANDLE md, + int escape_dash, int escape_from, int pgp2mode ); + +/*-- progress.c --*/ +int progress_filter (void *opaque, int control, + IOBUF a, byte *buf, size_t *ret_len); +void handle_progress (progress_filter_context_t *pfx, + IOBUF inp, const char *name); + +#endif /*G10_FILTER_H*/ diff --git a/g10/free-packet.c b/g10/free-packet.c new file mode 100644 index 0000000..be49bb5 --- /dev/null +++ b/g10/free-packet.c @@ -0,0 +1,569 @@ +/* free-packet.c - cleanup stuff for packets + * Copyright (C) 1998, 1999, 2000, 2001, 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 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> + +#include "packet.h" +#include "iobuf.h" +#include "mpi.h" +#include "util.h" +#include "cipher.h" +#include "memory.h" +#include "options.h" + +void +free_symkey_enc( PKT_symkey_enc *enc ) +{ + xfree(enc); +} + +void +free_pubkey_enc( PKT_pubkey_enc *enc ) +{ + int n, i; + n = pubkey_get_nenc( enc->pubkey_algo ); + if( !n ) + mpi_free(enc->data[0]); + for(i=0; i < n; i++ ) + mpi_free( enc->data[i] ); + xfree(enc); +} + +void +free_seckey_enc( PKT_signature *sig ) +{ + int n, i; + + n = pubkey_get_nsig( sig->pubkey_algo ); + if( !n ) + mpi_free(sig->data[0]); + for(i=0; i < n; i++ ) + mpi_free( sig->data[i] ); + + xfree(sig->revkey); + xfree(sig->hashed); + xfree(sig->unhashed); + + if (sig->pka_info) + { + xfree (sig->pka_info->uri); + xfree (sig->pka_info); + } + + xfree(sig); +} + + +void +release_public_key_parts( PKT_public_key *pk ) +{ + int n, i; + n = pubkey_get_npkey( pk->pubkey_algo ); + if( !n ) + mpi_free(pk->pkey[0]); + for(i=0; i < n; i++ ) { + mpi_free( pk->pkey[i] ); + pk->pkey[i] = NULL; + } + if (pk->prefs) { + xfree (pk->prefs); + pk->prefs = NULL; + } + if (pk->user_id) { + free_user_id (pk->user_id); + pk->user_id = NULL; + } + if (pk->revkey) { + xfree(pk->revkey); + pk->revkey=NULL; + pk->numrevkeys=0; + } +} + + +void +free_public_key( PKT_public_key *pk ) +{ + release_public_key_parts( pk ); + xfree(pk); +} + + +static subpktarea_t * +cp_subpktarea (subpktarea_t *s ) +{ + subpktarea_t *d; + + if( !s ) + return NULL; + d = xmalloc (sizeof (*d) + s->size - 1 ); + d->size = s->size; + d->len = s->len; + memcpy (d->data, s->data, s->len); + return d; +} + +/* + * Return a copy of the preferences + */ +prefitem_t * +copy_prefs (const prefitem_t *prefs) +{ + size_t n; + prefitem_t *new; + + if (!prefs) + return NULL; + + for (n=0; prefs[n].type; n++) + ; + new = xmalloc ( sizeof (*new) * (n+1)); + for (n=0; prefs[n].type; n++) { + new[n].type = prefs[n].type; + new[n].value = prefs[n].value; + } + new[n].type = PREFTYPE_NONE; + new[n].value = 0; + + return new; +} + + +PKT_public_key * +copy_public_key ( PKT_public_key *d, PKT_public_key *s) +{ + int n, i; + + if( !d ) + d = xmalloc(sizeof *d); + memcpy( d, s, sizeof *d ); + d->user_id = scopy_user_id (s->user_id); + d->prefs = copy_prefs (s->prefs); + n = pubkey_get_npkey( s->pubkey_algo ); + if( !n ) + d->pkey[0] = mpi_copy(s->pkey[0]); + else { + for(i=0; i < n; i++ ) + d->pkey[i] = mpi_copy( s->pkey[i] ); + } + if( !s->revkey && s->numrevkeys ) + BUG(); + if( s->numrevkeys ) { + d->revkey = xmalloc(sizeof(struct revocation_key)*s->numrevkeys); + memcpy(d->revkey,s->revkey,sizeof(struct revocation_key)*s->numrevkeys); + } + else + d->revkey = NULL; + return d; +} + +/**************** + * Replace all common parts of a sk by the one from the public key. + * This is a hack and a better solution will be to just store the real secret + * parts somewhere and don't duplicate all the other stuff. + */ +void +copy_public_parts_to_secret_key( PKT_public_key *pk, PKT_secret_key *sk ) +{ + sk->expiredate = pk->expiredate; + sk->pubkey_algo = pk->pubkey_algo; + sk->pubkey_usage= pk->pubkey_usage; + sk->req_usage = pk->req_usage; + sk->req_algo = pk->req_algo; + sk->has_expired = pk->has_expired; + sk->is_revoked = pk->is_revoked; + sk->is_valid = pk->is_valid; + sk->main_keyid[0]= pk->main_keyid[0]; + sk->main_keyid[1]= pk->main_keyid[1]; + sk->keyid[0] = pk->keyid[0]; + sk->keyid[1] = pk->keyid[1]; +} + + +static pka_info_t * +cp_pka_info (const pka_info_t *s) +{ + pka_info_t *d = xmalloc (sizeof *s + strlen (s->email)); + + d->valid = s->valid; + d->checked = s->checked; + d->uri = s->uri? xstrdup (s->uri):NULL; + memcpy (d->fpr, s->fpr, sizeof s->fpr); + strcpy (d->email, s->email); + return d; +} + + +PKT_signature * +copy_signature( PKT_signature *d, PKT_signature *s ) +{ + int n, i; + + if( !d ) + d = xmalloc(sizeof *d); + memcpy( d, s, sizeof *d ); + n = pubkey_get_nsig( s->pubkey_algo ); + if( !n ) + d->data[0] = mpi_copy(s->data[0]); + else { + for(i=0; i < n; i++ ) + d->data[i] = mpi_copy( s->data[i] ); + } + d->pka_info = s->pka_info? cp_pka_info (s->pka_info) : NULL; + d->hashed = cp_subpktarea (s->hashed); + d->unhashed = cp_subpktarea (s->unhashed); + if(s->numrevkeys) + { + d->revkey=NULL; + d->numrevkeys=0; + parse_revkeys(d); + } + return d; +} + + +/* + * shallow copy of the user ID + */ +PKT_user_id * +scopy_user_id (PKT_user_id *s) +{ + if (s) + s->ref++; + return s; +} + + + +void +release_secret_key_parts( PKT_secret_key *sk ) +{ + int n, i; + + n = pubkey_get_nskey( sk->pubkey_algo ); + if( !n ) + mpi_free(sk->skey[0]); + for(i=0; i < n; i++ ) { + mpi_free( sk->skey[i] ); + sk->skey[i] = NULL; + } +} + +void +free_secret_key( PKT_secret_key *sk ) +{ + release_secret_key_parts( sk ); + xfree(sk); +} + +PKT_secret_key * +copy_secret_key( PKT_secret_key *d, PKT_secret_key *s ) +{ + int n, i; + + if( !d ) + d = xmalloc_secure(sizeof *d); + else + release_secret_key_parts (d); + memcpy( d, s, sizeof *d ); + n = pubkey_get_nskey( s->pubkey_algo ); + if( !n ) + d->skey[0] = mpi_copy(s->skey[0]); + else { + for(i=0; i < n; i++ ) + d->skey[i] = mpi_copy( s->skey[i] ); + } + + return d; +} + +void +free_comment( PKT_comment *rem ) +{ + xfree(rem); +} + +void +free_attributes(PKT_user_id *uid) +{ + xfree(uid->attribs); + xfree(uid->attrib_data); + + uid->attribs=NULL; + uid->attrib_data=NULL; + uid->attrib_len=0; +} + +void +free_user_id (PKT_user_id *uid) +{ + assert (uid->ref > 0); + if (--uid->ref) + return; + + free_attributes(uid); + xfree (uid->prefs); + xfree (uid->namehash); + xfree (uid); +} + +void +free_compressed( PKT_compressed *zd ) +{ + if( zd->buf ) { /* have to skip some bytes */ + /* don't have any information about the length, so + * we assume this is the last packet */ + while( iobuf_read( zd->buf, NULL, 1<<30 ) != -1 ) + ; + } + xfree(zd); +} + +void +free_encrypted( PKT_encrypted *ed ) +{ + if( ed->buf ) { /* have to skip some bytes */ + if( ed->is_partial ) { + while( iobuf_read( ed->buf, NULL, 1<<30 ) != -1 ) + ; + } + else { + while( ed->len ) { /* skip the packet */ + int n = iobuf_read( ed->buf, NULL, ed->len ); + if( n == -1 ) + ed->len = 0; + else + ed->len -= n; + } + } + } + xfree(ed); +} + + +void +free_plaintext( PKT_plaintext *pt ) +{ + if( pt->buf ) { /* have to skip some bytes */ + if( pt->is_partial ) { + while( iobuf_read( pt->buf, NULL, 1<<30 ) != -1 ) + ; + } + else { + while( pt->len ) { /* skip the packet */ + int n = iobuf_read( pt->buf, NULL, pt->len ); + if( n == -1 ) + pt->len = 0; + else + pt->len -= n; + } + } + } + xfree(pt); +} + +/**************** + * Free the packet in pkt. + */ +void +free_packet( PACKET *pkt ) +{ + if( !pkt || !pkt->pkt.generic ) + return; + + if( DBG_MEMORY ) + log_debug("free_packet() type=%d\n", pkt->pkttype ); + + switch( pkt->pkttype ) { + case PKT_SIGNATURE: + free_seckey_enc( pkt->pkt.signature ); + break; + case PKT_PUBKEY_ENC: + free_pubkey_enc( pkt->pkt.pubkey_enc ); + break; + case PKT_SYMKEY_ENC: + free_symkey_enc( pkt->pkt.symkey_enc ); + break; + case PKT_PUBLIC_KEY: + case PKT_PUBLIC_SUBKEY: + free_public_key( pkt->pkt.public_key ); + break; + case PKT_SECRET_KEY: + case PKT_SECRET_SUBKEY: + free_secret_key( pkt->pkt.secret_key ); + break; + case PKT_COMMENT: + free_comment( pkt->pkt.comment ); + break; + case PKT_USER_ID: + free_user_id( pkt->pkt.user_id ); + break; + case PKT_COMPRESSED: + free_compressed( pkt->pkt.compressed); + break; + case PKT_ENCRYPTED: + case PKT_ENCRYPTED_MDC: + free_encrypted( pkt->pkt.encrypted ); + break; + case PKT_PLAINTEXT: + free_plaintext( pkt->pkt.plaintext ); + break; + default: + xfree( pkt->pkt.generic ); + break; + } + pkt->pkt.generic = NULL; +} + +/**************** + * returns 0 if they match. + */ +int +cmp_public_keys( PKT_public_key *a, PKT_public_key *b ) +{ + int n, i; + + if( a->timestamp != b->timestamp ) + return -1; + if( a->version < 4 && a->expiredate != b->expiredate ) + return -1; + if( a->pubkey_algo != b->pubkey_algo ) + return -1; + + n = pubkey_get_npkey( b->pubkey_algo ); + if( !n ) + return -1; /* can't compare due to unknown algorithm */ + for(i=0; i < n; i++ ) { + if( mpi_cmp( a->pkey[i], b->pkey[i] ) ) + return -1; + } + + return 0; +} + +/**************** + * Returns 0 if they match. + * We only compare the public parts. + */ +int +cmp_secret_keys( PKT_secret_key *a, PKT_secret_key *b ) +{ + int n, i; + + if( a->timestamp != b->timestamp ) + return -1; + if( a->version < 4 && a->expiredate != b->expiredate ) + return -1; + if( a->pubkey_algo != b->pubkey_algo ) + return -1; + + n = pubkey_get_npkey( b->pubkey_algo ); + if( !n ) + return -1; /* can't compare due to unknown algorithm */ + for(i=0; i < n; i++ ) { + if( mpi_cmp( a->skey[i], b->skey[i] ) ) + return -1; + } + + return 0; +} + +/**************** + * Returns 0 if they match. + */ +int +cmp_public_secret_key( PKT_public_key *pk, PKT_secret_key *sk ) +{ + int n, i; + + if( pk->timestamp != sk->timestamp ) + return -1; + if( pk->version < 4 && pk->expiredate != sk->expiredate ) + return -1; + if( pk->pubkey_algo != sk->pubkey_algo ) + return -1; + + n = pubkey_get_npkey( pk->pubkey_algo ); + if( !n ) + return -1; /* can't compare due to unknown algorithm */ + for(i=0; i < n; i++ ) { + if( mpi_cmp( pk->pkey[i] , sk->skey[i] ) ) + return -1; + } + return 0; +} + + + +int +cmp_signatures( PKT_signature *a, PKT_signature *b ) +{ + int n, i; + + if( a->keyid[0] != b->keyid[0] ) + return -1; + if( a->keyid[1] != b->keyid[1] ) + return -1; + if( a->pubkey_algo != b->pubkey_algo ) + return -1; + + n = pubkey_get_nsig( a->pubkey_algo ); + if( !n ) + return -1; /* can't compare due to unknown algorithm */ + for(i=0; i < n; i++ ) { + if( mpi_cmp( a->data[i] , b->data[i] ) ) + return -1; + } + return 0; +} + + +/**************** + * Returns: true if the user ids do not match + */ +int +cmp_user_ids( PKT_user_id *a, PKT_user_id *b ) +{ + int res=1; + + if( a == b ) + return 0; + + if( a->attrib_data && b->attrib_data ) + { + res = a->attrib_len - b->attrib_len; + if( !res ) + res = memcmp( a->attrib_data, b->attrib_data, a->attrib_len ); + } + else if( !a->attrib_data && !b->attrib_data ) + { + res = a->len - b->len; + if( !res ) + res = memcmp( a->name, b->name, a->len ); + } + + return res; +} diff --git a/g10/getkey.c b/g10/getkey.c new file mode 100644 index 0000000..74c0a74 --- /dev/null +++ b/g10/getkey.c @@ -0,0 +1,3003 @@ +/* getkey.c - Get a key from the database + * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, + * 2006 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <ctype.h> +#include "util.h" +#include "packet.h" +#include "memory.h" +#include "iobuf.h" +#include "keydb.h" +#include "options.h" +#include "main.h" +#include "trustdb.h" +#include "i18n.h" +#include "keyserver-internal.h" + +#define MAX_PK_CACHE_ENTRIES PK_UID_CACHE_SIZE +#define MAX_UID_CACHE_ENTRIES PK_UID_CACHE_SIZE + +#if MAX_PK_CACHE_ENTRIES < 2 +#error We need the cache for key creation +#endif + +struct getkey_ctx_s { + int exact; + KBNODE keyblock; + KBPOS kbpos; + KBNODE found_key; /* pointer into some keyblock */ + int last_rc; + int req_usage; + int req_algo; + KEYDB_HANDLE kr_handle; + int not_allocated; + int nitems; + KEYDB_SEARCH_DESC items[1]; +}; + +#if 0 +static struct { + int any; + int okay_count; + int nokey_count; + int error_count; +} lkup_stats[21]; +#endif + +typedef struct keyid_list { + struct keyid_list *next; + u32 keyid[2]; +} *keyid_list_t; + + +#if MAX_PK_CACHE_ENTRIES + typedef struct pk_cache_entry { + struct pk_cache_entry *next; + u32 keyid[2]; + PKT_public_key *pk; + } *pk_cache_entry_t; + static pk_cache_entry_t pk_cache; + static int pk_cache_entries; /* number of entries in pk cache */ + static int pk_cache_disabled; +#endif + +#if MAX_UID_CACHE_ENTRIES < 5 +#error we really need the userid cache +#endif +typedef struct user_id_db { + struct user_id_db *next; + keyid_list_t keyids; + int len; + char name[1]; +} *user_id_db_t; +static user_id_db_t user_id_db; +static int uid_cache_entries; /* number of entries in uid cache */ + +static void merge_selfsigs( KBNODE keyblock ); +static int lookup( GETKEY_CTX ctx, KBNODE *ret_keyblock, int secmode ); + +#if 0 +static void +print_stats() +{ + int i; + for(i=0; i < DIM(lkup_stats); i++ ) { + if( lkup_stats[i].any ) + fprintf(stderr, + "lookup stats: mode=%-2d ok=%-6d nokey=%-6d err=%-6d\n", + i, + lkup_stats[i].okay_count, + lkup_stats[i].nokey_count, + lkup_stats[i].error_count ); + } +} +#endif + + +void +cache_public_key( PKT_public_key *pk ) +{ +#if MAX_PK_CACHE_ENTRIES + pk_cache_entry_t ce; + u32 keyid[2]; + + if( pk_cache_disabled ) + return; + + if( pk->dont_cache ) + return; + + if( is_ELGAMAL(pk->pubkey_algo) + || pk->pubkey_algo == PUBKEY_ALGO_DSA + || is_RSA(pk->pubkey_algo) ) { + keyid_from_pk( pk, keyid ); + } + else + return; /* don't know how to get the keyid */ + + for( ce = pk_cache; ce; ce = ce->next ) + if( ce->keyid[0] == keyid[0] && ce->keyid[1] == keyid[1] ) { + if( DBG_CACHE ) + log_debug("cache_public_key: already in cache\n"); + return; + } + + if( pk_cache_entries >= MAX_PK_CACHE_ENTRIES ) { + /* fixme: use another algorithm to free some cache slots */ + pk_cache_disabled=1; + if( opt.verbose > 1 ) + log_info(_("too many entries in pk cache - disabled\n")); + return; + } + pk_cache_entries++; + ce = xmalloc( sizeof *ce ); + ce->next = pk_cache; + pk_cache = ce; + ce->pk = copy_public_key( NULL, pk ); + ce->keyid[0] = keyid[0]; + ce->keyid[1] = keyid[1]; +#endif +} + + +/* Return a const utf-8 string with the text "[User ID not found]". + This fucntion is required so that we don't need to switch gettext's + encoding temporary. */ +static const char * +user_id_not_found_utf8 (void) +{ + static char *text; + + if (!text) + text = native_to_utf8 (_("[User ID not found]")); + return text; +} + + + +/* + * Return the user ID from the given keyblock. + * We use the primary uid flag which has been set by the merge_selfsigs + * function. The returned value is only valid as long as then given + * keyblock is not changed + */ +static const char * +get_primary_uid ( KBNODE keyblock, size_t *uidlen ) +{ + KBNODE k; + const char *s; + + for (k=keyblock; k; k=k->next ) { + if ( k->pkt->pkttype == PKT_USER_ID + && !k->pkt->pkt.user_id->attrib_data + && k->pkt->pkt.user_id->is_primary ) { + *uidlen = k->pkt->pkt.user_id->len; + return k->pkt->pkt.user_id->name; + } + } + s = user_id_not_found_utf8 (); + *uidlen = strlen (s); + return s; +} + + +static void +release_keyid_list ( keyid_list_t k ) +{ + while ( k ) { + keyid_list_t k2 = k->next; + xfree (k); + k = k2; + } +} + +/**************** + * Store the association of keyid and userid + * Feed only public keys to this function. + */ +static void +cache_user_id( KBNODE keyblock ) +{ + user_id_db_t r; + const char *uid; + size_t uidlen; + keyid_list_t keyids = NULL; + KBNODE k; + + for (k=keyblock; k; k = k->next ) { + if ( k->pkt->pkttype == PKT_PUBLIC_KEY + || k->pkt->pkttype == PKT_PUBLIC_SUBKEY ) { + keyid_list_t a = xmalloc_clear ( sizeof *a ); + /* Hmmm: For a long list of keyids it might be an advantage + * to append the keys */ + keyid_from_pk( k->pkt->pkt.public_key, a->keyid ); + /* first check for duplicates */ + for(r=user_id_db; r; r = r->next ) { + keyid_list_t b = r->keyids; + for ( b = r->keyids; b; b = b->next ) { + if( b->keyid[0] == a->keyid[0] + && b->keyid[1] == a->keyid[1] ) { + if( DBG_CACHE ) + log_debug("cache_user_id: already in cache\n"); + release_keyid_list ( keyids ); + xfree ( a ); + return; + } + } + } + /* now put it into the cache */ + a->next = keyids; + keyids = a; + } + } + if ( !keyids ) + BUG (); /* No key no fun */ + + + uid = get_primary_uid ( keyblock, &uidlen ); + + if( uid_cache_entries >= MAX_UID_CACHE_ENTRIES ) { + /* fixme: use another algorithm to free some cache slots */ + r = user_id_db; + user_id_db = r->next; + release_keyid_list ( r->keyids ); + xfree(r); + uid_cache_entries--; + } + r = xmalloc( sizeof *r + uidlen-1 ); + r->keyids = keyids; + r->len = uidlen; + memcpy(r->name, uid, r->len); + r->next = user_id_db; + user_id_db = r; + uid_cache_entries++; +} + + +void +getkey_disable_caches() +{ +#if MAX_PK_CACHE_ENTRIES + { + pk_cache_entry_t ce, ce2; + + for( ce = pk_cache; ce; ce = ce2 ) { + ce2 = ce->next; + free_public_key( ce->pk ); + xfree( ce ); + } + pk_cache_disabled=1; + pk_cache_entries = 0; + pk_cache = NULL; + } +#endif + /* fixme: disable user id cache ? */ +} + + +static void +pk_from_block ( GETKEY_CTX ctx, PKT_public_key *pk, KBNODE keyblock ) +{ + KBNODE a = ctx->found_key ? ctx->found_key : keyblock; + + assert ( a->pkt->pkttype == PKT_PUBLIC_KEY + || a->pkt->pkttype == PKT_PUBLIC_SUBKEY ); + + copy_public_key ( pk, a->pkt->pkt.public_key ); +} + +static void +sk_from_block ( GETKEY_CTX ctx, + PKT_secret_key *sk, KBNODE keyblock ) +{ + KBNODE a = ctx->found_key ? ctx->found_key : keyblock; + + assert ( a->pkt->pkttype == PKT_SECRET_KEY + || a->pkt->pkttype == PKT_SECRET_SUBKEY ); + + copy_secret_key( sk, a->pkt->pkt.secret_key); +} + + +/**************** + * Get a public key and store it into the allocated pk + * can be called with PK set to NULL to just read it into some + * internal structures. + */ +int +get_pubkey( PKT_public_key *pk, u32 *keyid ) +{ + int internal = 0; + int rc = 0; + +#if MAX_PK_CACHE_ENTRIES + if(pk) + { + /* Try to get it from the cache. We don't do this when pk is + NULL as it does not guarantee that the user IDs are + cached. */ + pk_cache_entry_t ce; + for( ce = pk_cache; ce; ce = ce->next ) + { + if( ce->keyid[0] == keyid[0] && ce->keyid[1] == keyid[1] ) + { + copy_public_key( pk, ce->pk ); + return 0; + } + } + } +#endif + /* more init stuff */ + if( !pk ) { + pk = xmalloc_clear( sizeof *pk ); + internal++; + } + + + /* do a lookup */ + { struct getkey_ctx_s ctx; + KBNODE kb = NULL; + memset( &ctx, 0, sizeof ctx ); + ctx.exact = 1; /* use the key ID exactly as given */ + ctx.not_allocated = 1; + ctx.kr_handle = keydb_new (0); + ctx.nitems = 1; + ctx.items[0].mode = KEYDB_SEARCH_MODE_LONG_KID; + ctx.items[0].u.kid[0] = keyid[0]; + ctx.items[0].u.kid[1] = keyid[1]; + ctx.req_algo = pk->req_algo; + ctx.req_usage = pk->req_usage; + rc = lookup( &ctx, &kb, 0 ); + if ( !rc ) { + pk_from_block ( &ctx, pk, kb ); + } + get_pubkey_end( &ctx ); + release_kbnode ( kb ); + } + if( !rc ) + goto leave; + + rc = G10ERR_NO_PUBKEY; + + leave: + if( !rc ) + cache_public_key( pk ); + if( internal ) + free_public_key(pk); + return rc; +} + + +/* Get a public key and store it into the allocated pk. This function + differs from get_pubkey() in that it does not do a check of the key + to avoid recursion. It should be used only in very certain cases. + It will only retrieve primary keys. */ +int +get_pubkey_fast (PKT_public_key *pk, u32 *keyid) +{ + int rc = 0; + KEYDB_HANDLE hd; + KBNODE keyblock; + u32 pkid[2]; + + assert (pk); +#if MAX_PK_CACHE_ENTRIES + { /* Try to get it from the cache */ + pk_cache_entry_t ce; + + for (ce = pk_cache; ce; ce = ce->next) + { + if (ce->keyid[0] == keyid[0] && ce->keyid[1] == keyid[1]) + { + if (pk) + copy_public_key (pk, ce->pk); + return 0; + } + } + } +#endif + + hd = keydb_new (0); + rc = keydb_search_kid (hd, keyid); + if (rc == -1) + { + keydb_release (hd); + return G10ERR_NO_PUBKEY; + } + rc = keydb_get_keyblock (hd, &keyblock); + keydb_release (hd); + if (rc) + { + log_error ("keydb_get_keyblock failed: %s\n", g10_errstr(rc)); + return G10ERR_NO_PUBKEY; + } + + assert ( keyblock->pkt->pkttype == PKT_PUBLIC_KEY + || keyblock->pkt->pkttype == PKT_PUBLIC_SUBKEY ); + + keyid_from_pk(keyblock->pkt->pkt.public_key,pkid); + if(keyid[0]==pkid[0] && keyid[1]==pkid[1]) + copy_public_key (pk, keyblock->pkt->pkt.public_key ); + else + rc=G10ERR_NO_PUBKEY; + + release_kbnode (keyblock); + + /* Not caching key here since it won't have all of the fields + properly set. */ + + return rc; +} + + +KBNODE +get_pubkeyblock( u32 *keyid ) +{ + struct getkey_ctx_s ctx; + int rc = 0; + KBNODE keyblock = NULL; + + memset( &ctx, 0, sizeof ctx ); + /* no need to set exact here because we want the entire block */ + ctx.not_allocated = 1; + ctx.kr_handle = keydb_new (0); + ctx.nitems = 1; + ctx.items[0].mode = KEYDB_SEARCH_MODE_LONG_KID; + ctx.items[0].u.kid[0] = keyid[0]; + ctx.items[0].u.kid[1] = keyid[1]; + rc = lookup( &ctx, &keyblock, 0 ); + get_pubkey_end( &ctx ); + + return rc ? NULL : keyblock; +} + + + + +/**************** + * Get a secret key and store it into sk + */ +int +get_seckey( PKT_secret_key *sk, u32 *keyid ) +{ + int rc; + struct getkey_ctx_s ctx; + KBNODE kb = NULL; + + memset( &ctx, 0, sizeof ctx ); + ctx.exact = 1; /* use the key ID exactly as given */ + ctx.not_allocated = 1; + ctx.kr_handle = keydb_new (1); + ctx.nitems = 1; + ctx.items[0].mode = KEYDB_SEARCH_MODE_LONG_KID; + ctx.items[0].u.kid[0] = keyid[0]; + ctx.items[0].u.kid[1] = keyid[1]; + ctx.req_algo = sk->req_algo; + ctx.req_usage = sk->req_usage; + rc = lookup( &ctx, &kb, 1 ); + if ( !rc ) { + sk_from_block ( &ctx, sk, kb ); + } + get_seckey_end( &ctx ); + release_kbnode ( kb ); + + if( !rc ) { + /* check the secret key (this may prompt for a passprase to + * unlock the secret key + */ + rc = check_secret_key( sk, 0 ); + } + + return rc; +} + + +/**************** + * Check whether the secret key is available. This is just a fast + * check and does not tell us whether the secret key is valid. It + * merely tells other whether there is some secret key. + * Returns: 0 := key is available + * G10ERR_NO_SECKEY := not availabe + */ +int +seckey_available( u32 *keyid ) +{ + int rc; + KEYDB_HANDLE hd = keydb_new (1); + + rc = keydb_search_kid (hd, keyid); + if ( rc == -1 ) + rc = G10ERR_NO_SECKEY; + keydb_release (hd); + return rc; +} + + +/**************** + * Return the type of the user id: + * + * Please use the constants KEYDB_SERCH_MODE_xxx + * 0 = Invalid user ID + * 1 = exact match + * 2 = match a substring + * 3 = match an email address + * 4 = match a substring of an email address + * 5 = match an email address, but compare from end + * 6 = word match mode + * 10 = it is a short KEYID (don't care about keyid[0]) + * 11 = it is a long KEYID + * 12 = it is a trustdb index (keyid is looked up) + * 16 = it is a 16 byte fingerprint + * 20 = it is a 20 byte fingerprint + * 21 = Unified fingerprint :fpr:pk_algo: + * (We don't use pk_algo yet) + * + * Rules used: + * - If the username starts with 8,9,16 or 17 hex-digits (the first one + * must be in the range 0..9), this is considered a keyid; depending + * on the length a short or complete one. + * - If the username starts with 32,33,40 or 41 hex-digits (the first one + * must be in the range 0..9), this is considered a fingerprint. + * - If the username starts with a left angle, we assume it is a complete + * email address and look only at this part. + * - If the username starts with a colon we assume it is a unified + * key specfification. + * - If the username starts with a '.', we assume it is the ending + * part of an email address + * - If the username starts with an '@', we assume it is a part of an + * email address + * - If the userid start with an '=' an exact compare is done. + * - If the userid starts with a '*' a case insensitive substring search is + * done (This is the default). + * - If the userid starts with a '+' we will compare individual words + * and a match requires that all the words are in the userid. + * Words are delimited by white space or "()<>[]{}.@-+_,;/&!" + * (note that you can't search for these characters). Compare + * is not case sensitive. + */ + +int +classify_user_id( const char *name, KEYDB_SEARCH_DESC *desc ) +{ + const char *s; + int hexprefix = 0; + int hexlength; + int mode = 0; + KEYDB_SEARCH_DESC dummy_desc; + + if (!desc) + desc = &dummy_desc; + + /* clear the structure so that the mode field is set to zero unless + * we set it to the correct value right at the end of this function */ + memset (desc, 0, sizeof *desc); + + /* skip leading spaces. Fixme: what is with trailing spaces? */ + for(s = name; *s && spacep (s); s++ ) + ; + + switch (*s) { + case 0: /* empty string is an error */ + return 0; + +#if 0 + case '.': /* an email address, compare from end */ + mode = KEYDB_SEARCH_MODE_MAILEND; + s++; + desc->u.name = s; + break; +#endif + + case '<': /* an email address */ + mode = KEYDB_SEARCH_MODE_MAIL; + desc->u.name = s; + break; + + case '@': /* part of an email address */ + mode = KEYDB_SEARCH_MODE_MAILSUB; + s++; + desc->u.name = s; + break; + + case '=': /* exact compare */ + mode = KEYDB_SEARCH_MODE_EXACT; + s++; + desc->u.name = s; + break; + + case '*': /* case insensitive substring search */ + mode = KEYDB_SEARCH_MODE_SUBSTR; + s++; + desc->u.name = s; + break; + +#if 0 + case '+': /* compare individual words */ + mode = KEYDB_SEARCH_MODE_WORDS; + s++; + desc->u.name = s; + break; +#endif + + case '#': /* local user id */ + return 0; /* This is now obsolete and van't not be used anymore*/ + + case ':': /*Unified fingerprint */ + { + const char *se, *si; + int i; + + se = strchr( ++s,':'); + if ( !se ) + return 0; + for (i=0,si=s; si < se; si++, i++ ) { + if ( !strchr("01234567890abcdefABCDEF", *si ) ) + return 0; /* invalid digit */ + } + if (i != 32 && i != 40) + return 0; /* invalid length of fpr*/ + for (i=0,si=s; si < se; i++, si +=2) + desc->u.fpr[i] = hextobyte(si); + for ( ; i < 20; i++) + desc->u.fpr[i]= 0; + s = se + 1; + mode = KEYDB_SEARCH_MODE_FPR; + } + break; + + default: + if (s[0] == '0' && s[1] == 'x') { + hexprefix = 1; + s += 2; + } + + hexlength = strspn(s, "0123456789abcdefABCDEF"); + if (hexlength >= 8 && s[hexlength] =='!') { + desc->exact = 1; + hexlength++; /* just for the following check */ + } + + /* check if a hexadecimal number is terminated by EOS or blank */ + if (hexlength && s[hexlength] && !spacep(s+hexlength)) { + if (hexprefix) /* a "0x" prefix without correct */ + return 0; /* termination is an error */ + else /* The first chars looked like */ + hexlength = 0; /* a hex number, but really were not. */ + } + + if (desc->exact) + hexlength--; + + if (hexlength == 8 + || (!hexprefix && hexlength == 9 && *s == '0')){ + /* short keyid */ + if (hexlength == 9) + s++; + desc->u.kid[0] = 0; + desc->u.kid[1] = strtoul( s, NULL, 16 ); + mode = KEYDB_SEARCH_MODE_SHORT_KID; + } + else if (hexlength == 16 + || (!hexprefix && hexlength == 17 && *s == '0')) { + /* complete keyid */ + char buf[9]; + if (hexlength == 17) + s++; + mem2str(buf, s, 9 ); + desc->u.kid[0] = strtoul( buf, NULL, 16 ); + desc->u.kid[1] = strtoul( s+8, NULL, 16 ); + mode = KEYDB_SEARCH_MODE_LONG_KID; + } + else if (hexlength == 32 || (!hexprefix && hexlength == 33 + && *s == '0')) { + /* md5 fingerprint */ + int i; + if (hexlength == 33) + s++; + memset(desc->u.fpr+16, 0, 4); + for (i=0; i < 16; i++, s+=2) { + int c = hextobyte(s); + if (c == -1) + return 0; + desc->u.fpr[i] = c; + } + mode = KEYDB_SEARCH_MODE_FPR16; + } + else if (hexlength == 40 || (!hexprefix && hexlength == 41 + && *s == '0')) { + /* sha1/rmd160 fingerprint */ + int i; + if (hexlength == 41) + s++; + for (i=0; i < 20; i++, s+=2) { + int c = hextobyte(s); + if (c == -1) + return 0; + desc->u.fpr[i] = c; + } + mode = KEYDB_SEARCH_MODE_FPR20; + } + else { + if (hexprefix) /* This was a hex number with a prefix */ + return 0; /* and a wrong length */ + + desc->exact = 0; + desc->u.name = s; + mode = KEYDB_SEARCH_MODE_SUBSTR; /* default mode */ + } + } + + desc->mode = mode; + return mode; +} + + +static int +skip_unusable(void *dummy,u32 *keyid,PKT_user_id *uid) +{ + int unusable=0; + KBNODE keyblock; + + keyblock=get_pubkeyblock(keyid); + if(!keyblock) + { + log_error("error checking usability status of %s\n",keystr(keyid)); + goto leave; + } + + /* Is the user ID in question revoked/expired? */ + if(uid) + { + KBNODE node; + + for(node=keyblock;node;node=node->next) + { + if(node->pkt->pkttype==PKT_USER_ID) + { + if(cmp_user_ids(uid,node->pkt->pkt.user_id)==0 + && (node->pkt->pkt.user_id->is_revoked + || node->pkt->pkt.user_id->is_expired)) + { + unusable=1; + break; + } + } + } + } + + if(!unusable) + unusable=pk_is_disabled(keyblock->pkt->pkt.public_key); + + leave: + release_kbnode(keyblock); + return unusable; +} + +/**************** + * Try to get the pubkey by the userid. This function looks for the + * first pubkey certificate which has the given name in a user_id. if + * pk/sk has the pubkey algo set, the function will only return a + * pubkey with that algo. If namelist is NULL, the first key is + * returned. The caller should provide storage for either the pk or + * the sk. If ret_kb is not NULL the function will return the + * keyblock there. + */ + +static int +key_byname( GETKEY_CTX *retctx, STRLIST namelist, + PKT_public_key *pk, PKT_secret_key *sk, + int secmode, int include_unusable, + KBNODE *ret_kb, KEYDB_HANDLE *ret_kdbhd ) +{ + int rc = 0; + int n; + STRLIST r; + GETKEY_CTX ctx; + KBNODE help_kb = NULL; + + if( retctx ) {/* reset the returned context in case of error */ + assert (!ret_kdbhd); /* not allowed because the handle is + stored in the context */ + *retctx = NULL; + } + if (ret_kdbhd) + *ret_kdbhd = NULL; + + if(!namelist) + { + ctx = xmalloc_clear (sizeof *ctx); + ctx->nitems = 1; + ctx->items[0].mode=KEYDB_SEARCH_MODE_FIRST; + if(!include_unusable) + ctx->items[0].skipfnc=skip_unusable; + } + else + { + /* build the search context */ + for(n=0, r=namelist; r; r = r->next ) + n++; + + ctx = xmalloc_clear (sizeof *ctx + (n-1)*sizeof ctx->items ); + ctx->nitems = n; + + for(n=0, r=namelist; r; r = r->next, n++ ) + { + classify_user_id (r->d, &ctx->items[n]); + + if (ctx->items[n].exact) + ctx->exact = 1; + if (!ctx->items[n].mode) + { + xfree (ctx); + return G10ERR_INV_USER_ID; + } + if(!include_unusable + && ctx->items[n].mode!=KEYDB_SEARCH_MODE_SHORT_KID + && ctx->items[n].mode!=KEYDB_SEARCH_MODE_LONG_KID + && ctx->items[n].mode!=KEYDB_SEARCH_MODE_FPR16 + && ctx->items[n].mode!=KEYDB_SEARCH_MODE_FPR20 + && ctx->items[n].mode!=KEYDB_SEARCH_MODE_FPR) + ctx->items[n].skipfnc=skip_unusable; + } + } + + ctx->kr_handle = keydb_new (secmode); + if ( !ret_kb ) + ret_kb = &help_kb; + + if( secmode ) { + if (sk) { + ctx->req_algo = sk->req_algo; + ctx->req_usage = sk->req_usage; + } + rc = lookup( ctx, ret_kb, 1 ); + if ( !rc && sk ) { + sk_from_block ( ctx, sk, *ret_kb ); + } + } + else { + if (pk) { + ctx->req_algo = pk->req_algo; + ctx->req_usage = pk->req_usage; + } + rc = lookup( ctx, ret_kb, 0 ); + if ( !rc && pk ) { + pk_from_block ( ctx, pk, *ret_kb ); + } + } + + release_kbnode ( help_kb ); + + if (retctx) /* caller wants the context */ + *retctx = ctx; + else { + if (ret_kdbhd) { + *ret_kdbhd = ctx->kr_handle; + ctx->kr_handle = NULL; + } + get_pubkey_end (ctx); + } + + return rc; +} + + + +/* Find a public key from NAME and return the keyblock or the key. If + ret_kdb is not NULL, the KEYDB handle used to locate this keyblock + is returned and the caller is responsible for closing it. If a key + was not found and NAME is a valid RFC822 mailbox and PKA retrieval + has been enabled, we try to import the pkea via the PKA + mechanism. */ +int +get_pubkey_byname (PKT_public_key *pk, + const char *name, KBNODE *ret_keyblock, + KEYDB_HANDLE *ret_kdbhd, int include_unusable ) +{ + int rc; + STRLIST namelist = NULL; + + add_to_strlist( &namelist, name ); + + rc = key_byname( NULL, namelist, pk, NULL, 0, + include_unusable, ret_keyblock, ret_kdbhd); + + /* If the requested name resembles a valid mailbox and automatic + retrieval has been enabled, we try to import the key. */ + + if (rc == G10ERR_NO_PUBKEY && is_valid_mailbox(name)) + { + struct akl *akl; + + for(akl=opt.auto_key_locate;akl;akl=akl->next) + { + unsigned char *fpr=NULL; + size_t fpr_len; + + switch(akl->type) + { + case AKL_CERT: + glo_ctrl.in_auto_key_retrieve++; + rc=keyserver_import_cert(name,&fpr,&fpr_len); + glo_ctrl.in_auto_key_retrieve--; + + if(rc==0) + log_info(_("automatically retrieved `%s' via %s\n"), + name,"DNS CERT"); + break; + + case AKL_PKA: + glo_ctrl.in_auto_key_retrieve++; + rc=keyserver_import_pka(name,&fpr,&fpr_len); + glo_ctrl.in_auto_key_retrieve--; + + if(rc==0) + log_info(_("automatically retrieved `%s' via %s\n"), + name,"PKA"); + break; + + case AKL_LDAP: + glo_ctrl.in_auto_key_retrieve++; + rc=keyserver_import_ldap(name,&fpr,&fpr_len); + glo_ctrl.in_auto_key_retrieve--; + + if(rc==0) + log_info(_("automatically retrieved `%s' via %s\n"), + name,"LDAP"); + break; + + case AKL_KEYSERVER: + /* Strictly speaking, we don't need to only use a valid + mailbox for the getname search, but it helps cut down + on the problem of searching for something like "john" + and getting a whole lot of keys back. */ + if(opt.keyserver) + { + glo_ctrl.in_auto_key_retrieve++; + rc=keyserver_import_name(name,&fpr,&fpr_len,opt.keyserver); + glo_ctrl.in_auto_key_retrieve--; + + if(rc==0) + log_info(_("automatically retrieved `%s' via %s\n"), + name,opt.keyserver->uri); + } + break; + + case AKL_SPEC: + { + struct keyserver_spec *keyserver; + + keyserver=keyserver_match(akl->spec); + glo_ctrl.in_auto_key_retrieve++; + rc=keyserver_import_name(name,&fpr,&fpr_len,keyserver); + glo_ctrl.in_auto_key_retrieve--; + + if(rc==0) + log_info(_("automatically retrieved `%s' via %s\n"), + name,akl->spec->uri); + } + break; + } + + /* Use the fingerprint of the key that we actually fetched. + This helps prevent problems where the key that we fetched + doesn't have the same name that we used to fetch it. In + the case of CERT and PKA, this is an actual security + requirement as the URL might point to a key put in by an + attacker. By forcing the use of the fingerprint, we + won't use the attacker's key here. */ + if(rc==0 && fpr) + { + int i; + char fpr_string[MAX_FINGERPRINT_LEN*2+1]; + + assert(fpr_len<=MAX_FINGERPRINT_LEN); + + free_strlist(namelist); + namelist=NULL; + + for(i=0;i<fpr_len;i++) + sprintf(fpr_string+2*i,"%02X",fpr[i]); + + if(opt.verbose) + log_info("auto-key-locate found fingerprint %s\n",fpr_string); + + add_to_strlist( &namelist, fpr_string ); + + xfree(fpr); + } + + rc = key_byname( NULL, namelist, pk, NULL, 0, + include_unusable, ret_keyblock, ret_kdbhd); + if(rc!=G10ERR_NO_PUBKEY) + break; + } + } + + free_strlist( namelist ); + return rc; +} + +int +get_pubkey_bynames( GETKEY_CTX *retctx, PKT_public_key *pk, + STRLIST names, KBNODE *ret_keyblock ) +{ + return key_byname( retctx, names, pk, NULL, 0, 1, ret_keyblock, NULL); +} + +int +get_pubkey_next( GETKEY_CTX ctx, PKT_public_key *pk, KBNODE *ret_keyblock ) +{ + int rc; + + rc = lookup( ctx, ret_keyblock, 0 ); + if ( !rc && pk && ret_keyblock ) + pk_from_block ( ctx, pk, *ret_keyblock ); + + return rc; +} + +void +get_pubkey_end( GETKEY_CTX ctx ) +{ + if( ctx ) { + memset (&ctx->kbpos, 0, sizeof ctx->kbpos); + keydb_release (ctx->kr_handle); + if( !ctx->not_allocated ) + xfree( ctx ); + } +} + + +/**************** + * Search for a key with the given fingerprint. + * FIXME: + * We should replace this with the _byname function. Thiscsan be done + * by creating a userID conforming to the unified fingerprint style. + */ +int +get_pubkey_byfprint( PKT_public_key *pk, + const byte *fprint, size_t fprint_len) +{ + int rc; + + if( fprint_len == 20 || fprint_len == 16 ) { + struct getkey_ctx_s ctx; + KBNODE kb = NULL; + + memset( &ctx, 0, sizeof ctx ); + ctx.exact = 1 ; + ctx.not_allocated = 1; + ctx.kr_handle = keydb_new (0); + ctx.nitems = 1; + ctx.items[0].mode = fprint_len==16? KEYDB_SEARCH_MODE_FPR16 + : KEYDB_SEARCH_MODE_FPR20; + memcpy( ctx.items[0].u.fpr, fprint, fprint_len ); + rc = lookup( &ctx, &kb, 0 ); + if (!rc && pk ) + pk_from_block ( &ctx, pk, kb ); + release_kbnode ( kb ); + get_pubkey_end( &ctx ); + } + else + rc = G10ERR_GENERAL; /* Oops */ + return rc; +} + + +/* Get a public key and store it into the allocated pk. This function + differs from get_pubkey_byfprint() in that it does not do a check + of the key to avoid recursion. It should be used only in very + certain cases. PK may be NULL to check just for the existance of + the key. */ +int +get_pubkey_byfprint_fast (PKT_public_key *pk, + const byte *fprint, size_t fprint_len) +{ + int rc = 0; + KEYDB_HANDLE hd; + KBNODE keyblock; + byte fprbuf[MAX_FINGERPRINT_LEN]; + int i; + + for (i=0; i < MAX_FINGERPRINT_LEN && i < fprint_len; i++) + fprbuf[i] = fprint[i]; + while (i < MAX_FINGERPRINT_LEN) + fprbuf[i++] = 0; + + hd = keydb_new (0); + rc = keydb_search_fpr (hd, fprbuf); + if (rc == -1) + { + keydb_release (hd); + return G10ERR_NO_PUBKEY; + } + rc = keydb_get_keyblock (hd, &keyblock); + keydb_release (hd); + if (rc) + { + log_error ("keydb_get_keyblock failed: %s\n", g10_errstr(rc)); + return G10ERR_NO_PUBKEY; + } + + assert ( keyblock->pkt->pkttype == PKT_PUBLIC_KEY + || keyblock->pkt->pkttype == PKT_PUBLIC_SUBKEY ); + if (pk) + copy_public_key (pk, keyblock->pkt->pkt.public_key ); + release_kbnode (keyblock); + + /* Not caching key here since it won't have all of the fields + properly set. */ + + return 0; +} + +/**************** + * Search for a key with the given fingerprint and return the + * complete keyblock which may have more than only this key. + */ +int +get_keyblock_byfprint( KBNODE *ret_keyblock, const byte *fprint, + size_t fprint_len ) +{ + int rc; + + if( fprint_len == 20 || fprint_len == 16 ) { + struct getkey_ctx_s ctx; + + memset( &ctx, 0, sizeof ctx ); + ctx.not_allocated = 1; + ctx.kr_handle = keydb_new (0); + ctx.nitems = 1; + ctx.items[0].mode = fprint_len==16? KEYDB_SEARCH_MODE_FPR16 + : KEYDB_SEARCH_MODE_FPR20; + memcpy( ctx.items[0].u.fpr, fprint, fprint_len ); + rc = lookup( &ctx, ret_keyblock, 0 ); + get_pubkey_end( &ctx ); + } + else + rc = G10ERR_GENERAL; /* Oops */ + + return rc; +} + + +/**************** + * Get a secret key by name and store it into sk + * If NAME is NULL use the default key + */ +static int +get_seckey_byname2( GETKEY_CTX *retctx, + PKT_secret_key *sk, const char *name, int unprotect, + KBNODE *retblock ) +{ + STRLIST namelist = NULL; + int rc,include_unusable=1; + + /* If we have no name, try to use the default secret key. If we + have no default, we'll use the first usable one. */ + + if( !name && opt.def_secret_key && *opt.def_secret_key ) + add_to_strlist( &namelist, opt.def_secret_key ); + else if(name) + add_to_strlist( &namelist, name ); + else + include_unusable=0; + + rc = key_byname( retctx, namelist, NULL, sk, 1, include_unusable, + retblock, NULL ); + + free_strlist( namelist ); + + if( !rc && unprotect ) + rc = check_secret_key( sk, 0 ); + + return rc; +} + +int +get_seckey_byname( PKT_secret_key *sk, const char *name, int unlock ) +{ + return get_seckey_byname2 ( NULL, sk, name, unlock, NULL ); +} + + +int +get_seckey_bynames( GETKEY_CTX *retctx, PKT_secret_key *sk, + STRLIST names, KBNODE *ret_keyblock ) +{ + return key_byname( retctx, names, NULL, sk, 1, 1, ret_keyblock, NULL ); +} + + +int +get_seckey_next( GETKEY_CTX ctx, PKT_secret_key *sk, KBNODE *ret_keyblock ) +{ + int rc; + + rc = lookup( ctx, ret_keyblock, 1 ); + if ( !rc && sk && ret_keyblock ) + sk_from_block ( ctx, sk, *ret_keyblock ); + + return rc; +} + + +void +get_seckey_end( GETKEY_CTX ctx ) +{ + get_pubkey_end( ctx ); +} + + +/**************** + * Search for a key with the given fingerprint. + * FIXME: + * We should replace this with the _byname function. Thiscsan be done + * by creating a userID conforming to the unified fingerprint style. + */ +int +get_seckey_byfprint( PKT_secret_key *sk, + const byte *fprint, size_t fprint_len) +{ + int rc; + + if( fprint_len == 20 || fprint_len == 16 ) { + struct getkey_ctx_s ctx; + KBNODE kb = NULL; + + memset( &ctx, 0, sizeof ctx ); + ctx.exact = 1 ; + ctx.not_allocated = 1; + ctx.kr_handle = keydb_new (1); + ctx.nitems = 1; + ctx.items[0].mode = fprint_len==16? KEYDB_SEARCH_MODE_FPR16 + : KEYDB_SEARCH_MODE_FPR20; + memcpy( ctx.items[0].u.fpr, fprint, fprint_len ); + rc = lookup( &ctx, &kb, 1 ); + if (!rc && sk ) + sk_from_block ( &ctx, sk, kb ); + release_kbnode ( kb ); + get_seckey_end( &ctx ); + } + else + rc = G10ERR_GENERAL; /* Oops */ + return rc; +} + + +/* Search for a secret key with the given fingerprint and return the + complete keyblock which may have more than only this key. */ +int +get_seckeyblock_byfprint (KBNODE *ret_keyblock, const byte *fprint, + size_t fprint_len ) +{ + int rc; + struct getkey_ctx_s ctx; + + if (fprint_len != 20 && fprint_len == 16) + return G10ERR_GENERAL; /* Oops */ + + memset (&ctx, 0, sizeof ctx); + ctx.not_allocated = 1; + ctx.kr_handle = keydb_new (1); + ctx.nitems = 1; + ctx.items[0].mode = (fprint_len==16 + ? KEYDB_SEARCH_MODE_FPR16 + : KEYDB_SEARCH_MODE_FPR20); + memcpy (ctx.items[0].u.fpr, fprint, fprint_len); + rc = lookup (&ctx, ret_keyblock, 1); + get_seckey_end (&ctx); + + return rc; +} + + + +/************************************************ + ************* Merging stuff ******************** + ************************************************/ + +/**************** + * merge all selfsignatures with the keys. + * FIXME: replace this at least for the public key parts + * by merge_selfsigs. + * It is still used in keyedit.c and + * at 2 or 3 other places - check whether it is really needed. + * It might be needed by the key edit and import stuff because + * the keylock is changed. + */ +void +merge_keys_and_selfsig( KBNODE keyblock ) +{ + PKT_public_key *pk = NULL; + PKT_secret_key *sk = NULL; + PKT_signature *sig; + KBNODE k; + u32 kid[2] = { 0, 0 }; + u32 sigdate = 0; + + if (keyblock && keyblock->pkt->pkttype == PKT_PUBLIC_KEY ) { + /* divert to our new function */ + merge_selfsigs (keyblock); + return; + } + /* still need the old one because the new one can't handle secret keys */ + + for(k=keyblock; k; k = k->next ) { + if( k->pkt->pkttype == PKT_PUBLIC_KEY + || k->pkt->pkttype == PKT_PUBLIC_SUBKEY ) { + pk = k->pkt->pkt.public_key; sk = NULL; + if( pk->version < 4 ) + pk = NULL; /* not needed for old keys */ + else if( k->pkt->pkttype == PKT_PUBLIC_KEY ) + keyid_from_pk( pk, kid ); + else if( !pk->expiredate ) { /* and subkey */ + /* insert the expiration date here */ + /*FIXME!!! pk->expiredate = subkeys_expiretime( k, kid );*/ + } + sigdate = 0; + } + else if( k->pkt->pkttype == PKT_SECRET_KEY + || k->pkt->pkttype == PKT_SECRET_SUBKEY ) { + pk = NULL; sk = k->pkt->pkt.secret_key; + if( sk->version < 4 ) + sk = NULL; + else if( k->pkt->pkttype == PKT_SECRET_KEY ) + keyid_from_sk( sk, kid ); + sigdate = 0; + } + else if( (pk || sk ) && k->pkt->pkttype == PKT_SIGNATURE + && (sig=k->pkt->pkt.signature)->sig_class >= 0x10 + && sig->sig_class <= 0x30 && sig->version > 3 + && !(sig->sig_class == 0x18 || sig->sig_class == 0x28) + && sig->keyid[0] == kid[0] && sig->keyid[1] == kid[1] ) { + /* okay this is a self-signature which can be used. + * This is not used for subkey binding signature, becuase this + * is done above. + * FIXME: We should only use this if the signature is valid + * but this is time consuming - we must provide another + * way to handle this + */ + const byte *p; + u32 ed; + + p = parse_sig_subpkt( sig->hashed, SIGSUBPKT_KEY_EXPIRE, NULL ); + if( pk ) { + ed = p? pk->timestamp + buffer_to_u32(p):0; + if( sig->timestamp > sigdate ) { + pk->expiredate = ed; + sigdate = sig->timestamp; + } + } + else { + ed = p? sk->timestamp + buffer_to_u32(p):0; + if( sig->timestamp > sigdate ) { + sk->expiredate = ed; + sigdate = sig->timestamp; + } + } + } + + if(pk && (pk->expiredate==0 || + (pk->max_expiredate && pk->expiredate>pk->max_expiredate))) + pk->expiredate=pk->max_expiredate; + + if(sk && (sk->expiredate==0 || + (sk->max_expiredate && sk->expiredate>sk->max_expiredate))) + sk->expiredate=sk->max_expiredate; + } +} + +static int +parse_key_usage(PKT_signature *sig) +{ + int key_usage=0; + const byte *p; + size_t n; + byte flags; + + p=parse_sig_subpkt(sig->hashed,SIGSUBPKT_KEY_FLAGS,&n); + if(p && n) + { + /* first octet of the keyflags */ + flags=*p; + + if(flags & 1) + { + key_usage |= PUBKEY_USAGE_CERT; + flags&=~1; + } + + if(flags & 2) + { + key_usage |= PUBKEY_USAGE_SIG; + flags&=~2; + } + + /* We do not distinguish between encrypting communications and + encrypting storage. */ + if(flags & (0x04|0x08)) + { + key_usage |= PUBKEY_USAGE_ENC; + flags&=~(0x04|0x08); + } + + if(flags & 0x20) + { + key_usage |= PUBKEY_USAGE_AUTH; + flags&=~0x20; + } + + if(flags) + key_usage |= PUBKEY_USAGE_UNKNOWN; + } + + /* We set PUBKEY_USAGE_UNKNOWN to indicate that this key has a + capability that we do not handle. This serves to distinguish + between a zero key usage which we handle as the default + capabilities for that algorithm, and a usage that we do not + handle. */ + + return key_usage; +} + +/* + * Apply information from SIGNODE (which is the valid self-signature + * associated with that UID) to the UIDNODE: + * - wether the UID has been revoked + * - assumed creation date of the UID + * - temporary store the keyflags here + * - temporary store the key expiration time here + * - mark whether the primary user ID flag hat been set. + * - store the preferences + */ +static void +fixup_uidnode ( KBNODE uidnode, KBNODE signode, u32 keycreated ) +{ + PKT_user_id *uid = uidnode->pkt->pkt.user_id; + PKT_signature *sig = signode->pkt->pkt.signature; + const byte *p, *sym, *hash, *zip; + size_t n, nsym, nhash, nzip; + + sig->flags.chosen_selfsig = 1; /* we chose this one */ + uid->created = 0; /* not created == invalid */ + if ( IS_UID_REV ( sig ) ) { + uid->is_revoked = 1; + return; /* has been revoked */ + } + + uid->expiredate = sig->expiredate; + + if(sig->flags.expired) + { + uid->is_expired = 1; + return; /* has expired */ + } + + uid->created = sig->timestamp; /* this one is okay */ + uid->selfsigversion = sig->version; + /* If we got this far, it's not expired :) */ + uid->is_expired = 0; + + /* store the key flags in the helper variable for later processing */ + uid->help_key_usage=parse_key_usage(sig); + + /* ditto for the key expiration */ + p = parse_sig_subpkt (sig->hashed, SIGSUBPKT_KEY_EXPIRE, NULL); + if( p && buffer_to_u32(p) ) + uid->help_key_expire = keycreated + buffer_to_u32(p); + else + uid->help_key_expire = 0; + + /* Set the primary user ID flag - we will later wipe out some + * of them to only have one in our keyblock */ + uid->is_primary = 0; + p = parse_sig_subpkt ( sig->hashed, SIGSUBPKT_PRIMARY_UID, NULL ); + if ( p && *p ) + uid->is_primary = 2; + /* We could also query this from the unhashed area if it is not in + * the hased area and then later try to decide which is the better + * there should be no security problem with this. + * For now we only look at the hashed one. + */ + + /* Now build the preferences list. These must come from the + hashed section so nobody can modify the ciphers a key is + willing to accept. */ + p = parse_sig_subpkt ( sig->hashed, SIGSUBPKT_PREF_SYM, &n ); + sym = p; nsym = p?n:0; + p = parse_sig_subpkt ( sig->hashed, SIGSUBPKT_PREF_HASH, &n ); + hash = p; nhash = p?n:0; + p = parse_sig_subpkt ( sig->hashed, SIGSUBPKT_PREF_COMPR, &n ); + zip = p; nzip = p?n:0; + if (uid->prefs) + xfree (uid->prefs); + n = nsym + nhash + nzip; + if (!n) + uid->prefs = NULL; + else { + uid->prefs = xmalloc (sizeof (*uid->prefs) * (n+1)); + n = 0; + for (; nsym; nsym--, n++) { + uid->prefs[n].type = PREFTYPE_SYM; + uid->prefs[n].value = *sym++; + } + for (; nhash; nhash--, n++) { + uid->prefs[n].type = PREFTYPE_HASH; + uid->prefs[n].value = *hash++; + } + for (; nzip; nzip--, n++) { + uid->prefs[n].type = PREFTYPE_ZIP; + uid->prefs[n].value = *zip++; + } + uid->prefs[n].type = PREFTYPE_NONE; /* end of list marker */ + uid->prefs[n].value = 0; + } + + /* see whether we have the MDC feature */ + uid->flags.mdc = 0; + p = parse_sig_subpkt (sig->hashed, SIGSUBPKT_FEATURES, &n); + if (p && n && (p[0] & 0x01)) + uid->flags.mdc = 1; + + /* and the keyserver modify flag */ + uid->flags.ks_modify = 1; + p = parse_sig_subpkt (sig->hashed, SIGSUBPKT_KS_FLAGS, &n); + if (p && n && (p[0] & 0x80)) + uid->flags.ks_modify = 0; +} + +static void +sig_to_revoke_info(PKT_signature *sig,struct revoke_info *rinfo) +{ + rinfo->date = sig->timestamp; + rinfo->algo = sig->pubkey_algo; + rinfo->keyid[0] = sig->keyid[0]; + rinfo->keyid[1] = sig->keyid[1]; +} + +static void +merge_selfsigs_main(KBNODE keyblock, int *r_revoked, struct revoke_info *rinfo) +{ + PKT_public_key *pk = NULL; + KBNODE k; + u32 kid[2]; + u32 sigdate, uiddate, uiddate2; + KBNODE signode, uidnode, uidnode2; + u32 curtime = make_timestamp (); + unsigned int key_usage = 0; + u32 keytimestamp = 0; + u32 key_expire = 0; + int key_expire_seen = 0; + byte sigversion = 0; + + *r_revoked = 0; + memset(rinfo,0,sizeof(*rinfo)); + + if ( keyblock->pkt->pkttype != PKT_PUBLIC_KEY ) + BUG (); + pk = keyblock->pkt->pkt.public_key; + keytimestamp = pk->timestamp; + + keyid_from_pk( pk, kid ); + pk->main_keyid[0] = kid[0]; + pk->main_keyid[1] = kid[1]; + + if ( pk->version < 4 ) { + /* before v4 the key packet itself contains the expiration + * date and there was no way to change it, so we start with + * the one from the key packet */ + key_expire = pk->max_expiredate; + key_expire_seen = 1; + } + + /* first pass: find the latest direct key self-signature. + * We assume that the newest one overrides all others + */ + + /* In case this key was already merged */ + xfree(pk->revkey); + pk->revkey=NULL; + pk->numrevkeys=0; + + signode = NULL; + sigdate = 0; /* helper to find the latest signature */ + for(k=keyblock; k && k->pkt->pkttype != PKT_USER_ID; k = k->next ) { + if ( k->pkt->pkttype == PKT_SIGNATURE ) { + PKT_signature *sig = k->pkt->pkt.signature; + if ( sig->keyid[0] == kid[0] && sig->keyid[1]==kid[1] ) { + if ( check_key_signature( keyblock, k, NULL ) ) + ; /* signature did not verify */ + else if ( IS_KEY_REV (sig) ){ + /* key has been revoked - there is no way to override + * such a revocation, so we theoretically can stop now. + * We should not cope with expiration times for revocations + * here because we have to assume that an attacker can + * generate all kinds of signatures. However due to the + * fact that the key has been revoked it does not harm + * either and by continuing we gather some more info on + * that key. + */ + *r_revoked = 1; + sig_to_revoke_info(sig,rinfo); + } + else if ( IS_KEY_SIG (sig) ) { + /* Add any revocation keys onto the pk. This is + particularly interesting since we normally only + get data from the most recent 1F signature, but + you need multiple 1F sigs to properly handle + revocation keys (PGP does it this way, and a + revocation key could be sensitive and hence in a + different signature). */ + if(sig->revkey) { + int i; + + pk->revkey= + xrealloc(pk->revkey,sizeof(struct revocation_key)* + (pk->numrevkeys+sig->numrevkeys)); + + for(i=0;i<sig->numrevkeys;i++) + memcpy(&pk->revkey[pk->numrevkeys++], + sig->revkey[i], + sizeof(struct revocation_key)); + } + + if( sig->timestamp >= sigdate ) { + if(sig->flags.expired) + ; /* signature has expired - ignore it */ + else { + sigdate = sig->timestamp; + signode = k; + if( sig->version > sigversion ) + sigversion = sig->version; + + } + } + } + } + } + } + + /* Remove dupes from the revocation keys */ + + if(pk->revkey) + { + int i,j,x,changed=0; + + for(i=0;i<pk->numrevkeys;i++) + { + for(j=i+1;j<pk->numrevkeys;j++) + { + if(memcmp(&pk->revkey[i],&pk->revkey[j], + sizeof(struct revocation_key))==0) + { + /* remove j */ + + for(x=j;x<pk->numrevkeys-1;x++) + pk->revkey[x]=pk->revkey[x+1]; + + pk->numrevkeys--; + j--; + changed=1; + } + } + } + + if(changed) + pk->revkey=xrealloc(pk->revkey, + pk->numrevkeys*sizeof(struct revocation_key)); + } + + if ( signode ) + { + /* some information from a direct key signature take precedence + * over the same information given in UID sigs. + */ + PKT_signature *sig = signode->pkt->pkt.signature; + const byte *p; + + key_usage=parse_key_usage(sig); + + p = parse_sig_subpkt (sig->hashed, SIGSUBPKT_KEY_EXPIRE, NULL); + if( p && buffer_to_u32(p) ) + { + key_expire = keytimestamp + buffer_to_u32(p); + key_expire_seen = 1; + } + + /* mark that key as valid: one direct key signature should + * render a key as valid */ + pk->is_valid = 1; + } + + /* pass 1.5: look for key revocation signatures that were not made + by the key (i.e. did a revocation key issue a revocation for + us?). Only bother to do this if there is a revocation key in + the first place and we're not revoked already. */ + + if(!*r_revoked && pk->revkey) + for(k=keyblock; k && k->pkt->pkttype != PKT_USER_ID; k = k->next ) + { + if ( k->pkt->pkttype == PKT_SIGNATURE ) + { + PKT_signature *sig = k->pkt->pkt.signature; + + if(IS_KEY_REV(sig) && + (sig->keyid[0]!=kid[0] || sig->keyid[1]!=kid[1])) + { + int rc=check_revocation_keys(pk,sig); + if(rc==0) + { + *r_revoked=2; + sig_to_revoke_info(sig,rinfo); + /* don't continue checking since we can't be any + more revoked than this */ + break; + } + else if(rc==G10ERR_NO_PUBKEY) + pk->maybe_revoked=1; + + /* A failure here means the sig did not verify, was + not issued by a revocation key, or a revocation + key loop was broken. If a revocation key isn't + findable, however, the key might be revoked and + we don't know it. */ + + /* TODO: In the future handle subkey and cert + revocations? PGP doesn't, but it's in 2440. */ + } + } + } + + /* second pass: look at the self-signature of all user IDs */ + signode = uidnode = NULL; + sigdate = 0; /* helper to find the latest signature in one user ID */ + for(k=keyblock; k && k->pkt->pkttype != PKT_PUBLIC_SUBKEY; k = k->next ) { + if ( k->pkt->pkttype == PKT_USER_ID ) { + if ( uidnode && signode ) + { + fixup_uidnode ( uidnode, signode, keytimestamp ); + pk->is_valid=1; + } + uidnode = k; + signode = NULL; + sigdate = 0; + } + else if ( k->pkt->pkttype == PKT_SIGNATURE && uidnode ) { + PKT_signature *sig = k->pkt->pkt.signature; + if ( sig->keyid[0] == kid[0] && sig->keyid[1]==kid[1] ) { + if ( check_key_signature( keyblock, k, NULL ) ) + ; /* signature did not verify */ + else if ( (IS_UID_SIG (sig) || IS_UID_REV (sig)) + && sig->timestamp >= sigdate ) + { + /* Note: we allow to invalidate cert revocations + * by a newer signature. An attacker can't use this + * because a key should be revoced with a key revocation. + * The reason why we have to allow for that is that at + * one time an email address may become invalid but later + * the same email address may become valid again (hired, + * fired, hired again). + */ + + sigdate = sig->timestamp; + signode = k; + signode->pkt->pkt.signature->flags.chosen_selfsig=0; + if( sig->version > sigversion ) + sigversion = sig->version; + } + } + } + } + if ( uidnode && signode ) { + fixup_uidnode ( uidnode, signode, keytimestamp ); + pk->is_valid = 1; + } + + /* If the key isn't valid yet, and we have + --allow-non-selfsigned-uid set, then force it valid. */ + if(!pk->is_valid && opt.allow_non_selfsigned_uid) + { + if(opt.verbose) + log_info(_("Invalid key %s made valid by" + " --allow-non-selfsigned-uid\n"),keystr_from_pk(pk)); + pk->is_valid = 1; + } + + /* The key STILL isn't valid, so try and find an ultimately + trusted signature. */ + if(!pk->is_valid) + { + uidnode=NULL; + + for(k=keyblock; k && k->pkt->pkttype != PKT_PUBLIC_SUBKEY; k=k->next) + { + if ( k->pkt->pkttype == PKT_USER_ID ) + uidnode = k; + else if ( k->pkt->pkttype == PKT_SIGNATURE && uidnode ) + { + PKT_signature *sig = k->pkt->pkt.signature; + + if(sig->keyid[0] != kid[0] || sig->keyid[1]!=kid[1]) + { + PKT_public_key *ultimate_pk; + + ultimate_pk=xmalloc_clear(sizeof(*ultimate_pk)); + + /* We don't want to use the full get_pubkey to + avoid infinite recursion in certain cases. + There is no reason to check that an ultimately + trusted key is still valid - if it has been + revoked or the user should also renmove the + ultimate trust flag. */ + if(get_pubkey_fast(ultimate_pk,sig->keyid)==0 + && check_key_signature2(keyblock,k,ultimate_pk, + NULL,NULL,NULL,NULL)==0 + && get_ownertrust(ultimate_pk)==TRUST_ULTIMATE) + { + free_public_key(ultimate_pk); + pk->is_valid=1; + break; + } + + free_public_key(ultimate_pk); + } + } + } + } + + /* Record the highest selfsig version so we know if this is a v3 + key through and through, or a v3 key with a v4 selfsig + somewhere. This is useful in a few places to know if the key + must be treated as PGP2-style or OpenPGP-style. Note that a + selfsig revocation with a higher version number will also raise + this value. This is okay since such a revocation must be + issued by the user (i.e. it cannot be issued by someone else to + modify the key behavior.) */ + + pk->selfsigversion=sigversion; + + /* Now that we had a look at all user IDs we can now get some information + * from those user IDs. + */ + + if ( !key_usage ) { + /* find the latest user ID with key flags set */ + uiddate = 0; /* helper to find the latest user ID */ + for(k=keyblock; k && k->pkt->pkttype != PKT_PUBLIC_SUBKEY; + k = k->next ) { + if ( k->pkt->pkttype == PKT_USER_ID ) { + PKT_user_id *uid = k->pkt->pkt.user_id; + if ( uid->help_key_usage && uid->created > uiddate ) { + key_usage = uid->help_key_usage; + uiddate = uid->created; + } + } + } + } + if ( !key_usage ) { /* no key flags at all: get it from the algo */ + key_usage = openpgp_pk_algo_usage ( pk->pubkey_algo ); + } + else { /* check that the usage matches the usage as given by the algo */ + int x = openpgp_pk_algo_usage ( pk->pubkey_algo ); + if ( x ) /* mask it down to the actual allowed usage */ + key_usage &= x; + } + + /* Whatever happens, it's a primary key, so it can certify. */ + pk->pubkey_usage = key_usage|PUBKEY_USAGE_CERT; + + if ( !key_expire_seen ) { + /* find the latest valid user ID with a key expiration set + * Note, that this may be a different one from the above because + * some user IDs may have no expiration date set */ + uiddate = 0; + for(k=keyblock; k && k->pkt->pkttype != PKT_PUBLIC_SUBKEY; + k = k->next ) { + if ( k->pkt->pkttype == PKT_USER_ID ) { + PKT_user_id *uid = k->pkt->pkt.user_id; + if ( uid->help_key_expire && uid->created > uiddate ) { + key_expire = uid->help_key_expire; + uiddate = uid->created; + } + } + } + } + + /* Currently only v3 keys have a maximum expiration date, but I'll + bet v5 keys get this feature again. */ + if(key_expire==0 || (pk->max_expiredate && key_expire>pk->max_expiredate)) + key_expire=pk->max_expiredate; + + pk->has_expired = key_expire >= curtime? 0 : key_expire; + pk->expiredate = key_expire; + + /* Fixme: we should see how to get rid of the expiretime fields but + * this needs changes at other places too. */ + + /* and now find the real primary user ID and delete all others */ + uiddate = uiddate2 = 0; + uidnode = uidnode2 = NULL; + for(k=keyblock; k && k->pkt->pkttype != PKT_PUBLIC_SUBKEY; k = k->next ) { + if ( k->pkt->pkttype == PKT_USER_ID && + !k->pkt->pkt.user_id->attrib_data) { + PKT_user_id *uid = k->pkt->pkt.user_id; + if (uid->is_primary) + { + if(uid->created > uiddate) + { + uiddate = uid->created; + uidnode = k; + } + else if(uid->created==uiddate && uidnode) + { + /* The dates are equal, so we need to do a + different (and arbitrary) comparison. This + should rarely, if ever, happen. It's good to + try and guarantee that two different GnuPG + users with two different keyrings at least pick + the same primary. */ + if(cmp_user_ids(uid,uidnode->pkt->pkt.user_id)>0) + uidnode=k; + } + } + else + { + if(uid->created > uiddate2) + { + uiddate2 = uid->created; + uidnode2 = k; + } + else if(uid->created==uiddate2 && uidnode2) + { + if(cmp_user_ids(uid,uidnode2->pkt->pkt.user_id)>0) + uidnode2=k; + } + } + } + } + if ( uidnode ) { + for(k=keyblock; k && k->pkt->pkttype != PKT_PUBLIC_SUBKEY; + k = k->next ) { + if ( k->pkt->pkttype == PKT_USER_ID && + !k->pkt->pkt.user_id->attrib_data) { + PKT_user_id *uid = k->pkt->pkt.user_id; + if ( k != uidnode ) + uid->is_primary = 0; + } + } + } + else if( uidnode2 ) { + /* none is flagged primary - use the latest user ID we have, + and disambiguate with the arbitrary packet comparison. */ + uidnode2->pkt->pkt.user_id->is_primary = 1; + } + else + { + /* None of our uids were self-signed, so pick the one that + sorts first to be the primary. This is the best we can do + here since there are no self sigs to date the uids. */ + + uidnode = NULL; + + for(k=keyblock; k && k->pkt->pkttype != PKT_PUBLIC_SUBKEY; + k = k->next ) + { + if(k->pkt->pkttype==PKT_USER_ID + && !k->pkt->pkt.user_id->attrib_data) + { + if(!uidnode) + { + uidnode=k; + uidnode->pkt->pkt.user_id->is_primary=1; + continue; + } + else + { + if(cmp_user_ids(k->pkt->pkt.user_id, + uidnode->pkt->pkt.user_id)>0) + { + uidnode->pkt->pkt.user_id->is_primary=0; + uidnode=k; + uidnode->pkt->pkt.user_id->is_primary=1; + } + else + k->pkt->pkt.user_id->is_primary=0; /* just to be + safe */ + } + } + } + } +} + + +static void +merge_selfsigs_subkey( KBNODE keyblock, KBNODE subnode ) +{ + PKT_public_key *mainpk = NULL, *subpk = NULL; + PKT_signature *sig; + KBNODE k; + u32 mainkid[2]; + u32 sigdate = 0; + KBNODE signode; + u32 curtime = make_timestamp (); + unsigned int key_usage = 0; + u32 keytimestamp = 0; + u32 key_expire = 0; + const byte *p; + + if ( subnode->pkt->pkttype != PKT_PUBLIC_SUBKEY ) + BUG (); + mainpk = keyblock->pkt->pkt.public_key; + if ( mainpk->version < 4 ) + return; /* (actually this should never happen) */ + keyid_from_pk( mainpk, mainkid ); + subpk = subnode->pkt->pkt.public_key; + keytimestamp = subpk->timestamp; + + subpk->is_valid = 0; + subpk->main_keyid[0] = mainpk->main_keyid[0]; + subpk->main_keyid[1] = mainpk->main_keyid[1]; + + /* find the latest key binding self-signature. */ + signode = NULL; + sigdate = 0; /* helper to find the latest signature */ + for(k=subnode->next; k && k->pkt->pkttype != PKT_PUBLIC_SUBKEY; + k = k->next ) { + if ( k->pkt->pkttype == PKT_SIGNATURE ) { + sig = k->pkt->pkt.signature; + if ( sig->keyid[0] == mainkid[0] && sig->keyid[1]==mainkid[1] ) { + if ( check_key_signature( keyblock, k, NULL ) ) + ; /* signature did not verify */ + else if ( IS_SUBKEY_REV (sig) ) { + /* Note that this means that the date on a + revocation sig does not matter - even if the + binding sig is dated after the revocation sig, + the subkey is still marked as revoked. This + seems ok, as it is just as easy to make new + subkeys rather than re-sign old ones as the + problem is in the distribution. Plus, PGP (7) + does this the same way. */ + subpk->is_revoked = 1; + sig_to_revoke_info(sig,&subpk->revoked); + /* although we could stop now, we continue to + * figure out other information like the old expiration + * time */ + } + else if ( IS_SUBKEY_SIG (sig) && sig->timestamp >= sigdate ) + { + if(sig->flags.expired) + ; /* signature has expired - ignore it */ + else + { + sigdate = sig->timestamp; + signode = k; + signode->pkt->pkt.signature->flags.chosen_selfsig=0; + } + } + } + } + } + + /* no valid key binding */ + if ( !signode ) + return; + + sig = signode->pkt->pkt.signature; + sig->flags.chosen_selfsig=1; /* so we know which selfsig we chose later */ + + key_usage=parse_key_usage(sig); + if ( !key_usage ) + { + /* no key flags at all: get it from the algo */ + key_usage = openpgp_pk_algo_usage ( subpk->pubkey_algo ); + } + else + { + /* check that the usage matches the usage as given by the algo */ + int x = openpgp_pk_algo_usage ( subpk->pubkey_algo ); + if ( x ) /* mask it down to the actual allowed usage */ + key_usage &= x; + } + + subpk->pubkey_usage = key_usage; + + p = parse_sig_subpkt (sig->hashed, SIGSUBPKT_KEY_EXPIRE, NULL); + if ( p && buffer_to_u32(p) ) + key_expire = keytimestamp + buffer_to_u32(p); + else + key_expire = 0; + subpk->has_expired = key_expire >= curtime? 0 : key_expire; + subpk->expiredate = key_expire; + + /* algo doesn't exist */ + if(check_pubkey_algo(subpk->pubkey_algo)) + return; + + subpk->is_valid = 1; + + /* Find the first 0x19 embedded signature on our self-sig. */ + if(subpk->backsig==0) + { + int seq=0; + size_t n; + + /* We do this while() since there may be other embedded + signatures in the future. We only want 0x19 here. */ + while((p=enum_sig_subpkt(sig->hashed, + SIGSUBPKT_SIGNATURE,&n,&seq,NULL))) + if(n>3 && ((p[0]==3 && p[2]==0x19) || (p[0]==4 && p[1]==0x19))) + break; + + if(p==NULL) + { + seq=0; + /* It is safe to have this in the unhashed area since the + 0x19 is located on the selfsig for convenience, not + security. */ + while((p=enum_sig_subpkt(sig->unhashed,SIGSUBPKT_SIGNATURE, + &n,&seq,NULL))) + if(n>3 && ((p[0]==3 && p[2]==0x19) || (p[0]==4 && p[1]==0x19))) + break; + } + + if(p) + { + PKT_signature *backsig=xmalloc_clear(sizeof(PKT_signature)); + IOBUF backsig_buf=iobuf_temp_with_content(p,n); + + if(parse_signature(backsig_buf,PKT_SIGNATURE,n,backsig)==0) + { + if(check_backsig(mainpk,subpk,backsig)==0) + subpk->backsig=2; + else + subpk->backsig=1; + } + + iobuf_close(backsig_buf); + free_seckey_enc(backsig); + } + } +} + + +/* + * Merge information from the self-signatures with the key, so that + * we can later use them more easy. + * The function works by first applying the self signatures to the + * primary key and the to each subkey. + * Here are the rules we use to decide which inormation from which + * self-signature is used: + * We check all self signatures or validity and ignore all invalid signatures. + * All signatures are then ordered by their creation date .... + * For the primary key: + * FIXME the docs + */ +static void +merge_selfsigs( KBNODE keyblock ) +{ + KBNODE k; + int revoked; + struct revoke_info rinfo; + PKT_public_key *main_pk; + prefitem_t *prefs; + int mdc_feature; + + if ( keyblock->pkt->pkttype != PKT_PUBLIC_KEY ) { + if (keyblock->pkt->pkttype == PKT_SECRET_KEY ) { + log_error ("expected public key but found secret key " + "- must stop\n"); + /* we better exit here becuase a public key is expected at + other places too. FIXME: Figure this out earlier and + don't get to here at all */ + g10_exit (1); + } + BUG (); + } + + merge_selfsigs_main ( keyblock, &revoked, &rinfo ); + + /* now merge in the data from each of the subkeys */ + for(k=keyblock; k; k = k->next ) { + if ( k->pkt->pkttype == PKT_PUBLIC_SUBKEY ) { + merge_selfsigs_subkey ( keyblock, k ); + } + } + + main_pk = keyblock->pkt->pkt.public_key; + if ( revoked || main_pk->has_expired || !main_pk->is_valid ) { + /* if the primary key is revoked, expired, or invalid we + * better set the appropriate flags on that key and all + * subkeys */ + for(k=keyblock; k; k = k->next ) { + if ( k->pkt->pkttype == PKT_PUBLIC_KEY + || k->pkt->pkttype == PKT_PUBLIC_SUBKEY ) { + PKT_public_key *pk = k->pkt->pkt.public_key; + if(!main_pk->is_valid) + pk->is_valid = 0; + if(revoked && !pk->is_revoked) + { + pk->is_revoked = revoked; + memcpy(&pk->revoked,&rinfo,sizeof(rinfo)); + } + if(main_pk->has_expired) + pk->has_expired = main_pk->has_expired; + } + } + return; + } + + /* set the preference list of all keys to those of the primary real + * user ID. Note: we use these preferences when we don't know by + * which user ID the key has been selected. + * fixme: we should keep atoms of commonly used preferences or + * use reference counting to optimize the preference lists storage. + * FIXME: it might be better to use the intersection of + * all preferences. + * Do a similar thing for the MDC feature flag. + */ + prefs = NULL; + mdc_feature = 0; + for (k=keyblock; k && k->pkt->pkttype != PKT_PUBLIC_SUBKEY; k = k->next) { + if (k->pkt->pkttype == PKT_USER_ID + && !k->pkt->pkt.user_id->attrib_data + && k->pkt->pkt.user_id->is_primary) { + prefs = k->pkt->pkt.user_id->prefs; + mdc_feature = k->pkt->pkt.user_id->flags.mdc; + break; + } + } + for(k=keyblock; k; k = k->next ) { + if ( k->pkt->pkttype == PKT_PUBLIC_KEY + || k->pkt->pkttype == PKT_PUBLIC_SUBKEY ) { + PKT_public_key *pk = k->pkt->pkt.public_key; + if (pk->prefs) + xfree (pk->prefs); + pk->prefs = copy_prefs (prefs); + pk->mdc_feature = mdc_feature; + } + } +} + + +/* + * Merge the secret keys from secblock into the pubblock thereby + * replacing the public (sub)keys with their secret counterparts Hmmm: + * It might be better to get away from the concept of entire secret + * keys at all and have a way to store just the real secret parts + * from the key. + */ +static void +merge_public_with_secret ( KBNODE pubblock, KBNODE secblock ) +{ + KBNODE pub; + + assert ( pubblock->pkt->pkttype == PKT_PUBLIC_KEY ); + assert ( secblock->pkt->pkttype == PKT_SECRET_KEY ); + + for (pub=pubblock; pub; pub = pub->next ) { + if ( pub->pkt->pkttype == PKT_PUBLIC_KEY ) { + PKT_public_key *pk = pub->pkt->pkt.public_key; + PKT_secret_key *sk = secblock->pkt->pkt.secret_key; + assert ( pub == pubblock ); /* only in the first node */ + /* there is nothing to compare in this case, so just replace + * some information */ + copy_public_parts_to_secret_key ( pk, sk ); + free_public_key ( pk ); + pub->pkt->pkttype = PKT_SECRET_KEY; + pub->pkt->pkt.secret_key = copy_secret_key (NULL, sk); + } + else if ( pub->pkt->pkttype == PKT_PUBLIC_SUBKEY ) { + KBNODE sec; + PKT_public_key *pk = pub->pkt->pkt.public_key; + + /* this is more complicated: it may happen that the sequence + * of the subkeys dosn't match, so we have to find the + * appropriate secret key */ + for (sec=secblock->next; sec; sec = sec->next ) { + if ( sec->pkt->pkttype == PKT_SECRET_SUBKEY ) { + PKT_secret_key *sk = sec->pkt->pkt.secret_key; + if ( !cmp_public_secret_key ( pk, sk ) ) { + copy_public_parts_to_secret_key ( pk, sk ); + free_public_key ( pk ); + pub->pkt->pkttype = PKT_SECRET_SUBKEY; + pub->pkt->pkt.secret_key = copy_secret_key (NULL, sk); + break; + } + } + } + if ( !sec ) + BUG(); /* already checked in premerge */ + } + } +} + +/* This function checks that for every public subkey a corresponding + * secret subkey is available and deletes the public subkey otherwise. + * We need this function because we can't delete it later when we + * actually merge the secret parts into the pubring. + * The function also plays some games with the node flags. + */ +static void +premerge_public_with_secret ( KBNODE pubblock, KBNODE secblock ) +{ + KBNODE last, pub; + + assert ( pubblock->pkt->pkttype == PKT_PUBLIC_KEY ); + assert ( secblock->pkt->pkttype == PKT_SECRET_KEY ); + + for (pub=pubblock,last=NULL; pub; last = pub, pub = pub->next ) { + pub->flag &= ~3; /* reset bits 0 and 1 */ + if ( pub->pkt->pkttype == PKT_PUBLIC_SUBKEY ) { + KBNODE sec; + PKT_public_key *pk = pub->pkt->pkt.public_key; + + for (sec=secblock->next; sec; sec = sec->next ) { + if ( sec->pkt->pkttype == PKT_SECRET_SUBKEY ) { + PKT_secret_key *sk = sec->pkt->pkt.secret_key; + if ( !cmp_public_secret_key ( pk, sk ) ) { + if ( sk->protect.s2k.mode == 1001 ) { + /* The secret parts are not available so + we can't use that key for signing etc. + Fix the pubkey usage */ + pk->pubkey_usage &= ~(PUBKEY_USAGE_SIG + |PUBKEY_USAGE_AUTH); + } + /* transfer flag bits 0 and 1 to the pubblock */ + pub->flag |= (sec->flag &3); + break; + } + } + } + if ( !sec ) { + KBNODE next, ll; + + if (opt.verbose) + log_info (_("no secret subkey" + " for public subkey %s - ignoring\n"), + keystr_from_pk (pk)); + /* we have to remove the subkey in this case */ + assert ( last ); + /* find the next subkey */ + for (next=pub->next,ll=pub; + next && next->pkt->pkttype != PKT_PUBLIC_SUBKEY; + ll = next, next = next->next ) + ; + /* make new link */ + last->next = next; + /* release this public subkey with all sigs */ + ll->next = NULL; + release_kbnode( pub ); + /* let the loop continue */ + pub = last; + } + } + } + /* We need to copy the found bits (0 and 1) from the secret key to + the public key. This has already been done for the subkeys but + got lost on the primary key - fix it here *. */ + pubblock->flag |= (secblock->flag & 3); +} + + + + +/* See see whether the key fits + * our requirements and in case we do not + * request the primary key, we should select + * a suitable subkey. + * FIXME: Check against PGP 7 whether we still need a kludge + * to favor type 16 keys over type 20 keys when type 20 + * has not been explitely requested. + * Returns: True when a suitable key has been found. + * + * We have to distinguish four cases: FIXME! + * 1. No usage and no primary key requested + * Examples for this case are that we have a keyID to be used + * for decrytion or verification. + * 2. No usage but primary key requested + * This is the case for all functions which work on an + * entire keyblock, e.g. for editing or listing + * 3. Usage and primary key requested + * FXME + * 4. Usage but no primary key requested + * FIXME + * FIXME: Tell what is going to happen here and something about the rationale + * Note: We don't use this function if no specific usage is requested; + * This way the getkey functions can be used for plain key listings. + * + * CTX ist the keyblock we are investigating, if FOUNDK is not NULL this + * is the key we actually found by looking at the keyid or a fingerprint and + * may eitehr point to the primary or one of the subkeys. + */ + +static int +finish_lookup (GETKEY_CTX ctx) +{ + KBNODE keyblock = ctx->keyblock; + KBNODE k; + KBNODE foundk = NULL; + PKT_user_id *foundu = NULL; +#define USAGE_MASK (PUBKEY_USAGE_SIG|PUBKEY_USAGE_ENC|PUBKEY_USAGE_CERT) + unsigned int req_usage = ( ctx->req_usage & USAGE_MASK ); + /* Request the primary if we're certifying another key, and also + if signing data while --pgp6 or --pgp7 is on since pgp 6 and 7 + do not understand signatures made by a signing subkey. PGP 8 + does. */ + int req_prim = (ctx->req_usage & PUBKEY_USAGE_CERT) || + ((PGP6 || PGP7) && (ctx->req_usage & PUBKEY_USAGE_SIG)); + u32 latest_date; + KBNODE latest_key; + u32 curtime = make_timestamp (); + + assert( keyblock->pkt->pkttype == PKT_PUBLIC_KEY ); + + ctx->found_key = NULL; + + if (ctx->exact) { + for (k=keyblock; k; k = k->next) { + if ( (k->flag & 1) ) { + assert ( k->pkt->pkttype == PKT_PUBLIC_KEY + || k->pkt->pkttype == PKT_PUBLIC_SUBKEY ); + foundk = k; + break; + } + } + } + + for (k=keyblock; k; k = k->next) { + if ( (k->flag & 2) ) { + assert (k->pkt->pkttype == PKT_USER_ID); + foundu = k->pkt->pkt.user_id; + break; + } + } + + if ( DBG_CACHE ) + log_debug( "finish_lookup: checking key %08lX (%s)(req_usage=%x)\n", + (ulong)keyid_from_pk( keyblock->pkt->pkt.public_key, NULL), + foundk? "one":"all", req_usage); + + if (!req_usage) { + latest_key = foundk? foundk:keyblock; + goto found; + } + + if (!req_usage) { + PKT_public_key *pk = foundk->pkt->pkt.public_key; + if (pk->user_id) + free_user_id (pk->user_id); + pk->user_id = scopy_user_id (foundu); + ctx->found_key = foundk; + cache_user_id( keyblock ); + return 1; /* found */ + } + + latest_date = 0; + latest_key = NULL; + /* do not look at subkeys if a certification key is requested */ + if ((!foundk || foundk->pkt->pkttype == PKT_PUBLIC_SUBKEY) && !req_prim) { + KBNODE nextk; + /* either start a loop or check just this one subkey */ + for (k=foundk?foundk:keyblock; k; k = nextk ) { + PKT_public_key *pk; + nextk = k->next; + if ( k->pkt->pkttype != PKT_PUBLIC_SUBKEY ) + continue; + if ( foundk ) + nextk = NULL; /* what a hack */ + pk = k->pkt->pkt.public_key; + if (DBG_CACHE) + log_debug( "\tchecking subkey %08lX\n", + (ulong)keyid_from_pk( pk, NULL)); + if ( !pk->is_valid ) { + if (DBG_CACHE) + log_debug( "\tsubkey not valid\n"); + continue; + } + if ( pk->is_revoked ) { + if (DBG_CACHE) + log_debug( "\tsubkey has been revoked\n"); + continue; + } + if ( pk->has_expired ) { + if (DBG_CACHE) + log_debug( "\tsubkey has expired\n"); + continue; + } + if ( pk->timestamp > curtime && !opt.ignore_valid_from ) { + if (DBG_CACHE) + log_debug( "\tsubkey not yet valid\n"); + continue; + } + + if ( !((pk->pubkey_usage&USAGE_MASK) & req_usage) ) { + if (DBG_CACHE) + log_debug( "\tusage does not match: want=%x have=%x\n", + req_usage, pk->pubkey_usage ); + continue; + } + + if (DBG_CACHE) + log_debug( "\tsubkey looks fine\n"); + if ( pk->timestamp > latest_date ) { + latest_date = pk->timestamp; + latest_key = k; + } + } + } + + /* Okay now try the primary key unless we want an exact + * key ID match on a subkey */ + if ((!latest_key && !(ctx->exact && foundk != keyblock)) || req_prim) { + PKT_public_key *pk; + if (DBG_CACHE && !foundk && !req_prim ) + log_debug( "\tno suitable subkeys found - trying primary\n"); + pk = keyblock->pkt->pkt.public_key; + if ( !pk->is_valid ) { + if (DBG_CACHE) + log_debug( "\tprimary key not valid\n"); + } + else if ( pk->is_revoked ) { + if (DBG_CACHE) + log_debug( "\tprimary key has been revoked\n"); + } + else if ( pk->has_expired ) { + if (DBG_CACHE) + log_debug( "\tprimary key has expired\n"); + } + else if ( !((pk->pubkey_usage&USAGE_MASK) & req_usage) ) { + if (DBG_CACHE) + log_debug( "\tprimary key usage does not match: " + "want=%x have=%x\n", + req_usage, pk->pubkey_usage ); + } + else { /* okay */ + if (DBG_CACHE) + log_debug( "\tprimary key may be used\n"); + latest_key = keyblock; + latest_date = pk->timestamp; + } + } + + if ( !latest_key ) { + if (DBG_CACHE) + log_debug("\tno suitable key found - giving up\n"); + return 0; + } + + found: + if (DBG_CACHE) + log_debug( "\tusing key %08lX\n", + (ulong)keyid_from_pk( latest_key->pkt->pkt.public_key, NULL) ); + + if (latest_key) { + PKT_public_key *pk = latest_key->pkt->pkt.public_key; + if (pk->user_id) + free_user_id (pk->user_id); + pk->user_id = scopy_user_id (foundu); + } + + ctx->found_key = latest_key; + + if (latest_key != keyblock && opt.verbose) + { + char *tempkeystr= + xstrdup(keystr_from_pk(latest_key->pkt->pkt.public_key)); + log_info(_("using subkey %s instead of primary key %s\n"), + tempkeystr, keystr_from_pk(keyblock->pkt->pkt.public_key)); + xfree(tempkeystr); + } + + cache_user_id( keyblock ); + + return 1; /* found */ +} + + +static int +lookup( GETKEY_CTX ctx, KBNODE *ret_keyblock, int secmode ) +{ + int rc; + KBNODE secblock = NULL; /* helper */ + int no_suitable_key = 0; + + rc = 0; + while (!(rc = keydb_search (ctx->kr_handle, ctx->items, ctx->nitems))) { + /* If we are searching for the first key we have to make sure + that the next interation does not no an implicit reset. + This can be triggered by an empty key ring. */ + if (ctx->nitems && ctx->items->mode == KEYDB_SEARCH_MODE_FIRST) + ctx->items->mode = KEYDB_SEARCH_MODE_NEXT; + + rc = keydb_get_keyblock (ctx->kr_handle, &ctx->keyblock); + if (rc) { + log_error ("keydb_get_keyblock failed: %s\n", g10_errstr(rc)); + rc = 0; + goto skip; + } + + if ( secmode ) { + /* find the correspondig public key and use this + * this one for the selection process */ + u32 aki[2]; + KBNODE k = ctx->keyblock; + + if (k->pkt->pkttype != PKT_SECRET_KEY) + BUG(); + + keyid_from_sk (k->pkt->pkt.secret_key, aki); + k = get_pubkeyblock (aki); + if( !k ) + { + if (!opt.quiet) + log_info(_("key %s: secret key without public key" + " - skipped\n"), keystr(aki)); + goto skip; + } + secblock = ctx->keyblock; + ctx->keyblock = k; + + premerge_public_with_secret ( ctx->keyblock, secblock ); + } + + /* warning: node flag bits 0 and 1 should be preserved by + * merge_selfsigs. For secret keys, premerge did tranfer the + * keys to the keyblock */ + merge_selfsigs ( ctx->keyblock ); + if ( finish_lookup (ctx) ) { + no_suitable_key = 0; + if ( secmode ) { + merge_public_with_secret ( ctx->keyblock, + secblock); + release_kbnode (secblock); + secblock = NULL; + } + goto found; + } + else + no_suitable_key = 1; + + skip: + /* release resources and continue search */ + if ( secmode ) { + release_kbnode( secblock ); + secblock = NULL; + } + release_kbnode( ctx->keyblock ); + ctx->keyblock = NULL; + } + + found: + if( rc && rc != -1 ) + log_error("keydb_search failed: %s\n", g10_errstr(rc)); + + if( !rc ) { + *ret_keyblock = ctx->keyblock; /* return the keyblock */ + ctx->keyblock = NULL; + } + else if (rc == -1 && no_suitable_key) + rc = secmode ? G10ERR_UNU_SECKEY : G10ERR_UNU_PUBKEY; + else if( rc == -1 ) + rc = secmode ? G10ERR_NO_SECKEY : G10ERR_NO_PUBKEY; + + if ( secmode ) { + release_kbnode( secblock ); + secblock = NULL; + } + release_kbnode( ctx->keyblock ); + ctx->keyblock = NULL; + + ctx->last_rc = rc; + return rc; +} + + + + +/**************** + * FIXME: Replace by the generic function + * It does not work as it is right now - it is used at + * 2 places: a) to get the key for an anonyous recipient + * b) to get the ultimately trusted keys. + * The a) usage might have some problems. + * + * set with_subkeys true to include subkeys + * set with_spm true to include secret-parts-missing keys + * + * Enumerate all primary secret keys. Caller must use these procedure: + * 1) create a void pointer and initialize it to NULL + * 2) pass this void pointer by reference to this function + * and provide space for the secret key (pass a buffer for sk) + * 3) call this function as long as it does not return -1 + * to indicate EOF. + * 4) Always call this function a last time with SK set to NULL, + * so that can free it's context. + */ +int +enum_secret_keys( void **context, PKT_secret_key *sk, + int with_subkeys, int with_spm ) +{ + int rc=0; + struct { + int eof; + int first; + KEYDB_HANDLE hd; + KBNODE keyblock; + KBNODE node; + } *c = *context; + + + if( !c ) { /* make a new context */ + c = xmalloc_clear( sizeof *c ); + *context = c; + c->hd = keydb_new (1); + c->first = 1; + c->keyblock = NULL; + c->node = NULL; + } + + if( !sk ) { /* free the context */ + keydb_release (c->hd); + release_kbnode (c->keyblock); + xfree( c ); + *context = NULL; + return 0; + } + + if( c->eof ) + return -1; + + do { + /* get the next secret key from the current keyblock */ + for (; c->node; c->node = c->node->next) { + if ((c->node->pkt->pkttype == PKT_SECRET_KEY + || (with_subkeys + && c->node->pkt->pkttype == PKT_SECRET_SUBKEY) ) + && !(c->node->pkt->pkt.secret_key->protect.s2k.mode==1001 + && !with_spm)) { + copy_secret_key (sk, c->node->pkt->pkt.secret_key ); + c->node = c->node->next; + return 0; /* found */ + } + } + release_kbnode (c->keyblock); + c->keyblock = c->node = NULL; + + rc = c->first? keydb_search_first (c->hd) : keydb_search_next (c->hd); + c->first = 0; + if (rc) { + keydb_release (c->hd); c->hd = NULL; + c->eof = 1; + return -1; /* eof */ + } + + rc = keydb_get_keyblock (c->hd, &c->keyblock); + c->node = c->keyblock; + } while (!rc); + + return rc; /* error */ +} + + + +/********************************************* + *********** user ID printing helpers ******* + *********************************************/ + +/**************** + * Return a string with a printable representation of the user_id. + * this string must be freed by xfree. + */ +char* +get_user_id_string( u32 *keyid ) +{ + user_id_db_t r; + char *p; + int pass=0; + /* try it two times; second pass reads from key resources */ + do + { + for(r=user_id_db; r; r = r->next ) + { + keyid_list_t a; + for (a=r->keyids; a; a= a->next ) + { + if( a->keyid[0] == keyid[0] && a->keyid[1] == keyid[1] ) + { + p = xmalloc( keystrlen() + 1 + r->len + 1 ); + sprintf(p, "%s %.*s", keystr(keyid), r->len, r->name ); + return p; + } + } + } + } while( ++pass < 2 && !get_pubkey( NULL, keyid ) ); + p = xmalloc( keystrlen() + 5 ); + sprintf(p, "%s [?]", keystr(keyid)); + return p; +} + + +char* +get_user_id_string_native ( u32 *keyid ) +{ + char *p = get_user_id_string( keyid ); + char *p2 = utf8_to_native( p, strlen(p), 0 ); + xfree(p); + return p2; +} + + +char* +get_long_user_id_string( u32 *keyid ) +{ + user_id_db_t r; + char *p; + int pass=0; + /* try it two times; second pass reads from key resources */ + do { + for(r=user_id_db; r; r = r->next ) { + keyid_list_t a; + for (a=r->keyids; a; a= a->next ) { + if( a->keyid[0] == keyid[0] && a->keyid[1] == keyid[1] ) { + p = xmalloc( r->len + 20 ); + sprintf(p, "%08lX%08lX %.*s", + (ulong)keyid[0], (ulong)keyid[1], + r->len, r->name ); + return p; + } + } + } + } while( ++pass < 2 && !get_pubkey( NULL, keyid ) ); + p = xmalloc( 25 ); + sprintf(p, "%08lX%08lX [?]", (ulong)keyid[0], (ulong)keyid[1] ); + return p; +} + +char* +get_user_id( u32 *keyid, size_t *rn ) +{ + user_id_db_t r; + char *p; + int pass=0; + + /* try it two times; second pass reads from key resources */ + do { + for(r=user_id_db; r; r = r->next ) { + keyid_list_t a; + for (a=r->keyids; a; a= a->next ) { + if( a->keyid[0] == keyid[0] && a->keyid[1] == keyid[1] ) { + p = xmalloc( r->len ); + memcpy(p, r->name, r->len ); + *rn = r->len; + return p; + } + } + } + } while( ++pass < 2 && !get_pubkey( NULL, keyid ) ); + p = xstrdup( user_id_not_found_utf8 () ); + *rn = strlen(p); + return p; +} + +char* +get_user_id_native( u32 *keyid ) +{ + size_t rn; + char *p = get_user_id( keyid, &rn ); + char *p2 = utf8_to_native( p, rn, 0 ); + xfree(p); + return p2; +} + +KEYDB_HANDLE +get_ctx_handle(GETKEY_CTX ctx) +{ + return ctx->kr_handle; +} + +static void +free_akl(struct akl *akl) +{ + if(akl->spec) + free_keyserver_spec(akl->spec); + + xfree(akl); +} + +void +release_akl(void) +{ + while(opt.auto_key_locate) + { + struct akl *akl2=opt.auto_key_locate; + opt.auto_key_locate=opt.auto_key_locate->next; + free_akl(akl2); + } +} + +int +parse_auto_key_locate(char *options) +{ + char *tok; + + while((tok=optsep(&options))) + { + struct akl *akl,*check,*last=NULL; + int dupe=0; + + if(tok[0]=='\0') + continue; + + akl=xmalloc_clear(sizeof(*akl)); + + if(ascii_strcasecmp(tok,"ldap")==0) + akl->type=AKL_LDAP; + else if(ascii_strcasecmp(tok,"keyserver")==0) + akl->type=AKL_KEYSERVER; +#ifdef USE_DNS_CERT + else if(ascii_strcasecmp(tok,"cert")==0) + akl->type=AKL_CERT; +#endif +#ifdef USE_DNS_PKA + else if(ascii_strcasecmp(tok,"pka")==0) + akl->type=AKL_PKA; +#endif + else if((akl->spec=parse_keyserver_uri(tok,1,NULL,0))) + akl->type=AKL_SPEC; + else + { + free_akl(akl); + return 0; + } + + /* We must maintain the order the user gave us */ + for(check=opt.auto_key_locate;check;last=check,check=check->next) + { + /* Check for duplicates */ + if(check->type==akl->type + && (akl->type!=AKL_SPEC + || (akl->type==AKL_SPEC + && strcmp(check->spec->uri,akl->spec->uri)==0))) + { + dupe=1; + free_akl(akl); + break; + } + } + + if(!dupe) + { + if(last) + last->next=akl; + else + opt.auto_key_locate=akl; + } + } + + return 1; +} diff --git a/g10/global.h b/g10/global.h new file mode 100644 index 0000000..1b2f872 --- /dev/null +++ b/g10/global.h @@ -0,0 +1,30 @@ +/* global.h - Local typedefs and constants + * Copyright (C) 2001 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#ifndef GPG_GLOBAL_H +#define GPG_GLOBAL_H + +#define MAX_FINGERPRINT_LEN 20 + +typedef struct kbnode_struct *KBNODE; +typedef struct keydb_search_desc KEYDB_SEARCH_DESC; + +#endif /*GPG_GLOBAL_H*/ diff --git a/g10/gpg.c b/g10/gpg.c new file mode 100644 index 0000000..691cfff --- /dev/null +++ b/g10/gpg.c @@ -0,0 +1,4207 @@ +/* gpg.c - The GnuPG utility (main for gpg) + * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, + * 2006 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#include <config.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <unistd.h> +#include <assert.h> +#ifdef HAVE_DOSISH_SYSTEM +#include <fcntl.h> /* for setmode() */ +#endif +#ifdef HAVE_STAT +#include <sys/stat.h> /* for stat() */ +#endif +#include <fcntl.h> +#ifdef HAVE_W32_SYSTEM +#include <windows.h> +#endif + +#define INCLUDED_BY_MAIN_MODULE 1 +#include "packet.h" +#include "iobuf.h" +#include "memory.h" +#include "util.h" +#include "main.h" +#include "options.h" +#include "keydb.h" +#include "trustdb.h" +#include "mpi.h" +#include "cipher.h" +#include "filter.h" +#include "ttyio.h" +#include "i18n.h" +#include "status.h" +#include "g10defs.h" +#include "keyserver-internal.h" +#include "exec.h" +#include "cardglue.h" +#ifdef ENABLE_CARD_SUPPORT +#include "ccid-driver.h" +#endif + +#if defined(HAVE_DOSISH_SYSTEM) || defined(__CYGWIN__) +#define MY_O_BINARY O_BINARY +#ifndef S_IRGRP +# define S_IRGRP 0 +# define S_IWGRP 0 +#endif +#else +#define MY_O_BINARY 0 +#endif + + +enum cmd_and_opt_values + { + aNull = 0, + oArmor = 'a', + aDetachedSign = 'b', + aSym = 'c', + aDecrypt = 'd', + aEncr = 'e', + oInteractive = 'i', + oKOption = 'k', + oDryRun = 'n', + oOutput = 'o', + oQuiet = 'q', + oRecipient = 'r', + oHiddenRecipient = 'R', + aSign = 's', + oTextmodeShort= 't', + oLocalUser = 'u', + oVerbose = 'v', + oCompress = 'z', + oSetNotation = 'N', + aListSecretKeys = 'K', + oBatch = 500, + oMaxOutput, + oSigNotation, + oCertNotation, + oShowNotation, + oNoShowNotation, + aEncrFiles, + aEncrSym, + aDecryptFiles, + aClearsign, + aStore, + aKeygen, + aSignEncr, + aSignEncrSym, + aSignSym, + aSignKey, + aLSignKey, + aListConfig, + aGPGConfList, + aGPGConfTest, + aListPackets, + aEditKey, + aDeleteKeys, + aDeleteSecretKeys, + aDeleteSecretAndPublicKeys, + aKMode, + aKModeC, + aImport, + aFastImport, + aVerify, + aVerifyFiles, + aListKeys, + aListSigs, + aSendKeys, + aRecvKeys, + aSearchKeys, + aRefreshKeys, + aFetchKeys, + aExport, + aExportSecret, + aExportSecretSub, + aCheckKeys, + aGenRevoke, + aDesigRevoke, + aPrimegen, + aPrintMD, + aPrintMDs, + aCheckTrustDB, + aUpdateTrustDB, + aFixTrustDB, + aListTrustDB, + aListTrustPath, + aExportOwnerTrust, + aListOwnerTrust, + aImportOwnerTrust, + aDeArmor, + aEnArmor, + aGenRandom, + aPipeMode, + aRebuildKeydbCaches, + aCardStatus, + aCardEdit, + aChangePIN, + + oTextmode, + oNoTextmode, + oExpert, + oNoExpert, + oDefSigExpire, + oAskSigExpire, + oNoAskSigExpire, + oDefCertExpire, + oAskCertExpire, + oNoAskCertExpire, + oDefCertLevel, + oMinCertLevel, + oAskCertLevel, + oNoAskCertLevel, + oFingerprint, + oWithFingerprint, + oAnswerYes, + oAnswerNo, + oKeyring, + oPrimaryKeyring, + oSecretKeyring, + oShowKeyring, + oDefaultKey, + oDefRecipient, + oDefRecipientSelf, + oNoDefRecipient, + oOptions, + oDebug, + oDebugAll, + oDebugCCIDDriver, + oStatusFD, + oStatusFile, + oAttributeFD, + oAttributeFile, + oEmitVersion, + oNoEmitVersion, + oCompletesNeeded, + oMarginalsNeeded, + oMaxCertDepth, + oLoadExtension, + oGnuPG, + oRFC1991, + oRFC2440, + oOpenPGP, + oPGP2, + oPGP6, + oPGP7, + oPGP8, + oRFC2440Text, + oNoRFC2440Text, + oCipherAlgo, + oDigestAlgo, + oCertDigestAlgo, + oCompressAlgo, + oCompressLevel, + oBZ2CompressLevel, + oBZ2DecompressLowmem, + oPasswd, + oPasswdFD, + oPasswdFile, + oPasswdRepeat, + oCommandFD, + oCommandFile, + oQuickRandom, + oNoVerbose, + oTrustDBName, + oNoSecmemWarn, + oRequireSecmem, + oNoRequireSecmem, + oNoPermissionWarn, + oNoMDCWarn, + oNoArmor, + oNoDefKeyring, + oNoGreeting, + oNoTTY, + oNoOptions, + oNoBatch, + oHomedir, + oWithColons, + oWithKeyData, + oSkipVerify, + oCompressKeys, + oCompressSigs, + oAlwaysTrust, + oTrustModel, + oForceOwnertrust, + oRunAsShmCP, + oSetFilename, + oForYourEyesOnly, + oNoForYourEyesOnly, + oSetPolicyURL, + oSigPolicyURL, + oCertPolicyURL, + oShowPolicyURL, + oNoShowPolicyURL, + oSigKeyserverURL, + oUseEmbeddedFilename, + oNoUseEmbeddedFilename, + oComment, + oDefaultComment, + oNoComments, + oThrowKeyids, + oNoThrowKeyids, + oShowPhotos, + oNoShowPhotos, + oPhotoViewer, + oForceV3Sigs, + oNoForceV3Sigs, + oForceV4Certs, + oNoForceV4Certs, + oForceMDC, + oNoForceMDC, + oDisableMDC, + oNoDisableMDC, + oS2KMode, + oS2KDigest, + oS2KCipher, + oS2KCount, + oSimpleSKChecksum, + oDisplayCharset, + oNotDashEscaped, + oEscapeFrom, + oNoEscapeFrom, + oLockOnce, + oLockMultiple, + oLockNever, + oKeyServer, + oKeyServerOptions, + oImportOptions, + oExportOptions, + oListOptions, + oVerifyOptions, + oTempDir, + oExecPath, + oEncryptTo, + oHiddenEncryptTo, + oNoEncryptTo, + oLoggerFD, + oLoggerFile, + oUtf8Strings, + oNoUtf8Strings, + oDisableCipherAlgo, + oDisablePubkeyAlgo, + oAllowNonSelfsignedUID, + oNoAllowNonSelfsignedUID, + oAllowFreeformUID, + oNoAllowFreeformUID, + oAllowSecretKeyImport, + oEnableSpecialFilenames, + oNoLiteral, + oSetFilesize, + oHonorHttpProxy, + oFastListMode, + oListOnly, + oIgnoreTimeConflict, + oIgnoreValidFrom, + oIgnoreCrcError, + oIgnoreMDCError, + oShowSessionKey, + oOverrideSessionKey, + oNoRandomSeedFile, + oAutoKeyRetrieve, + oNoAutoKeyRetrieve, + oUseAgent, + oNoUseAgent, + oGpgAgentInfo, + oMergeOnly, + oTryAllSecrets, + oTrustedKey, + oNoExpensiveTrustChecks, + oFixedListMode, + oNoSigCache, + oNoSigCreateCheck, + oAutoCheckTrustDB, + oNoAutoCheckTrustDB, + oPreservePermissions, + oDefaultPreferenceList, + oDefaultKeyserverURL, + oPersonalCipherPreferences, + oPersonalDigestPreferences, + oPersonalCompressPreferences, + oDisplay, + oTTYname, + oTTYtype, + oLCctype, + oLCmessages, + oGroup, + oUnGroup, + oNoGroups, + oStrict, + oNoStrict, + oMangleDosFilenames, + oNoMangleDosFilenames, + oEnableProgressFilter, + oMultifile, + oKeyidFormat, + oExitOnStatusWriteError, + oLimitCardInsertTries, + oReaderPort, + octapiDriver, + opcscDriver, + oDisableCCID, + oRequireCrossCert, + oNoRequireCrossCert, + oAutoKeyLocate, + oNoAutoKeyLocate, + oAllowMultisigVerification, + oEnableDSA2, + oDisableDSA2, + + oNoop + }; + + +static ARGPARSE_OPTS opts[] = { + + { 300, NULL, 0, N_("@Commands:\n ") }, + + { aSign, "sign", 256, N_("|[file]|make a signature")}, + { aClearsign, "clearsign", 256, N_("|[file]|make a clear text signature")}, + { aDetachedSign, "detach-sign", 256, N_("make a detached signature")}, + { aEncr, "encrypt", 256, N_("encrypt data")}, + { aEncrFiles, "encrypt-files", 256, "@"}, + { aSym, "symmetric", 256, N_("encryption only with symmetric cipher")}, + { aStore, "store", 256, "@"}, + { aDecrypt, "decrypt", 256, N_("decrypt data (default)")}, + { aDecryptFiles, "decrypt-files", 256, "@"}, + { aVerify, "verify" , 256, N_("verify a signature")}, + { aVerifyFiles, "verify-files" , 256, "@" }, + { aListKeys, "list-keys", 256, N_("list keys")}, + { aListKeys, "list-public-keys", 256, "@" }, + { aListSigs, "list-sigs", 256, N_("list keys and signatures")}, + { aCheckKeys, "check-sigs",256, N_("list and check key signatures")}, + { oFingerprint, "fingerprint", 256, N_("list keys and fingerprints")}, + { aListSecretKeys, "list-secret-keys", 256, N_("list secret keys")}, + { aKeygen, "gen-key", 256, N_("generate a new key pair")}, + { aDeleteKeys,"delete-keys",256,N_("remove keys from the public keyring")}, + { aDeleteSecretKeys, "delete-secret-keys",256, + N_("remove keys from the secret keyring")}, + { aSignKey, "sign-key" ,256, N_("sign a key")}, + { aLSignKey, "lsign-key" ,256, N_("sign a key locally")}, + { aEditKey, "edit-key" ,256, N_("sign or edit a key")}, + { aGenRevoke, "gen-revoke",256, N_("generate a revocation certificate")}, + { aDesigRevoke, "desig-revoke",256, "@" }, + { aExport, "export" , 256, N_("export keys") }, + { aSendKeys, "send-keys" , 256, N_("export keys to a key server") }, + { aRecvKeys, "recv-keys" , 256, N_("import keys from a key server") }, + { aSearchKeys, "search-keys" , 256, + N_("search for keys on a key server") }, + { aRefreshKeys, "refresh-keys", 256, + N_("update all keys from a keyserver")}, + { aFetchKeys, "fetch-keys" , 256, "@" }, + { aExportSecret, "export-secret-keys" , 256, "@" }, + { aExportSecretSub, "export-secret-subkeys" , 256, "@" }, + { aImport, "import", 256 , N_("import/merge keys")}, + { aFastImport, "fast-import", 256 , "@"}, +#ifdef ENABLE_CARD_SUPPORT + { aCardStatus, "card-status", 256, N_("print the card status")}, + { aCardEdit, "card-edit", 256, N_("change data on a card")}, + { aChangePIN, "change-pin", 256, N_("change a card's PIN")}, +#endif + { aListConfig, "list-config", 256, "@"}, + { aGPGConfList, "gpgconf-list", 256, "@" }, + { aGPGConfTest, "gpgconf-test", 256, "@" }, + { aListPackets, "list-packets",256, "@"}, + { aExportOwnerTrust, "export-ownertrust", 256, "@"}, + { aImportOwnerTrust, "import-ownertrust", 256, "@"}, + { aUpdateTrustDB, + "update-trustdb",0 , N_("update the trust database")}, + { aCheckTrustDB, "check-trustdb", 0, "@"}, + { aFixTrustDB, "fix-trustdb", 0, "@"}, + { aDeArmor, "dearmor", 256, "@"}, + { aDeArmor, "dearmour", 256, "@"}, + { aEnArmor, "enarmor", 256, "@"}, + { aEnArmor, "enarmour", 256, "@"}, + { aPrintMD, "print-md" , 256, N_("|algo [files]|print message digests")}, + { aPrimegen, "gen-prime" , 256, "@" }, + { aGenRandom, "gen-random" , 256, "@" }, + + { 301, NULL, 0, N_("@\nOptions:\n ") }, + + { oArmor, "armor", 0, N_("create ascii armored output")}, + { oArmor, "armour", 0, "@" }, + { oRecipient, "recipient", 2, N_("|NAME|encrypt for NAME")}, + { oHiddenRecipient, "hidden-recipient", 2, "@" }, + { oRecipient, "remote-user", 2, "@"}, /* old option name */ + { oDefRecipient, "default-recipient", 2, "@"}, + { oDefRecipientSelf, "default-recipient-self", 0, "@"}, + { oNoDefRecipient, "no-default-recipient", 0, "@" }, + { oTempDir, "temp-directory", 2, "@" }, + { oExecPath, "exec-path", 2, "@" }, + { oEncryptTo, "encrypt-to", 2, "@" }, + { oHiddenEncryptTo, "hidden-encrypt-to", 2, "@" }, + { oNoEncryptTo, "no-encrypt-to", 0, "@" }, + { oLocalUser, "local-user",2, N_("use this user-id to sign or decrypt")}, + { oCompress, NULL, 1, N_("|N|set compress level N (0 disables)") }, + { oCompressLevel, "compress-level", 1, "@" }, + { oBZ2CompressLevel, "bzip2-compress-level", 1, "@" }, + { oBZ2DecompressLowmem, "bzip2-decompress-lowmem", 0, "@" }, + { oTextmodeShort, NULL, 0, "@"}, + { oTextmode, "textmode", 0, N_("use canonical text mode")}, + { oNoTextmode, "no-textmode", 0, "@"}, + { oExpert, "expert", 0, "@"}, + { oNoExpert, "no-expert", 0, "@"}, + { oDefSigExpire, "default-sig-expire", 2, "@"}, + { oAskSigExpire, "ask-sig-expire", 0, "@"}, + { oNoAskSigExpire, "no-ask-sig-expire", 0, "@"}, + { oDefCertExpire, "default-cert-expire", 2, "@"}, + { oAskCertExpire, "ask-cert-expire", 0, "@"}, + { oNoAskCertExpire, "no-ask-cert-expire", 0, "@"}, + { oDefCertLevel, "default-cert-level", 1, "@"}, + { oMinCertLevel, "min-cert-level", 1, "@"}, + { oAskCertLevel, "ask-cert-level", 0, "@"}, + { oNoAskCertLevel, "no-ask-cert-level", 0, "@"}, + { oOutput, "output", 2, N_("use as output file")}, + { oMaxOutput, "max-output", 16|4, "@" }, + { oVerbose, "verbose", 0, N_("verbose") }, + { oQuiet, "quiet", 0, "@"}, + { oNoTTY, "no-tty", 0, "@"}, + { oForceV3Sigs, "force-v3-sigs", 0, "@"}, + { oNoForceV3Sigs, "no-force-v3-sigs", 0, "@"}, + { oForceV4Certs, "force-v4-certs", 0, "@"}, + { oNoForceV4Certs, "no-force-v4-certs", 0, "@"}, + { oForceMDC, "force-mdc", 0, "@"}, + { oNoForceMDC, "no-force-mdc", 0, "@" }, + { oDisableMDC, "disable-mdc", 0, "@"}, + { oNoDisableMDC, "no-disable-mdc", 0, "@" }, + { oDryRun, "dry-run", 0, N_("do not make any changes") }, + { oInteractive, "interactive", 0, N_("prompt before overwriting") }, + { oUseAgent, "use-agent",0, "@"}, + { oNoUseAgent, "no-use-agent",0, "@"}, + { oGpgAgentInfo, "gpg-agent-info",2, "@"}, + { oBatch, "batch", 0, "@"}, + { oAnswerYes, "yes", 0, "@"}, + { oAnswerNo, "no", 0, "@"}, + { oKeyring, "keyring", 2, "@"}, + { oPrimaryKeyring, "primary-keyring",2, "@" }, + { oSecretKeyring, "secret-keyring", 2, "@"}, + { oShowKeyring, "show-keyring", 0, "@"}, + { oDefaultKey, "default-key", 2, "@"}, + { oKeyServer, "keyserver", 2, "@"}, + { oKeyServerOptions, "keyserver-options",2,"@"}, + { oImportOptions, "import-options",2,"@"}, + { oExportOptions, "export-options",2,"@"}, + { oListOptions, "list-options",2,"@"}, + { oVerifyOptions, "verify-options",2,"@"}, + { oDisplayCharset, "display-charset", 2, "@"}, + { oDisplayCharset, "charset", 2, "@"}, + { oOptions, "options", 2, "@"}, + { oDebug, "debug" ,4|16, "@"}, + { oDebugAll, "debug-all" ,0, "@"}, + { oStatusFD, "status-fd" ,1, "@"}, + { oStatusFile, "status-file" ,2, "@"}, + { oAttributeFD, "attribute-fd" ,1, "@" }, + { oAttributeFile, "attribute-file" ,2, "@" }, + { oNoop, "sk-comments", 0, "@"}, + { oNoop, "no-sk-comments", 0, "@"}, + { oCompletesNeeded, "completes-needed", 1, "@"}, + { oMarginalsNeeded, "marginals-needed", 1, "@"}, + { oMaxCertDepth, "max-cert-depth", 1, "@" }, + { oTrustedKey, "trusted-key", 2, "@"}, + { oLoadExtension, "load-extension", 2, "@"}, + { oGnuPG, "gnupg", 0, "@"}, + { oGnuPG, "no-pgp2", 0, "@"}, + { oGnuPG, "no-pgp6", 0, "@"}, + { oGnuPG, "no-pgp7", 0, "@"}, + { oGnuPG, "no-pgp8", 0, "@"}, + { oRFC1991, "rfc1991", 0, "@"}, + { oRFC2440, "rfc2440", 0, "@" }, + { oOpenPGP, "openpgp", 0, N_("use strict OpenPGP behavior")}, + { oPGP2, "pgp2", 0, N_("generate PGP 2.x compatible messages")}, + { oPGP6, "pgp6", 0, "@"}, + { oPGP7, "pgp7", 0, "@"}, + { oPGP8, "pgp8", 0, "@"}, + { oRFC2440Text, "rfc2440-text", 0, "@"}, + { oNoRFC2440Text, "no-rfc2440-text", 0, "@"}, + { oS2KMode, "s2k-mode", 1, "@"}, + { oS2KDigest, "s2k-digest-algo", 2, "@"}, + { oS2KCipher, "s2k-cipher-algo", 2, "@"}, + { oS2KCount, "s2k-count", 1, "@"}, + { oSimpleSKChecksum, "simple-sk-checksum", 0, "@"}, + { oCipherAlgo, "cipher-algo", 2, "@"}, + { oDigestAlgo, "digest-algo", 2, "@"}, + { oCertDigestAlgo, "cert-digest-algo", 2 , "@" }, + { oCompressAlgo,"compress-algo", 2, "@"}, + { oCompressAlgo, "compression-algo", 2, "@"}, /* Alias */ + { oThrowKeyids, "throw-keyid", 0, "@"}, + { oThrowKeyids, "throw-keyids", 0, "@"}, + { oNoThrowKeyids, "no-throw-keyid", 0, "@" }, + { oNoThrowKeyids, "no-throw-keyids", 0, "@" }, + { oShowPhotos, "show-photos", 0, "@" }, + { oNoShowPhotos, "no-show-photos", 0, "@" }, + { oPhotoViewer, "photo-viewer", 2, "@" }, + { oSetNotation, "set-notation", 2, "@" }, + { oSetNotation, "notation-data", 2, "@" }, /* Alias */ + { oSigNotation, "sig-notation", 2, "@" }, + { oCertNotation, "cert-notation", 2, "@" }, + + { 302, NULL, 0, N_( + "@\n(See the man page for a complete listing of all commands and options)\n" + )}, + + { 303, NULL, 0, N_("@\nExamples:\n\n" + " -se -r Bob [file] sign and encrypt for user Bob\n" + " --clearsign [file] make a clear text signature\n" + " --detach-sign [file] make a detached signature\n" + " --list-keys [names] show keys\n" + " --fingerprint [names] show fingerprints\n" ) }, + + /* hidden options */ + { aListOwnerTrust, "list-ownertrust", 256, "@"}, /* deprecated */ + { aPrintMDs, "print-mds" , 256, "@"}, /* old */ + { aListTrustDB, "list-trustdb",0 , "@"}, + /* Not yet used */ + /* { aListTrustPath, "list-trust-path",0, "@"}, */ + { aPipeMode, "pipemode", 0, "@" }, + { oKOption, NULL, 0, "@"}, + { oPasswd, "passphrase",2, "@" }, + { oPasswdFD, "passphrase-fd",1, "@" }, + { oPasswdFile, "passphrase-file",2, "@" }, + { oPasswdRepeat, "passphrase-repeat", 1, "@"}, + { oCommandFD, "command-fd",1, "@" }, + { oCommandFile, "command-file",2, "@" }, + { oQuickRandom, "quick-random", 0, "@"}, + { oNoVerbose, "no-verbose", 0, "@"}, + { oTrustDBName, "trustdb-name", 2, "@" }, + { oNoSecmemWarn, "no-secmem-warning", 0, "@" }, + { oRequireSecmem,"require-secmem", 0, "@" }, + { oNoRequireSecmem,"no-require-secmem", 0, "@" }, + { oNoPermissionWarn, "no-permission-warning", 0, "@" }, + { oNoMDCWarn, "no-mdc-warning", 0, "@" }, + { oNoArmor, "no-armor", 0, "@"}, + { oNoArmor, "no-armour", 0, "@"}, + { oNoDefKeyring, "no-default-keyring", 0, "@" }, + { oNoGreeting, "no-greeting", 0, "@" }, + { oNoOptions, "no-options", 0, "@" }, /* shortcut for --options /dev/null */ + { oHomedir, "homedir", 2, "@" }, /* defaults to "~/.gnupg" */ + { oNoBatch, "no-batch", 0, "@" }, + { oWithColons, "with-colons", 0, "@"}, + { oWithKeyData,"with-key-data", 0, "@"}, + { aListKeys, "list-key", 0, "@" }, /* alias */ + { aListSigs, "list-sig", 0, "@" }, /* alias */ + { aCheckKeys, "check-sig",0, "@" }, /* alias */ + { oSkipVerify, "skip-verify",0, "@" }, + { oCompressKeys, "compress-keys",0, "@"}, + { oCompressSigs, "compress-sigs",0, "@"}, + { oDefCertLevel, "default-cert-check-level", 1, "@"}, /* Old option */ + { oAlwaysTrust, "always-trust", 0, "@"}, + { oTrustModel, "trust-model", 2, "@"}, + { oForceOwnertrust, "force-ownertrust", 2, "@"}, + { oRunAsShmCP, "run-as-shm-coprocess", 4, "@" }, + { oSetFilename, "set-filename", 2, "@" }, + { oForYourEyesOnly, "for-your-eyes-only", 0, "@" }, + { oNoForYourEyesOnly, "no-for-your-eyes-only", 0, "@" }, + { oSetPolicyURL, "set-policy-url", 2, "@" }, + { oSigPolicyURL, "sig-policy-url", 2, "@" }, + { oCertPolicyURL, "cert-policy-url", 2, "@" }, + { oShowPolicyURL, "show-policy-url", 0, "@" }, + { oNoShowPolicyURL, "no-show-policy-url", 0, "@" }, + { oSigKeyserverURL, "sig-keyserver-url", 2, "@" }, + { oShowNotation, "show-notation", 0, "@" }, + { oNoShowNotation, "no-show-notation", 0, "@" }, + { oComment, "comment", 2, "@" }, + { oDefaultComment, "default-comment", 0, "@" }, + { oNoComments, "no-comments", 0, "@" }, + { oEmitVersion, "emit-version", 0, "@"}, + { oNoEmitVersion, "no-emit-version", 0, "@"}, + { oNoEmitVersion, "no-version", 0, "@"}, /* alias */ + { oNotDashEscaped, "not-dash-escaped", 0, "@" }, + { oEscapeFrom, "escape-from-lines", 0, "@" }, + { oNoEscapeFrom, "no-escape-from-lines", 0, "@" }, + { oLockOnce, "lock-once", 0, "@" }, + { oLockMultiple, "lock-multiple", 0, "@" }, + { oLockNever, "lock-never", 0, "@" }, + { oLoggerFD, "logger-fd",1, "@" }, + { oLoggerFile, "logger-file",2, "@" }, + { oUseEmbeddedFilename, "use-embedded-filename", 0, "@" }, + { oNoUseEmbeddedFilename, "no-use-embedded-filename", 0, "@" }, + { oUtf8Strings, "utf8-strings", 0, "@" }, + { oNoUtf8Strings, "no-utf8-strings", 0, "@" }, + { oWithFingerprint, "with-fingerprint", 0, "@" }, + { oDisableCipherAlgo, "disable-cipher-algo", 2, "@" }, + { oDisablePubkeyAlgo, "disable-pubkey-algo", 2, "@" }, + { oAllowNonSelfsignedUID, "allow-non-selfsigned-uid", 0, "@" }, + { oNoAllowNonSelfsignedUID, "no-allow-non-selfsigned-uid", 0, "@" }, + { oAllowFreeformUID, "allow-freeform-uid", 0, "@" }, + { oNoAllowFreeformUID, "no-allow-freeform-uid", 0, "@" }, + { oNoLiteral, "no-literal", 0, "@" }, + { oSetFilesize, "set-filesize", 20, "@" }, + { oHonorHttpProxy,"honor-http-proxy", 0, "@" }, + { oFastListMode,"fast-list-mode", 0, "@" }, + { oFixedListMode,"fixed-list-mode", 0, "@" }, + { oListOnly, "list-only", 0, "@"}, + { oIgnoreTimeConflict, "ignore-time-conflict", 0, "@" }, + { oIgnoreValidFrom, "ignore-valid-from", 0, "@" }, + { oIgnoreCrcError, "ignore-crc-error", 0,"@" }, + { oIgnoreMDCError, "ignore-mdc-error", 0,"@" }, + { oShowSessionKey, "show-session-key", 0, "@" }, + { oOverrideSessionKey, "override-session-key", 2, "@" }, + { oNoRandomSeedFile, "no-random-seed-file", 0, "@" }, + { oAutoKeyRetrieve, "auto-key-retrieve", 0, "@" }, + { oNoAutoKeyRetrieve, "no-auto-key-retrieve", 0, "@" }, + { oNoSigCache, "no-sig-cache", 0, "@" }, + { oNoSigCreateCheck, "no-sig-create-check", 0, "@" }, + { oAutoCheckTrustDB, "auto-check-trustdb", 0, "@"}, + { oNoAutoCheckTrustDB, "no-auto-check-trustdb", 0, "@"}, + { oMergeOnly, "merge-only", 0, "@" }, + { oAllowSecretKeyImport, "allow-secret-key-import", 0, "@" }, + { oTryAllSecrets, "try-all-secrets", 0, "@" }, + { oEnableSpecialFilenames, "enable-special-filenames", 0, "@" }, + { oNoExpensiveTrustChecks, "no-expensive-trust-checks", 0, "@" }, + { aDeleteSecretAndPublicKeys, "delete-secret-and-public-keys",256, "@" }, + { aRebuildKeydbCaches, "rebuild-keydb-caches", 256, "@"}, + { oPreservePermissions, "preserve-permissions", 0, "@"}, + { oDefaultPreferenceList, "default-preference-list", 2, "@"}, + { oDefaultKeyserverURL, "default-keyserver-url", 2, "@"}, + { oPersonalCipherPreferences, "personal-cipher-preferences", 2, "@"}, + { oPersonalDigestPreferences, "personal-digest-preferences", 2, "@"}, + { oPersonalCompressPreferences, "personal-compress-preferences", 2, "@"}, + /* Aliases. I constantly mistype these, and assume other people + do as well. */ + { oPersonalCipherPreferences, "personal-cipher-prefs", 2, "@"}, + { oPersonalDigestPreferences, "personal-digest-prefs", 2, "@"}, + { oPersonalCompressPreferences, "personal-compress-prefs", 2, "@"}, + { oDisplay, "display", 2, "@" }, + { oTTYname, "ttyname", 2, "@" }, + { oTTYtype, "ttytype", 2, "@" }, + { oLCctype, "lc-ctype", 2, "@" }, + { oLCmessages, "lc-messages", 2, "@" }, + { oGroup, "group", 2, "@" }, + { oUnGroup, "ungroup", 2, "@" }, + { oNoGroups, "no-groups", 0, "@" }, + { oStrict, "strict", 0, "@" }, + { oNoStrict, "no-strict", 0, "@" }, + { oMangleDosFilenames, "mangle-dos-filenames", 0, "@" }, + { oNoMangleDosFilenames, "no-mangle-dos-filenames", 0, "@" }, + { oEnableProgressFilter, "enable-progress-filter", 0, "@" }, + { oMultifile, "multifile", 0, "@" }, + { oKeyidFormat, "keyid-format", 2, "@" }, + { oExitOnStatusWriteError, "exit-on-status-write-error", 0, "@" }, + { oLimitCardInsertTries, "limit-card-insert-tries", 1, "@"}, + + { oReaderPort, "reader-port", 2, "@"}, + { octapiDriver, "ctapi-driver", 2, "@"}, + { opcscDriver, "pcsc-driver", 2, "@"}, + { oDisableCCID, "disable-ccid", 0, "@"}, +#if defined(ENABLE_CARD_SUPPORT) && defined(HAVE_LIBUSB) + { oDebugCCIDDriver, "debug-ccid-driver", 0, "@"}, +#endif + { oAllowMultisigVerification, "allow-multisig-verification", 0, "@"}, + { oEnableDSA2, "enable-dsa2", 0, "@"}, + { oDisableDSA2, "disable-dsa2", 0, "@"}, + + /* These two are aliases to help users of the PGP command line + product use gpg with minimal pain. Many commands are common + already as they seem to have borrowed commands from us. Now + I'm returning the favor. */ + { oLocalUser, "sign-with", 2, "@" }, + { oRecipient, "user", 2, "@" }, + { oRequireCrossCert, "require-backsigs", 0, "@"}, + { oRequireCrossCert, "require-cross-certification", 0, "@"}, + { oNoRequireCrossCert, "no-require-backsigs", 0, "@"}, + { oNoRequireCrossCert, "no-require-cross-certification", 0, "@"}, + { oAutoKeyLocate, "auto-key-locate", 2, "@"}, + { oNoAutoKeyLocate, "no-auto-key-locate", 0, "@"}, + {0,NULL,0,NULL} +}; + + +#ifdef ENABLE_SELINUX_HACKS +#define ALWAYS_ADD_KEYRINGS 1 +#else +#define ALWAYS_ADD_KEYRINGS 0 +#endif + + +int g10_errors_seen = 0; + +static int utf8_strings = 0; +static int maybe_setuid = 1; + +static char *build_list( const char *text, char letter, + const char *(*mapf)(int), int (*chkf)(int) ); +static void set_cmd( enum cmd_and_opt_values *ret_cmd, + enum cmd_and_opt_values new_cmd ); +static void print_mds( const char *fname, int algo ); +static void add_notation_data( const char *string, int which ); +static void add_policy_url( const char *string, int which ); +static void add_keyserver_url( const char *string, int which ); + +const char * +strusage( int level ) +{ + static char *digests, *pubkeys, *ciphers, *zips; + const char *p; + switch( level ) { + case 11: p = "gpg (GnuPG)"; + break; + case 13: p = VERSION; break; + case 17: p = PRINTABLE_OS_NAME; break; + case 19: p = + _("Please report bugs to <gnupg-bugs@gnu.org>.\n"); + break; + +#ifdef IS_DEVELOPMENT_VERSION + case 20: + p="NOTE: THIS IS A DEVELOPMENT VERSION!"; + break; + case 21: + p="It is only intended for test purposes and should NOT be"; + break; + case 22: + p="used in a production environment or with production keys!"; + break; +#endif + + case 1: + case 40: p = + _("Usage: gpg [options] [files] (-h for help)"); + break; + case 41: p = + _("Syntax: gpg [options] [files]\n" + "sign, check, encrypt or decrypt\n" + "default operation depends on the input data\n"); + break; + + case 31: p = "\nHome: "; break; +#ifndef __riscos__ + case 32: p = opt.homedir; break; +#else /* __riscos__ */ + case 32: p = make_filename(opt.homedir, NULL); break; +#endif /* __riscos__ */ + case 33: p = _("\nSupported algorithms:\n"); break; + case 34: + if( !pubkeys ) + pubkeys = build_list(_("Pubkey: "), 0, pubkey_algo_to_string, + check_pubkey_algo ); + p = pubkeys; + break; + case 35: + if( !ciphers ) + ciphers = build_list(_("Cipher: "), 'S', cipher_algo_to_string, + check_cipher_algo ); + p = ciphers; + break; + case 36: + if( !digests ) + digests = build_list(_("Hash: "), 'H', digest_algo_to_string, + check_digest_algo ); + p = digests; + break; + case 37: + if( !zips ) + zips = build_list(_("Compression: "),'Z',compress_algo_to_string, + check_compress_algo); + p = zips; + break; + + default: p = default_strusage(level); + } + return p; +} + + +static char * +build_list( const char *text, char letter, + const char * (*mapf)(int), int (*chkf)(int) ) +{ + int i; + const char *s; + size_t n=strlen(text)+2; + char *list, *p, *line=NULL; + + if( maybe_setuid ) + secmem_init( 0 ); /* drop setuid */ + + for(i=0; i <= 110; i++ ) + if( !chkf(i) && (s=mapf(i)) ) + n += strlen(s) + 7 + 2; + list = xmalloc( 21 + n ); *list = 0; + for(p=NULL, i=0; i <= 110; i++ ) { + if( !chkf(i) && (s=mapf(i)) ) { + if( !p ) { + p = stpcpy( list, text ); + line=p; + } + else + p = stpcpy( p, ", "); + + if(strlen(line)>60) { + int spaces=strlen(text); + + list=xrealloc(list,n+spaces+1); + /* realloc could move the block, so find the end again */ + p=list; + while(*p) + p++; + + p=stpcpy(p, "\n"); + line=p; + for(;spaces;spaces--) + p=stpcpy(p, " "); + } + + p = stpcpy(p, s ); + if(opt.verbose && letter) + { + char num[8]; + sprintf(num," (%c%d)",letter,i); + p = stpcpy(p,num); + } + } + } + if( p ) + p = stpcpy(p, "\n" ); + return list; +} + + +static void +i18n_init(void) +{ +#ifdef USE_SIMPLE_GETTEXT + set_gettext_file (PACKAGE, "Software\\GNU\\GnuPG"); +#else +#ifdef ENABLE_NLS + setlocale( LC_ALL, "" ); + bindtextdomain( PACKAGE, G10_LOCALEDIR ); + textdomain( PACKAGE ); +#endif +#endif +} + +static void +wrong_args( const char *text) +{ + fputs(_("usage: gpg [options] "),stderr); + fputs(text,stderr); + putc('\n',stderr); + g10_exit(2); +} + + +static char * +make_username( const char *string ) +{ + char *p; + if( utf8_strings ) + p = xstrdup(string); + else + p = native_to_utf8( string ); + return p; +} + + +static void +set_debug(void) +{ + if( opt.debug & DBG_MEMORY_VALUE ) + memory_debug_mode = 1; + if( opt.debug & DBG_MEMSTAT_VALUE ) + memory_stat_debug_mode = 1; + if( opt.debug & DBG_MPI_VALUE ) + mpi_debug_mode = 1; + if( opt.debug & DBG_CIPHER_VALUE ) + g10c_debug_mode = 1; + if( opt.debug & DBG_IOBUF_VALUE ) + iobuf_debug_mode = 1; + +} + + +/* We need the home directory also in some other directories, so make + sure that both variables are always in sync. */ +static void +set_homedir (char *dir) +{ + if (!dir) + dir = ""; + g10_opt_homedir = opt.homedir = dir; +} + + +/* We set the screen dimensions for UI purposes. Do not allow screens + smaller than 80x24 for the sake of simplicity. */ +static void +set_screen_dimensions(void) +{ +#ifndef _WIN32 + char *str; + + str=getenv("COLUMNS"); + if(str) + opt.screen_columns=atoi(str); + + str=getenv("LINES"); + if(str) + opt.screen_lines=atoi(str); +#endif + + if(opt.screen_columns<80 || opt.screen_columns>255) + opt.screen_columns=80; + + if(opt.screen_lines<24 || opt.screen_lines>255) + opt.screen_lines=24; +} + + +/* Helper to open a file FNAME either for reading or writing to be + used with --status-file etc functions. Not generally useful but it + avoids the riscos specific functions and well some Windows people + might like it too. Prints an error message and returns -1 on + error. On success the file descriptor is returned. */ +static int +open_info_file (const char *fname, int for_write) +{ +#ifdef __riscos__ + return riscos_fdopenfile (fname, for_write); +#elif defined (ENABLE_SELINUX_HACKS) + /* We can't allow these even when testing for a secured filename + because files to be secured might not yet been secured. This is + similar to the option file but in that case it is unlikely that + sensitive information may be retrieved by means of error + messages. */ + return -1; +#else + int fd; + +/* if (is_secured_filename (fname)) */ +/* { */ +/* fd = -1; */ +/* errno = EPERM; */ +/* } */ +/* else */ +/* { */ + do + { + if (for_write) + fd = open (fname, O_CREAT | O_TRUNC | O_WRONLY, + S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP); + else + fd = open (fname, O_RDONLY | MY_O_BINARY); + } + while (fd == -1 && errno == EINTR); +/* } */ + if ( fd == -1) + log_error ( for_write? _("can't create `%s': %s\n") + : _("can't open `%s': %s\n"), fname, strerror(errno)); + + return fd; +#endif +} + +static void +set_cmd( enum cmd_and_opt_values *ret_cmd, enum cmd_and_opt_values new_cmd ) +{ + enum cmd_and_opt_values cmd = *ret_cmd; + + if( !cmd || cmd == new_cmd ) + cmd = new_cmd; + else if( cmd == aSign && new_cmd == aEncr ) + cmd = aSignEncr; + else if( cmd == aEncr && new_cmd == aSign ) + cmd = aSignEncr; + else if( cmd == aSign && new_cmd == aSym ) + cmd = aSignSym; + else if( cmd == aSym && new_cmd == aSign ) + cmd = aSignSym; + else if( cmd == aSym && new_cmd == aEncr ) + cmd = aEncrSym; + else if( cmd == aEncr && new_cmd == aSym ) + cmd = aEncrSym; + else if( cmd == aKMode && new_cmd == aSym ) + cmd = aKModeC; + else if (cmd == aSignEncr && new_cmd == aSym) + cmd = aSignEncrSym; + else if (cmd == aSignSym && new_cmd == aEncr) + cmd = aSignEncrSym; + else if (cmd == aEncrSym && new_cmd == aSign) + cmd = aSignEncrSym; + else if( ( cmd == aSign && new_cmd == aClearsign ) + || ( cmd == aClearsign && new_cmd == aSign ) ) + cmd = aClearsign; + else { + log_error(_("conflicting commands\n")); + g10_exit(2); + } + + *ret_cmd = cmd; +} + + +static void +add_group(char *string) +{ + char *name,*value; + struct groupitem *item; + + /* Break off the group name */ + name=strsep(&string,"="); + if(string==NULL) + { + log_error(_("no = sign found in group definition `%s'\n"),name); + return; + } + + trim_trailing_ws(name,strlen(name)); + + /* Does this group already exist? */ + for(item=opt.grouplist;item;item=item->next) + if(strcasecmp(item->name,name)==0) + break; + + if(!item) + { + item=xmalloc(sizeof(struct groupitem)); + item->name=name; + item->next=opt.grouplist; + item->values=NULL; + opt.grouplist=item; + } + + /* Break apart the values */ + while ((value= strsep(&string," \t"))) + { + if (*value) + add_to_strlist2(&item->values,value,utf8_strings); + } +} + + +static void +rm_group(char *name) +{ + struct groupitem *item,*last=NULL; + + trim_trailing_ws(name,strlen(name)); + + for(item=opt.grouplist;item;last=item,item=item->next) + { + if(strcasecmp(item->name,name)==0) + { + if(last) + last->next=item->next; + else + opt.grouplist=item->next; + + free_strlist(item->values); + xfree(item); + break; + } + } +} + + +/* We need to check three things. + + 0) The homedir. It must be x00, a directory, and owned by the + user. + + 1) The options/gpg.conf file. Okay unless it or its containing + directory is group or other writable or not owned by us. Disable + exec in this case. + + 2) Extensions. Same as #1. + + Returns true if the item is unsafe. */ +static int +check_permissions(const char *path,int item) +{ +#if defined(HAVE_STAT) && !defined(HAVE_DOSISH_SYSTEM) + static int homedir_cache=-1; + char *tmppath,*dir; + struct stat statbuf,dirbuf; + int homedir=0,ret=0,checkonly=0; + int perm=0,own=0,enc_dir_perm=0,enc_dir_own=0; + + if(opt.no_perm_warn) + return 0; + + assert(item==0 || item==1 || item==2); + + /* extensions may attach a path */ + if(item==2 && path[0]!=DIRSEP_C) + { + if(strchr(path,DIRSEP_C)) + tmppath=make_filename(path,NULL); + else + tmppath=make_filename(GNUPG_LIBDIR,path,NULL); + } + else + tmppath=xstrdup(path); + + /* If the item is located in the homedir, but isn't the homedir, + don't continue if we already checked the homedir itself. This is + to avoid user confusion with an extra options file warning which + could be rectified if the homedir itself had proper + permissions. */ + if(item!=0 && homedir_cache>-1 + && ascii_strncasecmp(opt.homedir,tmppath,strlen(opt.homedir))==0) + { + ret=homedir_cache; + goto end; + } + + /* It's okay if the file or directory doesn't exist */ + if(stat(tmppath,&statbuf)!=0) + { + ret=0; + goto end; + } + + /* Now check the enclosing directory. Theoretically, we could walk + this test up to the root directory /, but for the sake of sanity, + I'm stopping at one level down. */ + dir=make_dirname(tmppath); + + if(stat(dir,&dirbuf)!=0 || !S_ISDIR(dirbuf.st_mode)) + { + /* Weird error */ + ret=1; + goto end; + } + + xfree(dir); + + /* Assume failure */ + ret=1; + + if(item==0) + { + /* The homedir must be x00, a directory, and owned by the user. */ + + if(S_ISDIR(statbuf.st_mode)) + { + if(statbuf.st_uid==getuid()) + { + if((statbuf.st_mode & (S_IRWXG|S_IRWXO))==0) + ret=0; + else + perm=1; + } + else + own=1; + + homedir_cache=ret; + } + } + else if(item==1 || item==2) + { + /* The options or extension file. Okay unless it or its + containing directory is group or other writable or not owned + by us or root. */ + + if(S_ISREG(statbuf.st_mode)) + { + if(statbuf.st_uid==getuid() || statbuf.st_uid==0) + { + if((statbuf.st_mode & (S_IWGRP|S_IWOTH))==0) + { + /* it's not writable, so make sure the enclosing + directory is also not writable */ + if(dirbuf.st_uid==getuid() || dirbuf.st_uid==0) + { + if((dirbuf.st_mode & (S_IWGRP|S_IWOTH))==0) + ret=0; + else + enc_dir_perm=1; + } + else + enc_dir_own=1; + } + else + { + /* it's writable, so the enclosing directory had + better not let people get to it. */ + if(dirbuf.st_uid==getuid() || dirbuf.st_uid==0) + { + if((dirbuf.st_mode & (S_IRWXG|S_IRWXO))==0) + ret=0; + else + perm=enc_dir_perm=1; /* unclear which one to fix! */ + } + else + enc_dir_own=1; + } + } + else + own=1; + } + } + else + BUG(); + + if(!checkonly) + { + if(own) + { + if(item==0) + log_info(_("WARNING: unsafe ownership on" + " homedir `%s'\n"),tmppath); + else if(item==1) + log_info(_("WARNING: unsafe ownership on" + " configuration file `%s'\n"),tmppath); + else + log_info(_("WARNING: unsafe ownership on" + " extension `%s'\n"),tmppath); + } + if(perm) + { + if(item==0) + log_info(_("WARNING: unsafe permissions on" + " homedir `%s'\n"),tmppath); + else if(item==1) + log_info(_("WARNING: unsafe permissions on" + " configuration file `%s'\n"),tmppath); + else + log_info(_("WARNING: unsafe permissions on" + " extension `%s'\n"),tmppath); + } + if(enc_dir_own) + { + if(item==0) + log_info(_("WARNING: unsafe enclosing directory ownership on" + " homedir `%s'\n"),tmppath); + else if(item==1) + log_info(_("WARNING: unsafe enclosing directory ownership on" + " configuration file `%s'\n"),tmppath); + else + log_info(_("WARNING: unsafe enclosing directory ownership on" + " extension `%s'\n"),tmppath); + } + if(enc_dir_perm) + { + if(item==0) + log_info(_("WARNING: unsafe enclosing directory permissions on" + " homedir `%s'\n"),tmppath); + else if(item==1) + log_info(_("WARNING: unsafe enclosing directory permissions on" + " configuration file `%s'\n"),tmppath); + else + log_info(_("WARNING: unsafe enclosing directory permissions on" + " extension `%s'\n"),tmppath); + } + } + + end: + xfree(tmppath); + + if(homedir) + homedir_cache=ret; + + return ret; + +#endif /* HAVE_STAT && !HAVE_DOSISH_SYSTEM */ + + return 0; +} + + +static void +print_algo_numbers(int (*checker)(int)) +{ + int i,first=1; + + for(i=0;i<=110;i++) + { + if(!checker(i)) + { + if(first) + first=0; + else + printf(";"); + printf("%d",i); + } + } +} + + +/* In the future, we can do all sorts of interesting configuration + output here. For now, just give "group" as the Enigmail folks need + it, and pubkey, cipher, hash, and compress as they may be useful + for frontends. */ +static void +list_config(char *items) +{ + int show_all=(items==NULL); + char *name=NULL; + + if(!opt.with_colons) + return; + + while(show_all || (name=strsep(&items," "))) + { + int any=0; + + if(show_all || ascii_strcasecmp(name,"group")==0) + { + struct groupitem *iter; + + for(iter=opt.grouplist;iter;iter=iter->next) + { + STRLIST sl; + + printf("cfg:group:"); + print_string(stdout,iter->name,strlen(iter->name),':'); + printf(":"); + + for(sl=iter->values;sl;sl=sl->next) + { + print_string2(stdout,sl->d,strlen(sl->d),':',';'); + if(sl->next) + printf(";"); + } + + printf("\n"); + } + + any=1; + } + + if(show_all || ascii_strcasecmp(name,"version")==0) + { + printf("cfg:version:"); + print_string(stdout,VERSION,strlen(VERSION),':'); + printf("\n"); + any=1; + } + + if(show_all || ascii_strcasecmp(name,"pubkey")==0) + { + printf("cfg:pubkey:"); + print_algo_numbers(check_pubkey_algo); + printf("\n"); + any=1; + } + + if(show_all || ascii_strcasecmp(name,"cipher")==0) + { + printf("cfg:cipher:"); + print_algo_numbers(check_cipher_algo); + printf("\n"); + any=1; + } + + if(show_all + || ascii_strcasecmp(name,"digest")==0 + || ascii_strcasecmp(name,"hash")==0) + { + printf("cfg:digest:"); + print_algo_numbers(check_digest_algo); + printf("\n"); + any=1; + } + + if(show_all || ascii_strcasecmp(name,"compress")==0) + { + printf("cfg:compress:"); + print_algo_numbers(check_compress_algo); + printf("\n"); + any=1; + } + + if(show_all || ascii_strcasecmp(name,"ccid-reader-id")==0) + { +#if defined(ENABLE_CARD_SUPPORT) && defined(HAVE_LIBUSB) + char *p, *p2, *list = ccid_get_reader_list (); + + for (p=list; p && (p2 = strchr (p, '\n')); p = p2+1) + { + *p2 = 0; + printf("cfg:ccid-reader-id:%s\n", p); + } + free (list); +#endif + any=1; + } + + if(show_all) + break; + + if(!any) + log_error(_("unknown configuration item `%s'\n"),name); + } +} + + +/* List options and default values in the GPG Conf format. This is a + new tool distributed with gnupg 1.9.x but we also want some limited + support in older gpg versions. The output is the name of the + configuration file and a list of options available for editing by + gpgconf. */ +static void +gpgconf_list (const char *configfile) +{ + /* The following definitions are taken from gnupg/tools/gpgconf-comp.c. */ +#define GC_OPT_FLAG_NONE 0UL +#define GC_OPT_FLAG_DEFAULT (1UL << 4) + + printf ("gpgconf-gpg.conf:%lu:\"%s\n", + GC_OPT_FLAG_DEFAULT,configfile?configfile:"/dev/null"); + printf ("verbose:%lu:\n", GC_OPT_FLAG_NONE); + printf ("quiet:%lu:\n", GC_OPT_FLAG_NONE); + printf ("keyserver:%lu:\n", GC_OPT_FLAG_NONE); + printf ("reader-port:%lu:\n", GC_OPT_FLAG_NONE); +} + + +static int +parse_subpacket_list(char *list) +{ + char *tok; + byte subpackets[128],i; + int count=0; + + if(!list) + { + /* No arguments means all subpackets */ + memset(subpackets+1,1,sizeof(subpackets)-1); + count=127; + } + else + { + memset(subpackets,0,sizeof(subpackets)); + + /* Merge with earlier copy */ + if(opt.show_subpackets) + { + byte *in; + + for(in=opt.show_subpackets;*in;in++) + { + if(*in>127 || *in<1) + BUG(); + + if(!subpackets[*in]) + count++; + subpackets[*in]=1; + } + } + + while((tok=strsep(&list," ,"))) + { + if(!*tok) + continue; + + i=atoi(tok); + if(i>127 || i<1) + return 0; + + if(!subpackets[i]) + count++; + subpackets[i]=1; + } + } + + xfree(opt.show_subpackets); + opt.show_subpackets=xmalloc(count+1); + opt.show_subpackets[count--]=0; + + for(i=1;i<128 && count>=0;i++) + if(subpackets[i]) + opt.show_subpackets[count--]=i; + + return 1; +} + + +static int +parse_list_options(char *str) +{ + char *subpackets=""; /* something that isn't NULL */ + struct parse_options lopts[]= + { + {"show-photos",LIST_SHOW_PHOTOS,NULL, + N_("display photo IDs during key listings")}, + {"show-policy-urls",LIST_SHOW_POLICY_URLS,NULL, + N_("show policy URLs during signature listings")}, + {"show-notations",LIST_SHOW_NOTATIONS,NULL, + N_("show all notations during signature listings")}, + {"show-std-notations",LIST_SHOW_STD_NOTATIONS,NULL, + N_("show IETF standard notations during signature listings")}, + {"show-standard-notations",LIST_SHOW_STD_NOTATIONS,NULL, + NULL}, + {"show-user-notations",LIST_SHOW_USER_NOTATIONS,NULL, + N_("show user-supplied notations during signature listings")}, + {"show-keyserver-urls",LIST_SHOW_KEYSERVER_URLS,NULL, + N_("show preferred keyserver URLs during signature listings")}, + {"show-uid-validity",LIST_SHOW_UID_VALIDITY,NULL, + N_("show user ID validity during key listings")}, + {"show-unusable-uids",LIST_SHOW_UNUSABLE_UIDS,NULL, + N_("show revoked and expired user IDs in key listings")}, + {"show-unusable-subkeys",LIST_SHOW_UNUSABLE_SUBKEYS,NULL, + N_("show revoked and expired subkeys in key listings")}, + {"show-keyring",LIST_SHOW_KEYRING,NULL, + N_("show the keyring name in key listings")}, + {"show-sig-expire",LIST_SHOW_SIG_EXPIRE,NULL, + N_("show expiration dates during signature listings")}, + {"show-sig-subpackets",LIST_SHOW_SIG_SUBPACKETS,NULL, + NULL}, + {NULL,0,NULL,NULL} + }; + + /* C99 allows for non-constant initializers, but we'd like to + compile everywhere, so fill in the show-sig-subpackets argument + here. Note that if the parse_options array changes, we'll have + to change the subscript here. */ + lopts[12].value=&subpackets; + + if(parse_options(str,&opt.list_options,lopts,1)) + { + if(opt.list_options&LIST_SHOW_SIG_SUBPACKETS) + { + /* Unset so users can pass multiple lists in. */ + opt.list_options&=~LIST_SHOW_SIG_SUBPACKETS; + if(!parse_subpacket_list(subpackets)) + return 0; + } + else if(subpackets==NULL && opt.show_subpackets) + { + /* User did 'no-show-subpackets' */ + xfree(opt.show_subpackets); + opt.show_subpackets=NULL; + } + + return 1; + } + else + return 0; +} + + +/* Collapses argc/argv into a single string that must be freed */ +static char * +collapse_args(int argc,char *argv[]) +{ + char *str=NULL; + int i,first=1,len=0; + + for(i=0;i<argc;i++) + { + len+=strlen(argv[i])+2; + str=xrealloc(str,len); + if(first) + { + str[0]='\0'; + first=0; + } + else + strcat(str," "); + + strcat(str,argv[i]); + } + + return str; +} + +static void +parse_trust_model(const char *model) +{ + if(ascii_strcasecmp(model,"pgp")==0) + opt.trust_model=TM_PGP; + else if(ascii_strcasecmp(model,"classic")==0) + opt.trust_model=TM_CLASSIC; + else if(ascii_strcasecmp(model,"always")==0) + opt.trust_model=TM_ALWAYS; + else if(ascii_strcasecmp(model,"direct")==0) + opt.trust_model=TM_DIRECT; + else if(ascii_strcasecmp(model,"auto")==0) + opt.trust_model=TM_AUTO; + else + log_error("unknown trust model `%s'\n",model); +} + +/* Must be called before we open any files. */ +static void +reopen_std(void) +{ +#if defined(HAVE_STAT) && !defined(HAVE_W32_SYSTEM) + struct stat statbuf; + int did_stdin=0,did_stdout=0,did_stderr=0; + FILE *complain; + + if(fstat(STDIN_FILENO,&statbuf)==-1 && errno==EBADF) + { + if(open("/dev/null",O_RDONLY)==STDIN_FILENO) + did_stdin=1; + else + did_stdin=2; + } + + if(fstat(STDOUT_FILENO,&statbuf)==-1 && errno==EBADF) + { + if(open("/dev/null",O_WRONLY)==STDOUT_FILENO) + did_stdout=1; + else + did_stdout=2; + } + + if(fstat(STDERR_FILENO,&statbuf)==-1 && errno==EBADF) + { + if(open("/dev/null",O_WRONLY)==STDERR_FILENO) + did_stderr=1; + else + did_stderr=2; + } + + /* It's hard to log this sort of thing since the filehandle we would + complain to may be closed... */ + if(did_stderr==0) + complain=stderr; + else if(did_stdout==0) + complain=stdout; + else + complain=NULL; + + if(complain) + { + if(did_stdin==1) + fprintf(complain,"gpg: WARNING: standard input reopened\n"); + if(did_stdout==1) + fprintf(complain,"gpg: WARNING: standard output reopened\n"); + if(did_stderr==1) + fprintf(complain,"gpg: WARNING: standard error reopened\n"); + + if(did_stdin==2 || did_stdout==2 || did_stderr==2) + fprintf(complain,"gpg: fatal: unable to reopen standard input," + " output, or error\n"); + } + + if(did_stdin==2 || did_stdout==2 || did_stderr==2) + exit(3); +#endif /* HAVE_STAT && !HAVE_W32_SYSTEM */ +} + +/* Pack an s2k iteration count into the form specified in 2440. If + we're in between valid values, round up. */ +static unsigned char +encode_s2k_iterations(int iterations) +{ + unsigned char c=0,result; + unsigned int count; + + if(iterations<=1024) + return 0; + + if(iterations>=65011712) + return 255; + + /* Need count to be in the range 16-31 */ + for(count=iterations>>6;count>=32;count>>=1) + c++; + + result=(c<<4)|(count-16); + + if(S2K_DECODE_COUNT(result)<iterations) + result++; + + return result; +} + +int +main (int argc, char **argv ) +{ + ARGPARSE_ARGS pargs; + IOBUF a; + int rc=0; + int orig_argc; + char **orig_argv; + const char *fname; + char *username; + int may_coredump; + STRLIST sl, remusr= NULL, locusr=NULL; + STRLIST nrings=NULL, sec_nrings=NULL; + armor_filter_context_t afx; + int detached_sig = 0; + FILE *configfp = NULL; + char *configname = NULL; + char *save_configname = NULL; + unsigned configlineno; + int parse_debug = 0; + int default_config = 1; + int default_keyring = 1; + int greeting = 0; + int nogreeting = 0; + int use_random_seed = 1; + enum cmd_and_opt_values cmd = 0; + const char *trustdb_name = NULL; + char *def_cipher_string = NULL; + char *def_digest_string = NULL; + char *compress_algo_string = NULL; + char *cert_digest_string = NULL; + char *s2k_cipher_string = NULL; + char *s2k_digest_string = NULL; + char *pers_cipher_list = NULL; + char *pers_digest_list = NULL; + char *pers_compress_list = NULL; + int eyes_only=0; + int multifile=0; + int pwfd = -1; + int with_fpr = 0; /* make an option out of --fingerprint */ + int any_explicit_recipient = 0; + int require_secmem=0,got_secmem=0; +#ifdef USE_SHM_COPROCESSING + ulong requested_shm_size=0; +#endif + +#ifdef __riscos__ + opt.lock_once = 1; +#endif /* __riscos__ */ + + reopen_std(); + trap_unaligned(); + secmem_set_flags( secmem_get_flags() | 2 ); /* suspend warnings */ + /* Please note that we may running SUID(ROOT), so be very CAREFUL + * when adding any stuff between here and the call to + * secmem_init() somewhere after the option parsing + */ + log_set_name("gpg"); + secure_randoxmalloc(); /* put random number into secure memory */ + may_coredump = disable_core_dumps(); + init_signals(); + create_dotlock(NULL); /* register locking cleanup */ + i18n_init(); + opt.command_fd = -1; /* no command fd */ + opt.compress_level = -1; /* defaults to standard compress level */ + opt.bz2_compress_level = -1; /* defaults to standard compress level */ + /* note: if you change these lines, look at oOpenPGP */ + opt.def_cipher_algo = 0; + opt.def_digest_algo = 0; + opt.cert_digest_algo = 0; + opt.compress_algo = -1; /* defaults to DEFAULT_COMPRESS_ALGO */ + opt.s2k_mode = 3; /* iterated+salted */ + opt.s2k_count = 96; /* 65536 iterations */ +#ifdef USE_CAST5 + opt.s2k_cipher_algo = CIPHER_ALGO_CAST5; +#else + opt.s2k_cipher_algo = CIPHER_ALGO_3DES; +#endif + opt.completes_needed = 1; + opt.marginals_needed = 3; + opt.max_cert_depth = 5; + opt.pgp2_workarounds = 1; + opt.force_v3_sigs = 1; + opt.escape_from = 1; + opt.import_options=IMPORT_SK2PK; + opt.export_options=EXPORT_ATTRIBUTES; + opt.keyserver_options.import_options=IMPORT_REPAIR_PKS_SUBKEY_BUG; + opt.keyserver_options.export_options=EXPORT_ATTRIBUTES; + opt.keyserver_options.options= + KEYSERVER_HONOR_KEYSERVER_URL|KEYSERVER_HONOR_PKA_RECORD; + opt.verify_options= + VERIFY_SHOW_POLICY_URLS|VERIFY_SHOW_STD_NOTATIONS|VERIFY_SHOW_KEYSERVER_URLS; + opt.trust_model=TM_AUTO; + opt.mangle_dos_filenames=0; + opt.min_cert_level=2; + set_screen_dimensions(); + opt.keyid_format=KF_SHORT; + opt.rfc2440_text=1; + opt.def_sig_expire="0"; + opt.def_cert_expire="0"; + set_homedir ( default_homedir () ); + opt.passwd_repeat=1; + +#ifdef ENABLE_CARD_SUPPORT +#if defined(_WIN32) || defined(__CYGWIN__) + opt.pcsc_driver = "winscard.dll"; +#elif defined(__APPLE__) + opt.pcsc_driver = "/System/Library/Frameworks/PCSC.framework/PCSC"; +#elif defined(__GLIBC__) + opt.pcsc_driver = "libpcsclite.so.1"; +#else + opt.pcsc_driver = "libpcsclite.so"; +#endif +#endif /*ENABLE_CARD_SUPPORT*/ + + /* check whether we have a config file on the commandline */ + orig_argc = argc; + orig_argv = argv; + pargs.argc = &argc; + pargs.argv = &argv; + pargs.flags= 1|(1<<6); /* do not remove the args, ignore version */ + while( arg_parse( &pargs, opts) ) { + if( pargs.r_opt == oDebug || pargs.r_opt == oDebugAll ) + parse_debug++; + else if( pargs.r_opt == oOptions ) { + /* yes there is one, so we do not try the default one, but + * read the option file when it is encountered at the commandline + */ + default_config = 0; + } + else if( pargs.r_opt == oNoOptions ) + default_config = 0; /* --no-options */ + else if( pargs.r_opt == oHomedir ) + set_homedir ( pargs.r.ret_str ); + else if( pargs.r_opt == oNoPermissionWarn ) + opt.no_perm_warn=1; + else if (pargs.r_opt == oStrict ) + { + opt.strict=1; + log_set_strict(1); + } + else if (pargs.r_opt == oNoStrict ) + { + opt.strict=0; + log_set_strict(0); + } +#ifdef USE_SHM_COPROCESSING + else if( pargs.r_opt == oRunAsShmCP ) { + /* does not make sense in a options file, we do it here, + * so that we are the able to drop setuid as soon as possible */ + opt.shm_coprocess = 1; + requested_shm_size = pargs.r.ret_ulong; + } + else if ( pargs.r_opt == oStatusFD ) { + /* this is needed to ensure that the status-fd filedescriptor is + * initialized when init_shm_coprocessing() is called */ + set_status_fd( iobuf_translate_file_handle (pargs.r.ret_int, 1) ); + } +#endif + } + +#ifdef HAVE_DOSISH_SYSTEM + if ( strchr (opt.homedir,'\\') ) { + char *d, *buf = xmalloc (strlen (opt.homedir)+1); + const char *s = opt.homedir; + for (d=buf,s=opt.homedir; *s; s++) + { + *d++ = *s == '\\'? '/': *s; +#ifdef HAVE_W32_SYSTEM + if (s[1] && IsDBCSLeadByte (*s)) + *d++ = *++s; +#endif + } + *d = 0; + set_homedir (buf); + } +#endif +#ifdef USE_SHM_COPROCESSING + if( opt.shm_coprocess ) { + init_shm_coprocessing(requested_shm_size, 1 ); + } +#endif + /* initialize the secure memory. */ + got_secmem=secmem_init( 32768 ); + maybe_setuid = 0; + /* Okay, we are now working under our real uid */ + +#if defined(HAVE_GETUID) && defined(HAVE_GETEUID) + /* There should be no way to get to this spot while still carrying + setuid privs. Just in case, bomb out if we are. */ + if(getuid()!=geteuid()) + BUG(); +#endif + + set_native_charset (NULL); /* Try to auto set the character set */ + + /* Try for a version specific config file first */ + if( default_config ) + { + char *name=xstrdup("gpg" EXTSEP_S "conf-" SAFE_VERSION); + char *ver=&name[strlen("gpg" EXTSEP_S "conf-")]; + + do + { + if(configname) + { + char *tok; + + xfree(configname); + configname=NULL; + + if((tok=strrchr(ver,SAFE_VERSION_DASH))) + *tok='\0'; + else if((tok=strrchr(ver,SAFE_VERSION_DOT))) + *tok='\0'; + else + break; + } + + configname = make_filename(opt.homedir,name,NULL); + } + while(access(configname,R_OK)); + + xfree(name); + + if(!configname) + configname=make_filename(opt.homedir, "gpg" EXTSEP_S "conf", NULL ); + if (!access (configname, R_OK)) + { /* Print a warning when both config files are present. */ + char *p = make_filename(opt.homedir, "options", NULL ); + if (!access (p, R_OK)) + log_info (_("NOTE: old default options file `%s' ignored\n"), p); + xfree (p); + } + else + { /* Keep on using the old default one. */ + xfree (configname); + configname = make_filename(opt.homedir, "options", NULL ); + } + } + argc = orig_argc; + argv = orig_argv; + pargs.argc = &argc; + pargs.argv = &argv; + pargs.flags= 1; /* do not remove the args */ + + /* By this point we have a homedir, and cannot change it. */ + check_permissions(opt.homedir,0); + + next_pass: + if( configname ) { + if(check_permissions(configname,1)) + { + /* If any options file is unsafe, then disable any external + programs for keyserver calls or photo IDs. Since the + external program to call is set in the options file, a + unsafe options file can lead to an arbitrary program + being run. */ + + opt.exec_disable=1; + } + + configlineno = 0; + configfp = fopen( configname, "r" ); + if (configfp && is_secured_file (fileno (configfp))) + { + fclose (configfp); + configfp = NULL; + errno = EPERM; + } + if( !configfp ) { + if( default_config ) { + if( parse_debug ) + log_info(_("NOTE: no default option file `%s'\n"), + configname ); + } + else { + log_error(_("option file `%s': %s\n"), + configname, strerror(errno) ); + g10_exit(2); + } + xfree(configname); configname = NULL; + } + if( parse_debug && configname ) + log_info(_("reading options from `%s'\n"), configname ); + default_config = 0; + } + + while( optfile_parse( configfp, configname, &configlineno, + &pargs, opts) ) + { + switch( pargs.r_opt ) + { + case aCheckKeys: + case aListConfig: + case aGPGConfList: + case aGPGConfTest: + case aListPackets: + case aImport: + case aFastImport: + case aSendKeys: + case aRecvKeys: + case aSearchKeys: + case aRefreshKeys: + case aFetchKeys: + case aExport: + set_cmd (&cmd, pargs.r_opt); + break; + case aListKeys: set_cmd( &cmd, aListKeys); break; + case aListSigs: set_cmd( &cmd, aListSigs); break; + case aExportSecret: set_cmd( &cmd, aExportSecret); break; + case aExportSecretSub: set_cmd( &cmd, aExportSecretSub); break; + case aDeleteSecretKeys: + set_cmd( &cmd, aDeleteSecretKeys); + greeting=1; + break; + case aDeleteSecretAndPublicKeys: + set_cmd( &cmd, aDeleteSecretAndPublicKeys); + greeting=1; + break; + case aDeleteKeys: set_cmd( &cmd, aDeleteKeys); greeting=1; break; + + case aDetachedSign: detached_sig = 1; set_cmd( &cmd, aSign ); break; + case aSym: set_cmd( &cmd, aSym); break; + + case aDecryptFiles: multifile=1; /* fall through */ + case aDecrypt: set_cmd( &cmd, aDecrypt); break; + + case aEncrFiles: multifile=1; /* fall through */ + case aEncr: set_cmd( &cmd, aEncr); break; + + case aVerifyFiles: multifile=1; /* fall through */ + case aVerify: set_cmd( &cmd, aVerify); break; + + case aSign: set_cmd( &cmd, aSign ); break; + case aKeygen: set_cmd( &cmd, aKeygen); greeting=1; break; + case aSignKey: set_cmd( &cmd, aSignKey); break; + case aLSignKey: set_cmd( &cmd, aLSignKey); break; + case aStore: set_cmd( &cmd, aStore); break; + case aEditKey: set_cmd( &cmd, aEditKey); greeting=1; break; + case aClearsign: set_cmd( &cmd, aClearsign); break; + case aGenRevoke: set_cmd( &cmd, aGenRevoke); break; + case aDesigRevoke: set_cmd( &cmd, aDesigRevoke); break; + case aPrimegen: set_cmd( &cmd, aPrimegen); break; + case aGenRandom: set_cmd( &cmd, aGenRandom); break; + case aPrintMD: set_cmd( &cmd, aPrintMD); break; + case aPrintMDs: set_cmd( &cmd, aPrintMDs); break; + case aListTrustDB: set_cmd( &cmd, aListTrustDB); break; + case aCheckTrustDB: set_cmd( &cmd, aCheckTrustDB); break; + case aUpdateTrustDB: set_cmd( &cmd, aUpdateTrustDB); break; + case aFixTrustDB: set_cmd( &cmd, aFixTrustDB); break; + case aListTrustPath: set_cmd( &cmd, aListTrustPath); break; + case aDeArmor: set_cmd( &cmd, aDeArmor); break; + case aEnArmor: set_cmd( &cmd, aEnArmor); break; + case aListOwnerTrust: + deprecated_warning(configname,configlineno, + "--list-ownertrust","--export-ownertrust",""); + case aExportOwnerTrust: set_cmd( &cmd, aExportOwnerTrust); break; + case aImportOwnerTrust: set_cmd( &cmd, aImportOwnerTrust); break; + case aPipeMode: + deprecated_command ("--pipemode"); + set_cmd( &cmd, aPipeMode); + break; + + case aRebuildKeydbCaches: set_cmd( &cmd, aRebuildKeydbCaches); break; + +#ifdef ENABLE_CARD_SUPPORT + case aCardStatus: set_cmd (&cmd, aCardStatus); break; + case aCardEdit: set_cmd (&cmd, aCardEdit); break; + case aChangePIN: set_cmd (&cmd, aChangePIN); break; + case oReaderPort: + card_set_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 oDisableCCID: opt.disable_ccid = 1; break; +#endif /* ENABLE_CARD_SUPPORT*/ + + case oArmor: opt.armor = 1; opt.no_armor=0; break; + case oOutput: opt.outfile = pargs.r.ret_str; break; + case oMaxOutput: opt.max_output = pargs.r.ret_ulong; break; + case oQuiet: opt.quiet = 1; break; + case oNoTTY: tty_no_terminal(1); break; + case oDryRun: opt.dry_run = 1; break; + case oInteractive: opt.interactive = 1; break; + case oVerbose: + g10_opt_verbose++; + opt.verbose++; + opt.list_options|=LIST_SHOW_UNUSABLE_UIDS; + opt.list_options|=LIST_SHOW_UNUSABLE_SUBKEYS; + break; + case oKOption: set_cmd( &cmd, aKMode ); break; + + case oBatch: opt.batch = 1; nogreeting = 1; break; + case oUseAgent: +#ifndef __riscos__ + opt.use_agent = 1; +#else /* __riscos__ */ + opt.use_agent = 0; + riscos_not_implemented("use-agent"); +#endif /* __riscos__ */ + break; + case oNoUseAgent: opt.use_agent = 0; break; + case oGpgAgentInfo: opt.gpg_agent_info = pargs.r.ret_str; break; + case oAnswerYes: opt.answer_yes = 1; break; + case oAnswerNo: opt.answer_no = 1; break; + case oKeyring: append_to_strlist( &nrings, pargs.r.ret_str); break; + case oPrimaryKeyring: + sl=append_to_strlist( &nrings, pargs.r.ret_str); + sl->flags=2; + break; + case oShowKeyring: + deprecated_warning(configname,configlineno,"--show-keyring", + "--list-options ","show-keyring"); + opt.list_options|=LIST_SHOW_KEYRING; + break; + case oDebug: opt.debug |= pargs.r.ret_ulong; break; + case oDebugAll: opt.debug = ~0; break; + case oDebugCCIDDriver: +#if defined(ENABLE_CARD_SUPPORT) && defined(HAVE_LIBUSB) + ccid_set_debug_level (ccid_set_debug_level (1)+1); +#endif + break; + case oStatusFD: + set_status_fd( iobuf_translate_file_handle (pargs.r.ret_int, 1) ); + break; + case oStatusFile: + set_status_fd ( open_info_file (pargs.r.ret_str, 1) ); + break; + case oAttributeFD: + set_attrib_fd(iobuf_translate_file_handle (pargs.r.ret_int, 1)); + break; + case oAttributeFile: + set_attrib_fd ( open_info_file (pargs.r.ret_str, 1) ); + break; + case oLoggerFD: + log_set_logfile( NULL, + iobuf_translate_file_handle (pargs.r.ret_int, 1)); + break; + case oLoggerFile: + log_set_logfile( NULL, open_info_file (pargs.r.ret_str, 1) ); + break; + + case oWithFingerprint: + opt.with_fingerprint = 1; + with_fpr=1; /*fall thru*/ + case oFingerprint: opt.fingerprint++; break; + case oSecretKeyring: + append_to_strlist( &sec_nrings, pargs.r.ret_str); + break; + case oOptions: + /* config files may not be nested (silently ignore them) */ + if( !configfp ) { + xfree(configname); + configname = xstrdup(pargs.r.ret_str); + goto next_pass; + } + break; + case oNoArmor: opt.no_armor=1; opt.armor=0; break; + case oNoDefKeyring: default_keyring = 0; break; + case oNoGreeting: nogreeting = 1; break; + case oNoVerbose: g10_opt_verbose = 0; + opt.verbose = 0; opt.list_sigs=0; break; + case oQuickRandom: quick_random_gen(1); break; + case oEmitVersion: opt.no_version=0; break; + case oNoEmitVersion: opt.no_version=1; break; + case oCompletesNeeded: opt.completes_needed = pargs.r.ret_int; break; + case oMarginalsNeeded: opt.marginals_needed = pargs.r.ret_int; break; + case oMaxCertDepth: opt.max_cert_depth = pargs.r.ret_int; break; + case oTrustDBName: trustdb_name = pargs.r.ret_str; break; + case oDefaultKey: opt.def_secret_key = pargs.r.ret_str; break; + case oDefRecipient: + if( *pargs.r.ret_str ) + opt.def_recipient = make_username(pargs.r.ret_str); + break; + case oDefRecipientSelf: + xfree(opt.def_recipient); opt.def_recipient = NULL; + opt.def_recipient_self = 1; + break; + case oNoDefRecipient: + xfree(opt.def_recipient); opt.def_recipient = NULL; + opt.def_recipient_self = 0; + break; + case oNoOptions: opt.no_homedir_creation = 1; break; /* no-options */ + case oHomedir: break; + case oNoBatch: opt.batch = 0; break; + case oWithKeyData: opt.with_key_data=1; /* fall thru */ + case oWithColons: opt.with_colons=':'; break; + + case oSkipVerify: opt.skip_verify=1; break; + case oCompressKeys: opt.compress_keys = 1; break; + case aListSecretKeys: set_cmd( &cmd, aListSecretKeys); break; + /* There are many programs (like mutt) that call gpg with + --always-trust so keep this option around for a long + time. */ + case oAlwaysTrust: opt.trust_model=TM_ALWAYS; break; + case oTrustModel: + parse_trust_model(pargs.r.ret_str); + break; + case oForceOwnertrust: + log_info(_("NOTE: %s is not for normal use!\n"), + "--force-ownertrust"); + opt.force_ownertrust=string_to_trust_value(pargs.r.ret_str); + if(opt.force_ownertrust==-1) + { + log_error("invalid ownertrust `%s'\n",pargs.r.ret_str); + opt.force_ownertrust=0; + } + break; + case oLoadExtension: +#ifndef __riscos__ +#if defined(USE_DYNAMIC_LINKING) || defined(_WIN32) + if(check_permissions(pargs.r.ret_str,2)) + log_info(_("cipher extension `%s' not loaded due to" + " unsafe permissions\n"),pargs.r.ret_str); + else + register_cipher_extension(orig_argc? *orig_argv:NULL, + pargs.r.ret_str); +#endif +#else /* __riscos__ */ + riscos_not_implemented("load-extension"); +#endif /* __riscos__ */ + break; + case oRFC1991: + opt.compliance = CO_RFC1991; + opt.force_v4_certs = 0; + opt.escape_from = 1; + break; + case oOpenPGP: + case oRFC2440: + /* TODO: When 2440bis becomes a RFC, set new values for + oOpenPGP. */ + opt.rfc2440_text=1; + opt.compliance = CO_RFC2440; + opt.allow_non_selfsigned_uid = 1; + opt.allow_freeform_uid = 1; + opt.pgp2_workarounds = 0; + opt.escape_from = 0; + opt.force_v3_sigs = 0; + opt.compress_keys = 0; /* not mandated, but we do it */ + opt.compress_sigs = 0; /* ditto. */ + opt.not_dash_escaped = 0; + opt.def_cipher_algo = 0; + opt.def_digest_algo = 0; + opt.cert_digest_algo = 0; + opt.compress_algo = -1; + opt.s2k_mode = 3; /* iterated+salted */ + opt.s2k_digest_algo = DIGEST_ALGO_SHA1; + opt.s2k_cipher_algo = CIPHER_ALGO_3DES; + break; + case oPGP2: opt.compliance = CO_PGP2; break; + case oPGP6: opt.compliance = CO_PGP6; break; + case oPGP7: opt.compliance = CO_PGP7; break; + case oPGP8: opt.compliance = CO_PGP8; break; + case oGnuPG: opt.compliance = CO_GNUPG; break; + case oCompressSigs: opt.compress_sigs = 1; break; + case oRFC2440Text: opt.rfc2440_text=1; break; + case oNoRFC2440Text: opt.rfc2440_text=0; break; + case oRunAsShmCP: +#ifndef __riscos__ +# ifndef USE_SHM_COPROCESSING + /* not possible in the option file, + * but we print the warning here anyway */ + log_error("shared memory coprocessing is not available\n"); +# endif +#else /* __riscos__ */ + riscos_not_implemented("run-as-shm-coprocess"); +#endif /* __riscos__ */ + break; + case oSetFilename: + if(utf8_strings) + opt.set_filename = pargs.r.ret_str; + else + opt.set_filename = native_to_utf8(pargs.r.ret_str); + break; + case oForYourEyesOnly: eyes_only = 1; break; + case oNoForYourEyesOnly: eyes_only = 0; break; + case oSetPolicyURL: + add_policy_url(pargs.r.ret_str,0); + add_policy_url(pargs.r.ret_str,1); + break; + case oSigPolicyURL: add_policy_url(pargs.r.ret_str,0); break; + case oCertPolicyURL: add_policy_url(pargs.r.ret_str,1); break; + case oShowPolicyURL: + deprecated_warning(configname,configlineno,"--show-policy-url", + "--list-options ","show-policy-urls"); + deprecated_warning(configname,configlineno,"--show-policy-url", + "--verify-options ","show-policy-urls"); + opt.list_options|=LIST_SHOW_POLICY_URLS; + opt.verify_options|=VERIFY_SHOW_POLICY_URLS; + break; + case oNoShowPolicyURL: + deprecated_warning(configname,configlineno,"--no-show-policy-url", + "--list-options ","no-show-policy-urls"); + deprecated_warning(configname,configlineno,"--no-show-policy-url", + "--verify-options ","no-show-policy-urls"); + opt.list_options&=~LIST_SHOW_POLICY_URLS; + opt.verify_options&=~VERIFY_SHOW_POLICY_URLS; + break; + case oSigKeyserverURL: add_keyserver_url(pargs.r.ret_str,0); break; + case oUseEmbeddedFilename: + opt.flags.use_embedded_filename=1; + break; + case oNoUseEmbeddedFilename: + opt.flags.use_embedded_filename=0; + break; + case oComment: + if(pargs.r.ret_str[0]) + append_to_strlist(&opt.comments,pargs.r.ret_str); + break; + case oDefaultComment: + deprecated_warning(configname,configlineno, + "--default-comment","--no-comments",""); + /* fall through */ + case oNoComments: + free_strlist(opt.comments); + opt.comments=NULL; + break; + case oThrowKeyids: opt.throw_keyid = 1; break; + case oNoThrowKeyids: opt.throw_keyid = 0; break; + case oShowPhotos: + deprecated_warning(configname,configlineno,"--show-photos", + "--list-options ","show-photos"); + deprecated_warning(configname,configlineno,"--show-photos", + "--verify-options ","show-photos"); + opt.list_options|=LIST_SHOW_PHOTOS; + opt.verify_options|=VERIFY_SHOW_PHOTOS; + break; + case oNoShowPhotos: + deprecated_warning(configname,configlineno,"--no-show-photos", + "--list-options ","no-show-photos"); + deprecated_warning(configname,configlineno,"--no-show-photos", + "--verify-options ","no-show-photos"); + opt.list_options&=~LIST_SHOW_PHOTOS; + opt.verify_options&=~VERIFY_SHOW_PHOTOS; + break; + case oPhotoViewer: opt.photo_viewer = pargs.r.ret_str; break; + case oForceV3Sigs: opt.force_v3_sigs = 1; break; + case oNoForceV3Sigs: opt.force_v3_sigs = 0; break; + case oForceV4Certs: opt.force_v4_certs = 1; break; + case oNoForceV4Certs: opt.force_v4_certs = 0; break; + case oForceMDC: opt.force_mdc = 1; break; + case oNoForceMDC: opt.force_mdc = 0; break; + case oDisableMDC: opt.disable_mdc = 1; break; + case oNoDisableMDC: opt.disable_mdc = 0; break; + case oS2KMode: opt.s2k_mode = pargs.r.ret_int; break; + case oS2KDigest: s2k_digest_string = xstrdup(pargs.r.ret_str); break; + case oS2KCipher: s2k_cipher_string = xstrdup(pargs.r.ret_str); break; + case oS2KCount: + opt.s2k_count=encode_s2k_iterations(pargs.r.ret_int); + break; + case oSimpleSKChecksum: opt.simple_sk_checksum = 1; break; + case oNoEncryptTo: opt.no_encrypt_to = 1; break; + case oEncryptTo: /* store the recipient in the second list */ + sl = add_to_strlist2( &remusr, pargs.r.ret_str, utf8_strings ); + sl->flags = 1; + break; + case oHiddenEncryptTo: /* store the recipient in the second list */ + sl = add_to_strlist2( &remusr, pargs.r.ret_str, utf8_strings ); + sl->flags = 1|2; + break; + case oRecipient: /* store the recipient */ + add_to_strlist2( &remusr, pargs.r.ret_str, utf8_strings ); + any_explicit_recipient = 1; + break; + case oHiddenRecipient: /* store the recipient with a flag */ + sl = add_to_strlist2( &remusr, pargs.r.ret_str, utf8_strings ); + sl->flags = 2; + any_explicit_recipient = 1; + break; + case oTextmodeShort: opt.textmode = 2; break; + case oTextmode: opt.textmode=1; break; + case oNoTextmode: opt.textmode=0; break; + case oExpert: opt.expert = 1; break; + case oNoExpert: opt.expert = 0; break; + case oDefSigExpire: + if(*pargs.r.ret_str!='\0') + { + if(parse_expire_string(pargs.r.ret_str)==(u32)-1) + log_error(_("`%s' is not a valid signature expiration\n"), + pargs.r.ret_str); + else + opt.def_sig_expire=pargs.r.ret_str; + } + break; + case oAskSigExpire: opt.ask_sig_expire = 1; break; + case oNoAskSigExpire: opt.ask_sig_expire = 0; break; + case oDefCertExpire: + if(*pargs.r.ret_str!='\0') + { + if(parse_expire_string(pargs.r.ret_str)==(u32)-1) + log_error(_("`%s' is not a valid signature expiration\n"), + pargs.r.ret_str); + else + opt.def_cert_expire=pargs.r.ret_str; + } + break; + case oAskCertExpire: opt.ask_cert_expire = 1; break; + case oNoAskCertExpire: opt.ask_cert_expire = 0; break; + case oDefCertLevel: opt.def_cert_level=pargs.r.ret_int; break; + case oMinCertLevel: opt.min_cert_level=pargs.r.ret_int; break; + case oAskCertLevel: opt.ask_cert_level = 1; break; + case oNoAskCertLevel: opt.ask_cert_level = 0; break; + case oLocalUser: /* store the local users */ + add_to_strlist2( &locusr, pargs.r.ret_str, utf8_strings ); + break; + case oCompress: + /* this is the -z command line option */ + opt.compress_level = opt.bz2_compress_level = pargs.r.ret_int; + break; + case oCompressLevel: opt.compress_level = pargs.r.ret_int; break; + case oBZ2CompressLevel: opt.bz2_compress_level = pargs.r.ret_int; break; + case oBZ2DecompressLowmem: opt.bz2_decompress_lowmem=1; break; + case oPasswd: + set_passphrase_from_string(pargs.r.ret_str); + break; + case oPasswdFD: + pwfd = iobuf_translate_file_handle (pargs.r.ret_int, 0); + opt.use_agent = 0; + break; + case oPasswdFile: + pwfd = open_info_file (pargs.r.ret_str, 0); + break; + case oPasswdRepeat: opt.passwd_repeat=pargs.r.ret_int; break; + case oCommandFD: + opt.command_fd = iobuf_translate_file_handle (pargs.r.ret_int, 0); + break; + case oCommandFile: + opt.command_fd = open_info_file (pargs.r.ret_str, 0); + break; + case oCipherAlgo: + def_cipher_string = xstrdup(pargs.r.ret_str); + break; + case oDigestAlgo: + def_digest_string = xstrdup(pargs.r.ret_str); + break; + case oCompressAlgo: + /* If it is all digits, stick a Z in front of it for + later. This is for backwards compatibility with + versions that took the compress algorithm number. */ + { + char *pt=pargs.r.ret_str; + while(*pt) + { + if (!isascii (*pt) || !isdigit (*pt)) + break; + + pt++; + } + + if(*pt=='\0') + { + compress_algo_string=xmalloc(strlen(pargs.r.ret_str)+2); + strcpy(compress_algo_string,"Z"); + strcat(compress_algo_string,pargs.r.ret_str); + } + else + compress_algo_string = xstrdup(pargs.r.ret_str); + } + break; + case oCertDigestAlgo: cert_digest_string = xstrdup(pargs.r.ret_str); break; + case oNoSecmemWarn: secmem_set_flags( secmem_get_flags() | 1 ); break; + case oRequireSecmem: require_secmem=1; break; + case oNoRequireSecmem: require_secmem=0; break; + case oNoPermissionWarn: opt.no_perm_warn=1; break; + case oNoMDCWarn: opt.no_mdc_warn=1; break; + case oDisplayCharset: + if( set_native_charset( pargs.r.ret_str ) ) + log_error(_("`%s' is not a valid character set\n"), + pargs.r.ret_str); + break; + case oNotDashEscaped: opt.not_dash_escaped = 1; break; + case oEscapeFrom: opt.escape_from = 1; break; + case oNoEscapeFrom: opt.escape_from = 0; break; + case oLockOnce: opt.lock_once = 1; break; + case oLockNever: + disable_dotlock (); + random_disable_locking (); + break; + case oLockMultiple: +#ifndef __riscos__ + opt.lock_once = 0; +#else /* __riscos__ */ + riscos_not_implemented("lock-multiple"); +#endif /* __riscos__ */ + break; + case oKeyServer: + { + struct keyserver_spec *keyserver; + keyserver=parse_keyserver_uri(pargs.r.ret_str,0, + configname,configlineno); + if(!keyserver) + log_error(_("could not parse keyserver URL\n")); + else + { + keyserver->next=opt.keyserver; + opt.keyserver=keyserver; + } + } + break; + case oKeyServerOptions: + if(!parse_keyserver_options(pargs.r.ret_str)) + { + if(configname) + log_error(_("%s:%d: invalid keyserver options\n"), + configname,configlineno); + else + log_error(_("invalid keyserver options\n")); + } + break; + case oImportOptions: + if(!parse_import_options(pargs.r.ret_str,&opt.import_options,1)) + { + if(configname) + log_error(_("%s:%d: invalid import options\n"), + configname,configlineno); + else + log_error(_("invalid import options\n")); + } + break; + case oExportOptions: + if(!parse_export_options(pargs.r.ret_str,&opt.export_options,1)) + { + if(configname) + log_error(_("%s:%d: invalid export options\n"), + configname,configlineno); + else + log_error(_("invalid export options\n")); + } + break; + case oListOptions: + if(!parse_list_options(pargs.r.ret_str)) + { + if(configname) + log_error(_("%s:%d: invalid list options\n"), + configname,configlineno); + else + log_error(_("invalid list options\n")); + } + break; + case oVerifyOptions: + { + struct parse_options vopts[]= + { + {"show-photos",VERIFY_SHOW_PHOTOS,NULL, + N_("display photo IDs during signature verification")}, + {"show-policy-urls",VERIFY_SHOW_POLICY_URLS,NULL, + N_("show policy URLs during signature verification")}, + {"show-notations",VERIFY_SHOW_NOTATIONS,NULL, + N_("show all notations during signature verification")}, + {"show-std-notations",VERIFY_SHOW_STD_NOTATIONS,NULL, + N_("show IETF standard notations during signature verification")}, + {"show-standard-notations",VERIFY_SHOW_STD_NOTATIONS,NULL, + NULL}, + {"show-user-notations",VERIFY_SHOW_USER_NOTATIONS,NULL, + N_("show user-supplied notations during signature verification")}, + {"show-keyserver-urls",VERIFY_SHOW_KEYSERVER_URLS,NULL, + N_("show preferred keyserver URLs during signature verification")}, + {"show-uid-validity",VERIFY_SHOW_UID_VALIDITY,NULL, + N_("show user ID validity during signature verification")}, + {"show-unusable-uids",VERIFY_SHOW_UNUSABLE_UIDS,NULL, + N_("show revoked and expired user IDs in signature verification")}, + {"pka-lookups",VERIFY_PKA_LOOKUPS,NULL, + N_("validate signatures with PKA data")}, + {"pka-trust-increase",VERIFY_PKA_TRUST_INCREASE,NULL, + N_("elevate the trust of signatures with valid PKA data")}, + {NULL,0,NULL,NULL} + }; + + if(!parse_options(pargs.r.ret_str,&opt.verify_options,vopts,1)) + { + if(configname) + log_error(_("%s:%d: invalid verify options\n"), + configname,configlineno); + else + log_error(_("invalid verify options\n")); + } + } + break; + case oTempDir: opt.temp_dir=pargs.r.ret_str; break; + case oExecPath: + if(set_exec_path(pargs.r.ret_str)) + log_error(_("unable to set exec-path to %s\n"),pargs.r.ret_str); + else + opt.exec_path_set=1; + break; + case oSetNotation: + add_notation_data( pargs.r.ret_str, 0 ); + add_notation_data( pargs.r.ret_str, 1 ); + break; + case oSigNotation: add_notation_data( pargs.r.ret_str, 0 ); break; + case oCertNotation: add_notation_data( pargs.r.ret_str, 1 ); break; + case oShowNotation: + deprecated_warning(configname,configlineno,"--show-notation", + "--list-options ","show-notations"); + deprecated_warning(configname,configlineno,"--show-notation", + "--verify-options ","show-notations"); + opt.list_options|=LIST_SHOW_NOTATIONS; + opt.verify_options|=VERIFY_SHOW_NOTATIONS; + break; + case oNoShowNotation: + deprecated_warning(configname,configlineno,"--no-show-notation", + "--list-options ","no-show-notations"); + deprecated_warning(configname,configlineno,"--no-show-notation", + "--verify-options ","no-show-notations"); + opt.list_options&=~LIST_SHOW_NOTATIONS; + opt.verify_options&=~VERIFY_SHOW_NOTATIONS; + break; + case oUtf8Strings: utf8_strings = 1; break; + case oNoUtf8Strings: utf8_strings = 0; break; + case oDisableCipherAlgo: + disable_cipher_algo( string_to_cipher_algo(pargs.r.ret_str) ); + break; + case oDisablePubkeyAlgo: + disable_pubkey_algo( string_to_pubkey_algo(pargs.r.ret_str) ); + break; + case oNoSigCache: opt.no_sig_cache = 1; break; + case oNoSigCreateCheck: opt.no_sig_create_check = 1; break; + case oAllowNonSelfsignedUID: opt.allow_non_selfsigned_uid = 1; break; + case oNoAllowNonSelfsignedUID: opt.allow_non_selfsigned_uid=0; break; + case oAllowFreeformUID: opt.allow_freeform_uid = 1; break; + case oNoAllowFreeformUID: opt.allow_freeform_uid = 0; break; + case oNoLiteral: opt.no_literal = 1; break; + case oSetFilesize: opt.set_filesize = pargs.r.ret_ulong; break; + case oHonorHttpProxy: + add_to_strlist(&opt.keyserver_options.other,"http-proxy"); + deprecated_warning(configname,configlineno, + "--honor-http-proxy", + "--keyserver-options ","http-proxy"); + break; + case oFastListMode: opt.fast_list_mode = 1; break; + case oFixedListMode: opt.fixed_list_mode = 1; break; + case oListOnly: opt.list_only=1; break; + case oIgnoreTimeConflict: opt.ignore_time_conflict = 1; break; + case oIgnoreValidFrom: opt.ignore_valid_from = 1; break; + case oIgnoreCrcError: opt.ignore_crc_error = 1; break; + case oIgnoreMDCError: opt.ignore_mdc_error = 1; break; + case oNoRandomSeedFile: use_random_seed = 0; break; + case oAutoKeyRetrieve: + case oNoAutoKeyRetrieve: + if(pargs.r_opt==oAutoKeyRetrieve) + opt.keyserver_options.options|=KEYSERVER_AUTO_KEY_RETRIEVE; + else + opt.keyserver_options.options&=~KEYSERVER_AUTO_KEY_RETRIEVE; + + deprecated_warning(configname,configlineno, + pargs.r_opt==oAutoKeyRetrieve?"--auto-key-retrieve": + "--no-auto-key-retrieve","--keyserver-options ", + pargs.r_opt==oAutoKeyRetrieve?"auto-key-retrieve": + "no-auto-key-retrieve"); + break; + case oShowSessionKey: opt.show_session_key = 1; break; + case oOverrideSessionKey: + opt.override_session_key = pargs.r.ret_str; + break; + case oMergeOnly: + deprecated_warning(configname,configlineno,"--merge-only", + "--import-options ","merge-only"); + opt.import_options|=IMPORT_MERGE_ONLY; + break; + case oAllowSecretKeyImport: /* obsolete */ break; + case oTryAllSecrets: opt.try_all_secrets = 1; break; + case oTrustedKey: register_trusted_key( pargs.r.ret_str ); break; + case oEnableSpecialFilenames: + iobuf_enable_special_filenames (1); + break; + case oNoExpensiveTrustChecks: opt.no_expensive_trust_checks=1; break; + case oAutoCheckTrustDB: opt.no_auto_check_trustdb=0; break; + case oNoAutoCheckTrustDB: opt.no_auto_check_trustdb=1; break; + case oPreservePermissions: opt.preserve_permissions=1; break; + case oDefaultPreferenceList: + opt.def_preference_list = pargs.r.ret_str; + break; + case oDefaultKeyserverURL: + { + struct keyserver_spec *keyserver; + keyserver=parse_keyserver_uri(pargs.r.ret_str,1, + configname,configlineno); + if(!keyserver) + log_error(_("could not parse keyserver URL\n")); + else + free_keyserver_spec(keyserver); + + opt.def_keyserver_url = pargs.r.ret_str; + } + break; + case oPersonalCipherPreferences: + pers_cipher_list=pargs.r.ret_str; + break; + case oPersonalDigestPreferences: + pers_digest_list=pargs.r.ret_str; + break; + case oPersonalCompressPreferences: + pers_compress_list=pargs.r.ret_str; + break; + case oDisplay: opt.display = pargs.r.ret_str; break; + case oTTYname: opt.ttyname = pargs.r.ret_str; break; + case oTTYtype: opt.ttytype = pargs.r.ret_str; break; + case oLCctype: opt.lc_ctype = pargs.r.ret_str; break; + case oLCmessages: opt.lc_messages = pargs.r.ret_str; break; + case oGroup: add_group(pargs.r.ret_str); break; + case oUnGroup: rm_group(pargs.r.ret_str); break; + case oNoGroups: + while(opt.grouplist) + { + struct groupitem *iter=opt.grouplist; + free_strlist(iter->values); + opt.grouplist=opt.grouplist->next; + xfree(iter); + } + break; + case oStrict: opt.strict=1; log_set_strict(1); break; + case oNoStrict: opt.strict=0; log_set_strict(0); break; + case oMangleDosFilenames: opt.mangle_dos_filenames = 1; break; + case oNoMangleDosFilenames: opt.mangle_dos_filenames = 0; break; + case oEnableProgressFilter: opt.enable_progress_filter = 1; break; + case oMultifile: multifile=1; break; + case oKeyidFormat: + if(ascii_strcasecmp(pargs.r.ret_str,"short")==0) + opt.keyid_format=KF_SHORT; + else if(ascii_strcasecmp(pargs.r.ret_str,"long")==0) + opt.keyid_format=KF_LONG; + else if(ascii_strcasecmp(pargs.r.ret_str,"0xshort")==0) + opt.keyid_format=KF_0xSHORT; + else if(ascii_strcasecmp(pargs.r.ret_str,"0xlong")==0) + opt.keyid_format=KF_0xLONG; + else + log_error("unknown keyid-format `%s'\n",pargs.r.ret_str); + break; + + case oExitOnStatusWriteError: + opt.exit_on_status_write_error = 1; + break; + + case oLimitCardInsertTries: + opt.limit_card_insert_tries = pargs.r.ret_int; + break; + + case oRequireCrossCert: opt.flags.require_cross_cert=1; break; + case oNoRequireCrossCert: opt.flags.require_cross_cert=0; break; + + case oAutoKeyLocate: + if(!parse_auto_key_locate(pargs.r.ret_str)) + { + if(configname) + log_error(_("%s:%d: invalid auto-key-locate list\n"), + configname,configlineno); + else + log_error(_("invalid auto-key-locate list\n")); + } + break; + case oNoAutoKeyLocate: + release_akl(); + break; + + case oAllowMultisigVerification: + opt.allow_multisig_verification = 1; + break; + + case oEnableDSA2: opt.flags.dsa2=1; break; + case oDisableDSA2: opt.flags.dsa2=0; break; + + case oNoop: break; + + default : pargs.err = configfp? 1:2; break; + } + } + + + if( configfp ) { + fclose( configfp ); + configfp = NULL; + /* Remember the first config file name. */ + if (!save_configname) + save_configname = configname; + else + xfree(configname); + configname = NULL; + goto next_pass; + } + xfree( configname ); configname = NULL; + if( log_get_errorcount(0) ) + g10_exit(2); + + /* The command --gpgconf-list is pretty simple and may be called + directly after the option parsing. */ + if (cmd == aGPGConfList) + { + gpgconf_list (save_configname); + g10_exit (0); + } + xfree (save_configname); + + if( nogreeting ) + greeting = 0; + + if( greeting ) { + fprintf(stderr, "%s %s; %s\n", + strusage(11), strusage(13), strusage(14) ); + fprintf(stderr, "%s\n", strusage(15) ); + } +#ifdef IS_DEVELOPMENT_VERSION + if( !opt.batch ) + { + const char *s; + + if((s=strusage(20))) + log_info("%s\n",s); + if((s=strusage(21))) + log_info("%s\n",s); + if((s=strusage(22))) + log_info("%s\n",s); + } +#endif + + if (opt.verbose > 2) + log_info ("using character set `%s'\n", get_native_charset ()); + + if( may_coredump && !opt.quiet ) + log_info(_("WARNING: program may create a core file!\n")); + + if (eyes_only) { + if (opt.set_filename) + log_info(_("WARNING: %s overrides %s\n"), + "--for-your-eyes-only","--set-filename"); + + opt.set_filename="_CONSOLE"; + } + + if (opt.no_literal) { + log_info(_("NOTE: %s is not for normal use!\n"), "--no-literal"); + if (opt.textmode) + log_error(_("%s not allowed with %s!\n"), + "--textmode", "--no-literal" ); + if (opt.set_filename) + log_error(_("%s makes no sense with %s!\n"), + eyes_only?"--for-your-eyes-only":"--set-filename", + "--no-literal" ); + } + +#ifndef ENABLE_AGENT_SUPPORT + if (opt.use_agent) { + log_info(_("NOTE: %s is not available in this version\n"), + "--use-agent"); + opt.use_agent = 0; + } +#endif /*!ENABLE_AGENT_SUPPORT*/ + + if (opt.set_filesize) + log_info(_("NOTE: %s is not for normal use!\n"), "--set-filesize"); + if( opt.batch ) + tty_batchmode( 1 ); + + secmem_set_flags( secmem_get_flags() & ~2 ); /* resume warnings */ + + if(require_secmem && !got_secmem) + { + log_info(_("will not run with insecure memory due to %s\n"), + "--require-secmem"); + g10_exit(2); + } + + set_debug(); + + /* Do these after the switch(), so they can override settings. */ + if(PGP2) + { + int unusable=0; + + if(cmd==aSign && !detached_sig) + { + log_info(_("you can only make detached or clear signatures " + "while in --pgp2 mode\n")); + unusable=1; + } + else if(cmd==aSignEncr || cmd==aSignSym) + { + log_info(_("you can't sign and encrypt at the " + "same time while in --pgp2 mode\n")); + unusable=1; + } + else if(argc==0 && (cmd==aSign || cmd==aEncr || cmd==aSym)) + { + log_info(_("you must use files (and not a pipe) when " + "working with --pgp2 enabled.\n")); + unusable=1; + } + else if(cmd==aEncr || cmd==aSym) + { + /* Everything else should work without IDEA (except using + a secret key encrypted with IDEA and setting an IDEA + preference, but those have their own error + messages). */ + + if(check_cipher_algo(CIPHER_ALGO_IDEA)) + { + log_info(_("encrypting a message in --pgp2 mode requires " + "the IDEA cipher\n")); + idea_cipher_warn(1); + unusable=1; + } + else if(cmd==aSym) + { + /* This only sets IDEA for symmetric encryption + since it is set via select_algo_from_prefs for + pk encryption. */ + xfree(def_cipher_string); + def_cipher_string = xstrdup("idea"); + } + + /* PGP2 can't handle the output from the textmode + filter, so we disable it for anything that could + create a literal packet (only encryption and + symmetric encryption, since we disable signing + above). */ + if(!unusable) + opt.textmode=0; + } + + if(unusable) + compliance_failure(); + else + { + opt.force_v4_certs = 0; + opt.escape_from = 1; + opt.force_v3_sigs = 1; + opt.pgp2_workarounds = 1; + opt.ask_sig_expire = 0; + opt.ask_cert_expire = 0; + xfree(def_digest_string); + def_digest_string = xstrdup("md5"); + xfree(s2k_digest_string); + s2k_digest_string = xstrdup("md5"); + opt.compress_algo = COMPRESS_ALGO_ZIP; + } + } + else if(PGP6) + { + opt.escape_from=1; + opt.force_v3_sigs=1; + opt.ask_sig_expire=0; + } + else if(PGP7) + { + opt.escape_from=1; + opt.force_v3_sigs=1; + opt.ask_sig_expire=0; + } + else if(PGP8) + { + opt.escape_from=1; + } + + /* must do this after dropping setuid, because string_to... + * may try to load an module */ + if( def_cipher_string ) { + opt.def_cipher_algo = string_to_cipher_algo(def_cipher_string); + if(opt.def_cipher_algo==0 && + (ascii_strcasecmp(def_cipher_string,"idea")==0 + || ascii_strcasecmp(def_cipher_string,"s1")==0)) + idea_cipher_warn(1); + xfree(def_cipher_string); def_cipher_string = NULL; + if( check_cipher_algo(opt.def_cipher_algo) ) + log_error(_("selected cipher algorithm is invalid\n")); + } + if( def_digest_string ) { + opt.def_digest_algo = string_to_digest_algo(def_digest_string); + xfree(def_digest_string); def_digest_string = NULL; + if( check_digest_algo(opt.def_digest_algo) ) + log_error(_("selected digest algorithm is invalid\n")); + } + if( compress_algo_string ) { + opt.compress_algo = string_to_compress_algo(compress_algo_string); + xfree(compress_algo_string); compress_algo_string = NULL; + if( check_compress_algo(opt.compress_algo) ) + log_error(_("selected compression algorithm is invalid\n")); + } + if( cert_digest_string ) { + opt.cert_digest_algo = string_to_digest_algo(cert_digest_string); + xfree(cert_digest_string); cert_digest_string = NULL; + if( check_digest_algo(opt.cert_digest_algo) ) + log_error(_("selected certification digest algorithm is invalid\n")); + } + if( s2k_cipher_string ) { + opt.s2k_cipher_algo = string_to_cipher_algo(s2k_cipher_string); + xfree(s2k_cipher_string); s2k_cipher_string = NULL; + if( check_cipher_algo(opt.s2k_cipher_algo) ) + log_error(_("selected cipher algorithm is invalid\n")); + } + if( s2k_digest_string ) { + opt.s2k_digest_algo = string_to_digest_algo(s2k_digest_string); + xfree(s2k_digest_string); s2k_digest_string = NULL; + if( check_digest_algo(opt.s2k_digest_algo) ) + log_error(_("selected digest algorithm is invalid\n")); + } + if( opt.completes_needed < 1 ) + log_error(_("completes-needed must be greater than 0\n")); + if( opt.marginals_needed < 2 ) + log_error(_("marginals-needed must be greater than 1\n")); + if( opt.max_cert_depth < 1 || opt.max_cert_depth > 255 ) + log_error(_("max-cert-depth must be in the range from 1 to 255\n")); + if(opt.def_cert_level<0 || opt.def_cert_level>3) + log_error(_("invalid default-cert-level; must be 0, 1, 2, or 3\n")); + if( opt.min_cert_level < 1 || opt.min_cert_level > 3 ) + log_error(_("invalid min-cert-level; must be 1, 2, or 3\n")); + switch( opt.s2k_mode ) { + case 0: + log_info(_("NOTE: simple S2K mode (0) is strongly discouraged\n")); + break; + case 1: case 3: break; + default: + log_error(_("invalid S2K mode; must be 0, 1 or 3\n")); + } + + /* This isn't actually needed, but does serve to error out if the + string is invalid. */ + if(opt.def_preference_list && + keygen_set_std_prefs(opt.def_preference_list,0)) + log_error(_("invalid default preferences\n")); + + /* We provide defaults for the personal digest list. This is + SHA-1. */ + if(!pers_digest_list) + pers_digest_list="h2"; + + if(pers_cipher_list && + keygen_set_std_prefs(pers_cipher_list,PREFTYPE_SYM)) + log_error(_("invalid personal cipher preferences\n")); + + if(pers_digest_list && + keygen_set_std_prefs(pers_digest_list,PREFTYPE_HASH)) + log_error(_("invalid personal digest preferences\n")); + + if(pers_compress_list && + keygen_set_std_prefs(pers_compress_list,PREFTYPE_ZIP)) + log_error(_("invalid personal compress preferences\n")); + + /* We don't support all possible commands with multifile yet */ + if(multifile) + { + char *cmdname; + + switch(cmd) + { + case aSign: + cmdname="--sign"; + break; + case aClearsign: + cmdname="--clearsign"; + break; + case aDetachedSign: + cmdname="--detach-sign"; + break; + case aSym: + cmdname="--symmetric"; + break; + case aEncrSym: + cmdname="--symmetric --encrypt"; + break; + case aStore: + cmdname="--store"; + break; + default: + cmdname=NULL; + break; + } + + if(cmdname) + log_error(_("%s does not yet work with %s\n"),cmdname,"--multifile"); + } + + if( log_get_errorcount(0) ) + g10_exit(2); + + if(opt.compress_level==0) + opt.compress_algo=COMPRESS_ALGO_NONE; + + /* Check our chosen algorithms against the list of legal + algorithms. */ + + if(!GNUPG) + { + const char *badalg=NULL; + preftype_t badtype=PREFTYPE_NONE; + + if(opt.def_cipher_algo + && !algo_available(PREFTYPE_SYM,opt.def_cipher_algo,NULL)) + { + badalg=cipher_algo_to_string(opt.def_cipher_algo); + badtype=PREFTYPE_SYM; + } + else if(opt.def_digest_algo + && !algo_available(PREFTYPE_HASH,opt.def_digest_algo,NULL)) + { + badalg=digest_algo_to_string(opt.def_digest_algo); + badtype=PREFTYPE_HASH; + } + else if(opt.cert_digest_algo + && !algo_available(PREFTYPE_HASH,opt.cert_digest_algo,NULL)) + { + badalg=digest_algo_to_string(opt.cert_digest_algo); + badtype=PREFTYPE_HASH; + } + else if(opt.compress_algo!=-1 + && !algo_available(PREFTYPE_ZIP,opt.compress_algo,NULL)) + { + badalg=compress_algo_to_string(opt.compress_algo); + badtype=PREFTYPE_ZIP; + } + + if(badalg) + { + switch(badtype) + { + case PREFTYPE_SYM: + log_info(_("you may not use cipher algorithm `%s'" + " while in %s mode\n"), + badalg,compliance_option_string()); + break; + case PREFTYPE_HASH: + log_info(_("you may not use digest algorithm `%s'" + " while in %s mode\n"), + badalg,compliance_option_string()); + break; + case PREFTYPE_ZIP: + log_info(_("you may not use compression algorithm `%s'" + " while in %s mode\n"), + badalg,compliance_option_string()); + break; + default: + BUG(); + } + + compliance_failure(); + } + } + + /* set the random seed file */ + if( use_random_seed ) { + char *p = make_filename(opt.homedir, "random_seed", NULL ); + set_random_seed_file(p); + if (!access (p, F_OK)) + register_secured_file (p); + xfree(p); + } + + if( !cmd && opt.fingerprint && !with_fpr ) { + set_cmd( &cmd, aListKeys); + } + + if( cmd == aKMode || cmd == aKModeC ) { /* kludge to be compatible to pgp */ + if( cmd == aKModeC ) { + opt.fingerprint = 1; + cmd = aKMode; + } + opt.list_sigs = 0; + if( opt.verbose > 2 ) + opt.check_sigs++; + if( opt.verbose > 1 ) + opt.list_sigs++; + + opt.verbose = opt.verbose > 1; + g10_opt_verbose = opt.verbose; + } + + /* kludge to let -sat generate a clear text signature */ + if( opt.textmode == 2 && !detached_sig && opt.armor && cmd == aSign ) + cmd = aClearsign; + + if( opt.verbose > 1 ) + set_packet_list_mode(1); + + if (cmd == aGPGConfTest) + g10_exit(0); + + /* Add the keyrings, but not for some special commands and not in + case of "-kvv userid keyring". Also avoid adding the secret + keyring for a couple of commands to avoid unneeded access in + case the secrings are stored on a floppy. + + We always need to add the keyrings if we are running under + SELinux, this is so that the rings are added to the list of + secured files. */ + if( ALWAYS_ADD_KEYRINGS + || (cmd != aDeArmor && cmd != aEnArmor + && !(cmd == aKMode && argc == 2 )) ) + { + if (ALWAYS_ADD_KEYRINGS + || (cmd != aCheckKeys && cmd != aListSigs && cmd != aListKeys + && cmd != aVerify && cmd != aSym)) + { + if (!sec_nrings || default_keyring) /* add default secret rings */ + keydb_add_resource ("secring" EXTSEP_S "gpg", 4, 1); + for (sl = sec_nrings; sl; sl = sl->next) + keydb_add_resource ( sl->d, 0, 1 ); + } + if( !nrings || default_keyring ) /* add default ring */ + keydb_add_resource ("pubring" EXTSEP_S "gpg", 4, 0); + for(sl = nrings; sl; sl = sl->next ) + keydb_add_resource ( sl->d, sl->flags, 0 ); + } + FREE_STRLIST(nrings); + FREE_STRLIST(sec_nrings); + + + if( pwfd != -1 ) /* read the passphrase now. */ + read_passphrase_from_fd( pwfd ); + + fname = argc? *argv : NULL; + + if(fname && utf8_strings) + opt.flags.utf8_filename=1; + + switch( cmd ) { + case aPrimegen: + case aPrintMD: + case aPrintMDs: + case aGenRandom: + case aDeArmor: + case aEnArmor: + case aFixTrustDB: + break; + case aExportOwnerTrust: rc = setup_trustdb( 0, trustdb_name ); break; + case aListTrustDB: rc = setup_trustdb( argc? 1:0, trustdb_name ); break; + default: rc = setup_trustdb(1, trustdb_name ); break; + } + if( rc ) + log_error(_("failed to initialize the TrustDB: %s\n"), g10_errstr(rc)); + + + switch (cmd) + { + case aStore: + case aSym: + case aSign: + case aSignSym: + case aClearsign: + if (!opt.quiet && any_explicit_recipient) + log_info (_("WARNING: recipients (-r) given " + "without using public key encryption\n")); + break; + default: + break; + } + + switch( cmd ) + { + case aStore: /* only store the file */ + if( argc > 1 ) + wrong_args(_("--store [filename]")); + if( (rc = encode_store(fname)) ) + log_error ("storing `%s' failed: %s\n", + print_fname_stdin(fname),g10_errstr(rc) ); + break; + case aSym: /* encrypt the given file only with the symmetric cipher */ + if( argc > 1 ) + wrong_args(_("--symmetric [filename]")); + if( (rc = encode_symmetric(fname)) ) + log_error (_("symmetric encryption of `%s' failed: %s\n"), + print_fname_stdin(fname),g10_errstr(rc) ); + break; + + case aEncr: /* encrypt the given file */ + if(multifile) + encode_crypt_files(argc, argv, remusr); + else + { + if( argc > 1 ) + wrong_args(_("--encrypt [filename]")); + if( (rc = encode_crypt(fname,remusr,0)) ) + log_error("%s: encryption failed: %s\n", + print_fname_stdin(fname), g10_errstr(rc) ); + } + break; + + case aEncrSym: + /* This works with PGP 8 in the sense that it acts just like a + symmetric message. It doesn't work at all with 2 or 6. It + might work with 7, but alas, I don't have a copy to test + with right now. */ + if( argc > 1 ) + wrong_args(_("--symmetric --encrypt [filename]")); + else if(opt.s2k_mode==0) + log_error(_("you cannot use --symmetric --encrypt" + " with --s2k-mode 0\n")); + else if(PGP2 || PGP6 || PGP7 || RFC1991) + log_error(_("you cannot use --symmetric --encrypt" + " while in %s mode\n"),compliance_option_string()); + else + { + if( (rc = encode_crypt(fname,remusr,1)) ) + log_error("%s: encryption failed: %s\n", + print_fname_stdin(fname), g10_errstr(rc) ); + } + break; + + case aSign: /* sign the given file */ + sl = NULL; + if( detached_sig ) { /* sign all files */ + for( ; argc; argc--, argv++ ) + add_to_strlist( &sl, *argv ); + } + else { + if( argc > 1 ) + wrong_args(_("--sign [filename]")); + if( argc ) { + sl = xmalloc_clear( sizeof *sl + strlen(fname)); + strcpy(sl->d, fname); + } + } + if( (rc = sign_file( sl, detached_sig, locusr, 0, NULL, NULL)) ) + log_error("signing failed: %s\n", g10_errstr(rc) ); + free_strlist(sl); + break; + + case aSignEncr: /* sign and encrypt the given file */ + if( argc > 1 ) + wrong_args(_("--sign --encrypt [filename]")); + if( argc ) { + sl = xmalloc_clear( sizeof *sl + strlen(fname)); + strcpy(sl->d, fname); + } + else + sl = NULL; + if( (rc = sign_file(sl, detached_sig, locusr, 1, remusr, NULL)) ) + log_error("%s: sign+encrypt failed: %s\n", + print_fname_stdin(fname), g10_errstr(rc) ); + free_strlist(sl); + break; + + case aSignEncrSym: /* sign and encrypt the given file */ + if( argc > 1 ) + wrong_args(_("--symmetric --sign --encrypt [filename]")); + else if(opt.s2k_mode==0) + log_error(_("you cannot use --symmetric --sign --encrypt" + " with --s2k-mode 0\n")); + else if(PGP2 || PGP6 || PGP7 || RFC1991) + log_error(_("you cannot use --symmetric --sign --encrypt" + " while in %s mode\n"),compliance_option_string()); + else + { + if( argc ) + { + sl = xmalloc_clear( sizeof *sl + strlen(fname)); + strcpy(sl->d, fname); + } + else + sl = NULL; + if( (rc = sign_file(sl, detached_sig, locusr, 2, remusr, NULL)) ) + log_error("%s: symmetric+sign+encrypt failed: %s\n", + print_fname_stdin(fname), g10_errstr(rc) ); + free_strlist(sl); + } + break; + + case aSignSym: /* sign and conventionally encrypt the given file */ + if (argc > 1) + wrong_args(_("--sign --symmetric [filename]")); + rc = sign_symencrypt_file (fname, locusr); + if (rc) + log_error("%s: sign+symmetric failed: %s\n", + print_fname_stdin(fname), g10_errstr(rc) ); + break; + + case aClearsign: /* make a clearsig */ + if( argc > 1 ) + wrong_args(_("--clearsign [filename]")); + if( (rc = clearsign_file(fname, locusr, NULL)) ) + log_error("%s: clearsign failed: %s\n", + print_fname_stdin(fname), g10_errstr(rc) ); + break; + + case aVerify: + if(multifile) + { + if( (rc = verify_files( argc, argv ) )) + log_error("verify files failed: %s\n", g10_errstr(rc) ); + } + else + { + if( (rc = verify_signatures( argc, argv ) )) + log_error("verify signatures failed: %s\n", g10_errstr(rc) ); + } + break; + + case aDecrypt: + if(multifile) + decrypt_messages(argc, argv); + else + { + if( argc > 1 ) + wrong_args(_("--decrypt [filename]")); + if( (rc = decrypt_message( fname ) )) + log_error("decrypt_message failed: %s\n", g10_errstr(rc) ); + } + break; + + case aSignKey: + if( argc != 1 ) + wrong_args(_("--sign-key user-id")); + /* fall through */ + case aLSignKey: + if( argc != 1 ) + wrong_args(_("--lsign-key user-id")); + /* fall through */ + + sl=NULL; + + if(cmd==aSignKey) + append_to_strlist(&sl,"sign"); + else if(cmd==aLSignKey) + append_to_strlist(&sl,"lsign"); + else + BUG(); + + append_to_strlist( &sl, "save" ); + username = make_username( fname ); + keyedit_menu(fname, locusr, sl, 0, 0 ); + xfree(username); + free_strlist(sl); + break; + + case aEditKey: /* Edit a key signature */ + if( !argc ) + wrong_args(_("--edit-key user-id [commands]")); + username = make_username( fname ); + if( argc > 1 ) { + sl = NULL; + for( argc--, argv++ ; argc; argc--, argv++ ) + append_to_strlist( &sl, *argv ); + keyedit_menu( username, locusr, sl, 0, 1 ); + free_strlist(sl); + } + else + keyedit_menu(username, locusr, NULL, 0, 1 ); + xfree(username); + break; + + case aDeleteKeys: + case aDeleteSecretKeys: + case aDeleteSecretAndPublicKeys: + sl = NULL; + /* I'm adding these in reverse order as add_to_strlist2 + reverses them again, and it's easier to understand in the + proper order :) */ + for( ; argc; argc-- ) + add_to_strlist2( &sl, argv[argc-1], utf8_strings ); + delete_keys(sl,cmd==aDeleteSecretKeys,cmd==aDeleteSecretAndPublicKeys); + free_strlist(sl); + break; + + case aCheckKeys: + opt.check_sigs = 1; + case aListSigs: + opt.list_sigs = 1; + case aListKeys: + sl = NULL; + for( ; argc; argc--, argv++ ) + add_to_strlist2( &sl, *argv, utf8_strings ); + public_key_list( sl ); + free_strlist(sl); + break; + case aListSecretKeys: + sl = NULL; + for( ; argc; argc--, argv++ ) + add_to_strlist2( &sl, *argv, utf8_strings ); + secret_key_list( sl ); + free_strlist(sl); + break; + + case aKMode: /* list keyring -- NOTE: This will be removed soon */ + if( argc < 2 ) { /* -kv [userid] */ + sl = NULL; + if (argc && **argv) + add_to_strlist2( &sl, *argv, utf8_strings ); + public_key_list( sl ); + free_strlist(sl); + } + else if( argc == 2 ) { /* -kv userid keyring */ + if( access( argv[1], R_OK ) ) { + log_error(_("can't open `%s': %s\n"), + print_fname_stdin(argv[1]), strerror(errno)); + } + else { + /* add keyring (default keyrings are not registered in this + * special case */ + keydb_add_resource( argv[1], 0, 0 ); + sl = NULL; + if (**argv) + add_to_strlist2( &sl, *argv, utf8_strings ); + public_key_list( sl ); + free_strlist(sl); + } + } + else + wrong_args(_("-k[v][v][v][c] [user-id] [keyring]") ); + break; + + case aKeygen: /* generate a key */ + if( opt.batch ) { + if( argc > 1 ) + wrong_args("--gen-key [parameterfile]"); + generate_keypair( argc? *argv : NULL, NULL, NULL ); + } + else { + if( argc ) + wrong_args("--gen-key"); + generate_keypair(NULL, NULL, NULL); + } + break; + + case aFastImport: + opt.import_options |= IMPORT_FAST; + case aImport: + import_keys( argc? argv:NULL, argc, NULL, opt.import_options ); + break; + + /* TODO: There are a number of command that use this same + "make strlist, call function, report error, free strlist" + pattern. Join them together here and avoid all that + duplicated code. */ + + case aExport: + case aSendKeys: + case aRecvKeys: + sl = NULL; + for( ; argc; argc--, argv++ ) + append_to_strlist2( &sl, *argv, utf8_strings ); + if( cmd == aSendKeys ) + rc=keyserver_export( sl ); + else if( cmd == aRecvKeys ) + rc=keyserver_import( sl ); + else + rc=export_pubkeys( sl, opt.export_options ); + if(rc) + { + if(cmd==aSendKeys) + log_error(_("keyserver send failed: %s\n"),g10_errstr(rc)); + else if(cmd==aRecvKeys) + log_error(_("keyserver receive failed: %s\n"),g10_errstr(rc)); + else + log_error(_("key export failed: %s\n"),g10_errstr(rc)); + } + free_strlist(sl); + break; + + case aSearchKeys: + sl = NULL; + for( ; argc; argc--, argv++ ) + append_to_strlist2( &sl, *argv, utf8_strings ); + rc=keyserver_search( sl ); + if(rc) + log_error(_("keyserver search failed: %s\n"),g10_errstr(rc)); + free_strlist(sl); + break; + + case aRefreshKeys: + sl = NULL; + for( ; argc; argc--, argv++ ) + append_to_strlist2( &sl, *argv, utf8_strings ); + rc=keyserver_refresh(sl); + if(rc) + log_error(_("keyserver refresh failed: %s\n"),g10_errstr(rc)); + free_strlist(sl); + break; + + case aFetchKeys: + sl = NULL; + for( ; argc; argc--, argv++ ) + append_to_strlist2( &sl, *argv, utf8_strings ); + rc=keyserver_fetch(sl); + if(rc) + log_error("key fetch failed: %s\n",g10_errstr(rc)); + free_strlist(sl); + break; + + case aExportSecret: + sl = NULL; + for( ; argc; argc--, argv++ ) + add_to_strlist2( &sl, *argv, utf8_strings ); + export_seckeys( sl ); + free_strlist(sl); + break; + + case aExportSecretSub: + sl = NULL; + for( ; argc; argc--, argv++ ) + add_to_strlist2( &sl, *argv, utf8_strings ); + export_secsubkeys( sl ); + free_strlist(sl); + break; + + case aGenRevoke: + if( argc != 1 ) + wrong_args("--gen-revoke user-id"); + username = make_username(*argv); + gen_revoke( username ); + xfree( username ); + break; + + case aDesigRevoke: + if( argc != 1 ) + wrong_args("--desig-revoke user-id"); + username = make_username(*argv); + gen_desig_revoke( username, locusr ); + xfree( username ); + break; + + case aDeArmor: + if( argc > 1 ) + wrong_args("--dearmor [file]"); + rc = dearmor_file( argc? *argv: NULL ); + if( rc ) + log_error(_("dearmoring failed: %s\n"), g10_errstr(rc)); + break; + + case aEnArmor: + if( argc > 1 ) + wrong_args("--enarmor [file]"); + rc = enarmor_file( argc? *argv: NULL ); + if( rc ) + log_error(_("enarmoring failed: %s\n"), g10_errstr(rc)); + break; + + + case aPrimegen: + { int mode = argc < 2 ? 0 : atoi(*argv); + + if( mode == 1 && argc == 2 ) { + mpi_print( stdout, generate_public_prime( atoi(argv[1]) ), 1); + } + else if( mode == 2 && argc == 3 ) { + mpi_print( stdout, generate_elg_prime( + 0, atoi(argv[1]), + atoi(argv[2]), NULL,NULL ), 1); + } + else if( mode == 3 && argc == 3 ) { + MPI *factors; + mpi_print( stdout, generate_elg_prime( + 1, atoi(argv[1]), + atoi(argv[2]), NULL,&factors ), 1); + putchar('\n'); + mpi_print( stdout, factors[0], 1 ); /* print q */ + } + else if( mode == 4 && argc == 3 ) { + MPI g = mpi_alloc(1); + mpi_print( stdout, generate_elg_prime( + 0, atoi(argv[1]), + atoi(argv[2]), g, NULL ), 1); + putchar('\n'); + mpi_print( stdout, g, 1 ); + mpi_free(g); + } + else + wrong_args("--gen-prime mode bits [qbits] "); + putchar('\n'); + } + break; + + case aGenRandom: + { + int level = argc ? atoi(*argv):0; + int count = argc > 1 ? atoi(argv[1]): 0; + int endless = !count; + + if( argc < 1 || argc > 2 || level < 0 || level > 2 || count < 0 ) + wrong_args("--gen-random 0|1|2 [count]"); + + while( endless || count ) { + byte *p; + /* Wee need a multiple of 3, so that in case of + armored output we get a correct string. No + linefolding is done, as it is best to levae this to + other tools */ + size_t n = !endless && count < 99? count : 99; + + p = get_random_bits( n*8, level, 0); +#ifdef HAVE_DOSISH_SYSTEM + setmode ( fileno(stdout), O_BINARY ); +#endif + if (opt.armor) { + char *tmp = make_radix64_string (p, n); + fputs (tmp, stdout); + xfree (tmp); + if (n%3 == 1) + putchar ('='); + if (n%3) + putchar ('='); + } else { + fwrite( p, n, 1, stdout ); + } + xfree(p); + if( !endless ) + count -= n; + } + if (opt.armor) + putchar ('\n'); + } + break; + + case aPrintMD: + if( argc < 1) + wrong_args("--print-md algo [files]"); + { + int all_algos = (**argv=='*' && !(*argv)[1]); + int algo = all_algos? 0 : string_to_digest_algo(*argv); + + if( !algo && !all_algos ) + log_error(_("invalid hash algorithm `%s'\n"), *argv ); + else { + argc--; argv++; + if( !argc ) + print_mds(NULL, algo); + else { + for(; argc; argc--, argv++ ) + print_mds(*argv, algo); + } + } + } + break; + + case aPrintMDs: /* old option */ + if( !argc ) + print_mds(NULL,0); + else { + for(; argc; argc--, argv++ ) + print_mds(*argv,0); + } + break; + + case aListTrustDB: + if( !argc ) + list_trustdb(NULL); + else { + for( ; argc; argc--, argv++ ) + list_trustdb( *argv ); + } + break; + + case aUpdateTrustDB: + if( argc ) + wrong_args("--update-trustdb"); + update_trustdb(); + break; + + case aCheckTrustDB: + /* Old versions allowed for arguments - ignore them */ + check_trustdb(); + break; + + case aFixTrustDB: + log_error("this command is not yet implemented.\n"); + log_error("A workaround is to use \"--export-ownertrust\", remove\n"); + log_error("the trustdb file and do an \"--import-ownertrust\".\n" ); + break; + + case aListTrustPath: + if( !argc ) + wrong_args("--list-trust-path <user-ids>"); + for( ; argc; argc--, argv++ ) { + username = make_username( *argv ); + list_trust_path( username ); + xfree(username); + } + break; + + case aExportOwnerTrust: + if( argc ) + wrong_args("--export-ownertrust"); + export_ownertrust(); + break; + + case aImportOwnerTrust: + if( argc > 1 ) + wrong_args("--import-ownertrust [file]"); + import_ownertrust( argc? *argv:NULL ); + break; + + case aPipeMode: + if ( argc ) + wrong_args ("--pipemode"); + run_in_pipemode (); + break; + + case aRebuildKeydbCaches: + if (argc) + wrong_args ("--rebuild-keydb-caches"); + keydb_rebuild_caches (1); + break; + +#ifdef ENABLE_CARD_SUPPORT + case aCardStatus: + if (argc) + wrong_args ("--card-status"); + card_status (stdout, NULL, 0); + break; + + case aCardEdit: + if (argc) { + sl = NULL; + for (argc--, argv++ ; argc; argc--, argv++) + append_to_strlist (&sl, *argv); + card_edit (sl); + free_strlist (sl); + } + else + card_edit (NULL); + break; + + case aChangePIN: + if (!argc) + change_pin (0,1); + else if (argc == 1) + change_pin (atoi (*argv),1); + else + wrong_args ("--change-pin [no]"); + break; +#endif /* ENABLE_CARD_SUPPORT*/ + + case aListConfig: + { + char *str=collapse_args(argc,argv); + list_config(str); + xfree(str); + } + break; + + case aListPackets: + opt.list_packets=2; + default: + if( argc > 1 ) + wrong_args(_("[filename]")); + /* Issue some output for the unix newbie */ + if( !fname && !opt.outfile && isatty( fileno(stdin) ) + && isatty( fileno(stdout) ) && isatty( fileno(stderr) ) ) + log_info(_("Go ahead and type your message ...\n")); + + a = iobuf_open(fname); + if (a && is_secured_file (iobuf_get_fd (a))) + { + iobuf_close (a); + a = NULL; + errno = EPERM; + } + if( !a ) + log_error(_("can't open `%s'\n"), print_fname_stdin(fname)); + else { + + if( !opt.no_armor ) { + if( use_armor_filter( a ) ) { + memset( &afx, 0, sizeof afx); + iobuf_push_filter( a, armor_filter, &afx ); + } + } + if( cmd == aListPackets ) { + set_packet_list_mode(1); + opt.list_packets=1; + } + rc = proc_packets(NULL, a ); + if( rc ) + log_error("processing message failed: %s\n", g10_errstr(rc) ); + iobuf_close(a); + } + break; + } + + /* cleanup */ + FREE_STRLIST(remusr); + FREE_STRLIST(locusr); + g10_exit(0); + return 8; /*NEVER REACHED*/ +} + + +void +g10_exit( int rc ) +{ +#ifdef ENABLE_CARD_SUPPORT + card_close (); +#endif + update_random_seed_file(); + if( opt.debug & DBG_MEMSTAT_VALUE ) { + m_print_stats("on exit"); + random_dump_stats(); + } + if( opt.debug ) + secmem_dump_stats(); + secmem_term(); + rc = rc? rc : log_get_errorcount(0)? 2 : + g10_errors_seen? 1 : 0; + exit(rc ); +} + + +/* Pretty-print hex hashes. This assumes at least an 80-character + display, but there are a few other similar assumptions in the + display code. */ +static void +print_hex( MD_HANDLE md, int algo, const char *fname ) +{ + int i,n,count,indent=0; + const byte *p; + + if(fname) + indent=printf("%s: ",fname); + + if(indent>40) + { + printf("\n"); + indent=0; + } + + if(algo==DIGEST_ALGO_RMD160) + indent+=printf("RMD160 = "); + else if(algo>0) + indent+=printf("%6s = ",digest_algo_to_string(algo)); + else + algo=abs(algo); + + count=indent; + + p = md_read( md, algo ); + n = md_digest_length(algo); + + count+=printf("%02X",*p++); + + for(i=1;i<n;i++,p++) + { + if(n==16) + { + if(count+2>79) + { + printf("\n%*s",indent," "); + count=indent; + } + else + count+=printf(" "); + + if(!(i%8)) + count+=printf(" "); + } + else if (n==20) + { + if(!(i%2)) + { + if(count+4>79) + { + printf("\n%*s",indent," "); + count=indent; + } + else + count+=printf(" "); + } + + if(!(i%10)) + count+=printf(" "); + } + else + { + if(!(i%4)) + { + if(count+8>79) + { + printf("\n%*s",indent," "); + count=indent; + } + else + count+=printf(" "); + } + } + + count+=printf("%02X",*p); + } + + printf("\n"); +} + +static void +print_hashline( MD_HANDLE md, int algo, const char *fname ) +{ + int i, n; + const byte *p; + + if ( fname ) { + for (p = fname; *p; p++ ) { + if ( *p <= 32 || *p > 127 || *p == ':' || *p == '%' ) + printf("%%%02X", *p ); + else + putchar( *p ); + } + } + putchar(':'); + printf("%d:", algo ); + p = md_read( md, algo ); + n = md_digest_length(algo); + for(i=0; i < n ; i++, p++ ) + printf("%02X", *p ); + putchar(':'); + putchar('\n'); +} + +static void +print_mds( const char *fname, int algo ) +{ + FILE *fp; + char buf[1024]; + size_t n; + MD_HANDLE md; + + if( !fname ) { + fp = stdin; +#ifdef HAVE_DOSISH_SYSTEM + setmode ( fileno(fp) , O_BINARY ); +#endif + } + else { + fp = fopen( fname, "rb" ); + if (fp && is_secured_file (fileno (fp))) + { + fclose (fp); + fp = NULL; + errno = EPERM; + } + } + if( !fp ) { + log_error("%s: %s\n", fname?fname:"[stdin]", strerror(errno) ); + return; + } + + md = md_open( 0, 0 ); + if( algo ) + md_enable( md, algo ); + else { + md_enable( md, DIGEST_ALGO_MD5 ); + md_enable( md, DIGEST_ALGO_SHA1 ); + md_enable( md, DIGEST_ALGO_RMD160 ); +#ifdef USE_SHA256 + md_enable( md, DIGEST_ALGO_SHA224 ); + md_enable( md, DIGEST_ALGO_SHA256 ); +#endif +#ifdef USE_SHA512 + md_enable( md, DIGEST_ALGO_SHA384 ); + md_enable( md, DIGEST_ALGO_SHA512 ); +#endif + } + + while( (n=fread( buf, 1, DIM(buf), fp )) ) + md_write( md, buf, n ); + if( ferror(fp) ) + log_error("%s: %s\n", fname?fname:"[stdin]", strerror(errno) ); + else { + md_final(md); + if ( opt.with_colons ) { + if ( algo ) + print_hashline( md, algo, fname ); + else { + print_hashline( md, DIGEST_ALGO_MD5, fname ); + print_hashline( md, DIGEST_ALGO_SHA1, fname ); + print_hashline( md, DIGEST_ALGO_RMD160, fname ); +#ifdef USE_SHA256 + print_hashline( md, DIGEST_ALGO_SHA224, fname ); + print_hashline( md, DIGEST_ALGO_SHA256, fname ); +#endif +#ifdef USE_SHA512 + print_hashline( md, DIGEST_ALGO_SHA384, fname ); + print_hashline( md, DIGEST_ALGO_SHA512, fname ); +#endif + } + } + else { + if( algo ) + print_hex(md,-algo,fname); + else { + print_hex( md, DIGEST_ALGO_MD5, fname ); + print_hex( md, DIGEST_ALGO_SHA1, fname ); + print_hex( md, DIGEST_ALGO_RMD160, fname ); +#ifdef USE_SHA256 + print_hex( md, DIGEST_ALGO_SHA224, fname ); + print_hex( md, DIGEST_ALGO_SHA256, fname ); +#endif +#ifdef USE_SHA512 + print_hex( md, DIGEST_ALGO_SHA384, fname ); + print_hex( md, DIGEST_ALGO_SHA512, fname ); +#endif + } + } + } + md_close(md); + + if( fp != stdin ) + fclose(fp); +} + + +/**************** + * Check the supplied name,value string and add it to the notation + * data to be used for signatures. which==0 for sig notations, and 1 + * for cert notations. +*/ +static void +add_notation_data( const char *string, int which ) +{ + struct notation *notation; + + notation=string_to_notation(string,utf8_strings); + if(notation) + { + if(which) + { + notation->next=opt.cert_notations; + opt.cert_notations=notation; + } + else + { + notation->next=opt.sig_notations; + opt.sig_notations=notation; + } + } +} + +static void +add_policy_url( const char *string, int which ) +{ + unsigned int i,critical=0; + STRLIST sl; + + if(*string=='!') + { + string++; + critical=1; + } + + for(i=0;i<strlen(string);i++) + if( !isascii (string[i]) || iscntrl(string[i])) + break; + + if(i==0 || i<strlen(string)) + { + if(which) + log_error(_("the given certification policy URL is invalid\n")); + else + log_error(_("the given signature policy URL is invalid\n")); + } + + if(which) + sl=add_to_strlist( &opt.cert_policy_url, string ); + else + sl=add_to_strlist( &opt.sig_policy_url, string ); + + if(critical) + sl->flags |= 1; +} + +static void +add_keyserver_url( const char *string, int which ) +{ + unsigned int i,critical=0; + STRLIST sl; + + if(*string=='!') + { + string++; + critical=1; + } + + for(i=0;i<strlen(string);i++) + if( !isascii (string[i]) || iscntrl(string[i])) + break; + + if(i==0 || i<strlen(string)) + { + if(which) + BUG(); + else + log_error(_("the given preferred keyserver URL is invalid\n")); + } + + if(which) + BUG(); + else + sl=add_to_strlist( &opt.sig_keyserver_url, string ); + + if(critical) + sl->flags |= 1; +} diff --git a/g10/gpgv.c b/g10/gpgv.c new file mode 100644 index 0000000..5192c26 --- /dev/null +++ b/g10/gpgv.c @@ -0,0 +1,434 @@ +/* gpgv.c - The GnuPG signature verify utility + * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2005, + * 2006 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#include <config.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <unistd.h> +#ifdef HAVE_DOSISH_SYSTEM +#include <fcntl.h> /* for setmode() */ +#endif +#ifdef HAVE_LIBREADLINE +#include <stdio.h> +#include <readline/readline.h> +#endif + +#define INCLUDED_BY_MAIN_MODULE 1 +#include "packet.h" +#include "iobuf.h" +#include "memory.h" +#include "util.h" +#include "main.h" +#include "options.h" +#include "keydb.h" +#include "trustdb.h" +#include "mpi.h" +#include "cipher.h" +#include "filter.h" +#include "ttyio.h" +#include "i18n.h" +#include "status.h" +#include "g10defs.h" +#include "cardglue.h" + + +enum cmd_and_opt_values { aNull = 0, + oQuiet = 'q', + oVerbose = 'v', + oBatch = 500, + oKeyring, + oIgnoreTimeConflict, + oStatusFD, + oLoggerFD, + oHomedir, +aTest }; + + +static ARGPARSE_OPTS opts[] = { + + { 301, NULL, 0, N_("@\nOptions:\n ") }, + + { oVerbose, "verbose", 0, N_("verbose") }, + { oQuiet, "quiet", 0, N_("be somewhat more quiet") }, + { oKeyring, "keyring" ,2, N_("take the keys from this keyring")}, + { oIgnoreTimeConflict, "ignore-time-conflict", 0, + N_("make timestamp conflicts only a warning") }, + { oStatusFD, "status-fd" ,1, N_("|FD|write status info to this FD") }, + { oLoggerFD, "logger-fd",1, "@" }, + { oHomedir, "homedir", 2, "@" }, /* defaults to "~/.gnupg" */ + +{0} }; + + + +int g10_errors_seen = 0; + +const char * +strusage( int level ) +{ + const char *p; + switch( level ) { + case 11: p = "gpgv (GnuPG)"; + break; + case 13: p = VERSION; break; + case 17: p = PRINTABLE_OS_NAME; break; + case 19: p = + _("Please report bugs to <gnupg-bugs@gnu.org>.\n"); + break; + case 1: + case 40: p = + _("Usage: gpgv [options] [files] (-h for help)"); + break; + case 41: p = + _("Syntax: gpg [options] [files]\n" + "Check signatures against known trusted keys\n"); + break; + + default: p = default_strusage(level); + } + return p; +} + + + + +static void +i18n_init(void) +{ +#ifdef USE_SIMPLE_GETTEXT + set_gettext_file (PACKAGE, "Software\\GNU\\GnuPG"); +#else +#ifdef ENABLE_NLS + setlocale( LC_ALL, "" ); + bindtextdomain( PACKAGE, G10_LOCALEDIR ); + textdomain( PACKAGE ); +#endif +#endif +} + + +int +main( int argc, char **argv ) +{ + ARGPARSE_ARGS pargs; + int rc=0; + STRLIST sl; + STRLIST nrings=NULL; + unsigned configlineno; + + log_set_name("gpgv"); + init_signals(); + i18n_init(); + opt.command_fd = -1; /* no command fd */ + opt.pgp2_workarounds = 1; + opt.keyserver_options.options|=KEYSERVER_AUTO_KEY_RETRIEVE; + opt.trust_model = TM_ALWAYS; + opt.batch = 1; + + opt.homedir = default_homedir (); + + tty_no_terminal(1); + tty_batchmode(1); + disable_dotlock(); + + set_native_charset (NULL); /* Try to auto set the character set */ + + pargs.argc = &argc; + pargs.argv = &argv; + pargs.flags= 1; /* do not remove the args */ + while( optfile_parse( NULL, NULL, &configlineno, &pargs, opts) ) { + switch( pargs.r_opt ) { + case oQuiet: opt.quiet = 1; break; + case oVerbose: g10_opt_verbose++; + opt.verbose++; opt.list_sigs=1; break; + case oKeyring: append_to_strlist( &nrings, pargs.r.ret_str); break; + case oStatusFD: set_status_fd( pargs.r.ret_int ); break; + case oLoggerFD: log_set_logfile( NULL, pargs.r.ret_int ); break; + case oHomedir: opt.homedir = pargs.r.ret_str; break; + case oIgnoreTimeConflict: opt.ignore_time_conflict = 1; break; + default : pargs.err = 2; break; + } + } + + if( log_get_errorcount(0) ) + g10_exit(2); + + g10_opt_homedir = opt.homedir; + + if( opt.verbose > 1 ) + set_packet_list_mode(1); + + if( !nrings ) /* no keyring given: use default one */ + keydb_add_resource ("trustedkeys" EXTSEP_S "gpg", 0, 0); + for(sl = nrings; sl; sl = sl->next ) + keydb_add_resource (sl->d, 0, 0 ); + + FREE_STRLIST(nrings); + + if( (rc = verify_signatures( argc, argv ) )) + log_error("verify signatures failed: %s\n", g10_errstr(rc) ); + + /* cleanup */ + g10_exit(0); + return 8; /*NEVER REACHED*/ +} + + +void +g10_exit( int rc ) +{ + rc = rc? rc : log_get_errorcount(0)? 2 : + g10_errors_seen? 1 : 0; + exit(rc ); +} + + +/* Stub: + * We have to override the trustcheck from pkclist.c becuase + * this utility assumes that all keys in the keyring are trustworthy + */ +int +check_signatures_trust( PKT_signature *sig ) +{ + return 0; +} + +void +read_trust_options(byte *trust_model,ulong *created,ulong *nextcheck, + byte *marginals,byte *completes,byte *cert_depth) {} + +/* Stub: + * We don't have the trustdb , so we have to provide some stub functions + * instead + */ + +int +cache_disabled_value(PKT_public_key *pk) +{ + return 0; +} + +void +check_trustdb_stale(void) {} + +int +get_validity_info (PKT_public_key *pk, PKT_user_id *uid) +{ + return '?'; +} + +unsigned int +get_validity (PKT_public_key *pk, PKT_user_id *uid) +{ + return 0; +} + +const char * +trust_value_to_string (unsigned int value) +{ + return "err"; +} + +const char * +uid_trust_string_fixed(PKT_public_key *key,PKT_user_id *uid) +{ + return "err"; +} + +int +get_ownertrust_info (PKT_public_key *pk) +{ + return '?'; +} + +unsigned int +get_ownertrust (PKT_public_key *pk) +{ + return TRUST_UNKNOWN; +} + + +/* Stubs: + * Because we only work with trusted keys, it does not make sense to + * get them from a keyserver + */ + +struct keyserver_spec * +keyserver_match(struct keyserver_spec *spec) { return NULL; } + +int +keyserver_import_keyid( u32 *keyid, void *dummy ) +{ + return -1; +} + +int +keyserver_import_cert(const char *name) { return -1; } + +int +keyserver_import_pka(const char *name,unsigned char *fpr) { return -1; } + +int +keyserver_import_name(const char *name,struct keyserver_spec *spec) +{ + return -1; +} + +int +keyserver_import_ldap(const char *name) { return -1; } + +/* Stub: + * No encryption here but mainproc links to these functions. + */ +int +get_session_key( PKT_pubkey_enc *k, DEK *dek ) +{ + return G10ERR_GENERAL; +} +/* Stub: */ +int +get_override_session_key( DEK *dek, const char *string ) +{ + return G10ERR_GENERAL; +} +/* Stub: */ +int +decrypt_data( void *procctx, PKT_encrypted *ed, DEK *dek ) +{ + return G10ERR_GENERAL; +} + + +/* Stub: + * No interactive commnds, so we don't need the helptexts + */ +void +display_online_help( const char *keyword ) +{ +} + +/* Stub: + * We don't use secret keys, but getkey.c links to this + */ +int +check_secret_key( PKT_secret_key *sk, int n ) +{ + return G10ERR_GENERAL; +} + +/* Stub: + * No secret key, so no passphrase needed + */ +DEK * +passphrase_to_dek( u32 *keyid, int pubkey_algo, + int cipher_algo, STRING2KEY *s2k, int mode, + const char *tmp, int *canceled) +{ + if (canceled) + *canceled = 0; + return NULL; +} + +struct keyserver_spec *parse_preferred_keyserver(PKT_signature *sig) {return NULL;} +struct keyserver_spec *parse_keyserver_uri(const char *uri,int require_scheme, + const char *configname, + unsigned int configlineno) +{ + return NULL; +} + +void free_keyserver_spec(struct keyserver_spec *keyserver) {} + +/* Stubs to avoid linking to photoid.c */ +void show_photos(const struct user_attribute *attrs,int count,PKT_public_key *pk) {} +int parse_image_header(const struct user_attribute *attr,byte *type,u32 *len) {return 0;} +char *image_type_to_string(byte type,int string) {return NULL;} + +#ifdef ENABLE_CARD_SUPPORT +int agent_scd_getattr (const char *name, struct agent_card_info_s *info) {return 0;} +#endif /* ENABLE_CARD_SUPPORT */ + +/* Stubs to void linking to ../cipher/cipher.c */ +int string_to_cipher_algo( const char *string ) { return 0; } +const char *cipher_algo_to_string( int algo ) { return "?";} +void disable_cipher_algo( int algo ) {} +int check_cipher_algo( int algo ) { return -1;} +unsigned int cipher_get_keylen( int algo ) { return 0; } +unsigned int cipher_get_blocksize( int algo ) {return 0;} +CIPHER_HANDLE cipher_open( int algo, int mode, int secure ) { return NULL;} +void cipher_close( CIPHER_HANDLE c ) {} +int cipher_setkey( CIPHER_HANDLE c, byte *key, unsigned keylen ) { return -1;} +void cipher_setiv( CIPHER_HANDLE c, const byte *iv, unsigned ivlen ){} +void cipher_encrypt( CIPHER_HANDLE c, byte *outbuf, + byte *inbuf, unsigned nbytes ) {} +void cipher_decrypt( CIPHER_HANDLE c, byte *outbuf, + byte *inbuf, unsigned nbytes ) {} +void cipher_sync( CIPHER_HANDLE c ) {} + +/* Stubs to avoid linking to ../cipher/random.c */ +void random_dump_stats(void) {} +int quick_random_gen( int onoff ) { return -1;} +void randomize_buffer( byte *buffer, size_t length, int level ) {} +int random_is_faked() { return -1;} +byte *get_random_bits( size_t nbits, int level, int secure ) { return NULL;} +void set_random_seed_file( const char *name ) {} +void update_random_seed_file() {} +void fast_random_poll() {} + +/* Stubs to avoid linking of ../cipher/primegen.c */ +void register_primegen_progress ( void (*cb)( void *, int), void *cb_data ) {} +MPI generate_secret_prime( unsigned nbits ) { return NULL;} +MPI generate_public_prime( unsigned nbits ) { return NULL;} +MPI generate_elg_prime( int mode, unsigned pbits, unsigned qbits, + MPI g, MPI **ret_factors ) { return NULL;} + +/* Do not link to ../cipher/rndlinux.c */ +void rndlinux_constructor(void) {} + + +/* Stubs to avoid linking to ../util/ttyio.c */ +int tty_batchmode( int onoff ) { return 0; } +void tty_printf( const char *fmt, ... ) { } +void tty_fprintf (FILE *fp, const char *fmt, ... ) { } +void tty_print_string( const byte *p, size_t n ) { } +void tty_print_utf8_string( const byte *p, size_t n ) {} +void tty_print_utf8_string2( const byte *p, size_t n, size_t max_n ) {} +char *tty_get( const char *prompt ) { return NULL;} +char *tty_get_hidden( const char *prompt ) {return NULL; } +void tty_kill_prompt(void) {} +int tty_get_answer_is_yes( const char *prompt ) {return 0;} +int tty_no_terminal(int onoff) {return 0;} +#ifdef HAVE_LIBREADLINE +void tty_enable_completion(rl_completion_func_t *completer) {} +void tty_disable_completion(void) {} +#endif + +/* We do not do any locking, so use these stubs here */ +void disable_dotlock(void) {} +DOTLOCK create_dotlock( const char *file_to_lock ) { return NULL; } +void destroy_dotlock (DOTLOCK h) {} +int make_dotlock( DOTLOCK h, long timeout ) { return 0;} +int release_dotlock( DOTLOCK h ) {return 0;} +void remove_lockfiles(void) {} diff --git a/g10/helptext.c b/g10/helptext.c new file mode 100644 index 0000000..e1f7851 --- /dev/null +++ b/g10/helptext.c @@ -0,0 +1,300 @@ +/* helptext.c - English help texts + * Copyright (C) 1998, 1999, 2000, 2001, 2002, + * 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 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include "util.h" +#include "ttyio.h" +#include "main.h" +#include "i18n.h" + + +/**************** + * These helptexts are used for the "online" help feature. We use + * a key consisting of words and dots. Because the lookup is only + * done in an interactive mode on a user request (when she enters a "?" + * as response to a prompt) we can use a simple search through the list. + * + * Mini glossary: + * + * "user ID", "trustdb", "NOTE" and "WARNING". + */ + +static struct helptexts { const char *key; const char *help; } helptexts[] = { + +/* begin of list */ + +{ "edit_ownertrust.value", N_( +"It's up to you to assign a value here; this value will never be exported\n" +"to any 3rd party. We need it to implement the web-of-trust; it has nothing\n" +"to do with the (implicitly created) web-of-certificates." +)}, + +{ "edit_ownertrust.set_ultimate.okay", N_( + "To build the Web-of-Trust, GnuPG needs to know which keys are\n" + "ultimately trusted - those are usually the keys for which you have\n" + "access to the secret key. Answer \"yes\" to set this key to\n" + "ultimately trusted\n" +)}, + +{ "untrusted_key.override", N_( +"If you want to use this untrusted key anyway, answer \"yes\"." +)}, + +{ "pklist.user_id.enter", N_( +"Enter the user ID of the addressee to whom you want to send the message." +)}, + +{ "keygen.algo", N_( +"Select the algorithm to use.\n" +"\n" +"DSA (aka DSS) is the Digital Signature Algorithm and can only be used\n" +"for signatures.\n" +"\n" +"Elgamal is an encrypt-only algorithm.\n" +"\n" +"RSA may be used for signatures or encryption.\n" +"\n" +"The first (primary) key must always be a key which is capable of signing." +)}, + + +{ "keygen.algo.rsa_se", N_( +"In general it is not a good idea to use the same key for signing and\n" +"encryption. This algorithm should only be used in certain domains.\n" +"Please consult your security expert first." +)}, + + +{ "keygen.size", N_( + "Enter the size of the key" +)}, + +{ "keygen.size.huge.okay", N_( + "Answer \"yes\" or \"no\"" +)}, + + +{ "keygen.size.large.okay", N_( + "Answer \"yes\" or \"no\"" +)}, + + +{ "keygen.valid", N_( + "Enter the required value as shown in the prompt.\n" + "It is possible to enter a ISO date (YYYY-MM-DD) but you won't\n" + "get a good error response - instead the system tries to interpret\n" + "the given value as an interval." +)}, + +{ "keygen.valid.okay", N_( + "Answer \"yes\" or \"no\"" +)}, + + +{ "keygen.name", N_( + "Enter the name of the key holder" +)}, + + +{ "keygen.email", N_( + "please enter an optional but highly suggested email address" +)}, + +{ "keygen.comment", N_( + "Please enter an optional comment" +)}, + + +{ "keygen.userid.cmd", N_( + "" +"N to change the name.\n" +"C to change the comment.\n" +"E to change the email address.\n" +"O to continue with key generation.\n" +"Q to to quit the key generation." +)}, + +{ "keygen.sub.okay", N_( + "Answer \"yes\" (or just \"y\") if it is okay to generate the sub key." +)}, + +{ "sign_uid.okay", N_( + "Answer \"yes\" or \"no\"" +)}, + +{ "sign_uid.class", N_( +"When you sign a user ID on a key, you should first verify that the key\n" +"belongs to the person named in the user ID. It is useful for others to\n" +"know how carefully you verified this.\n\n" +"\"0\" means you make no particular claim as to how carefully you verified the\n" +" key.\n\n" +"\"1\" means you believe the key is owned by the person who claims to own it\n" +" but you could not, or did not verify the key at all. This is useful for\n" +" a \"persona\" verification, where you sign the key of a pseudonymous user.\n\n" +"\"2\" means you did casual verification of the key. For example, this could\n" +" mean that you verified the key fingerprint and checked the user ID on the\n" +" key against a photo ID.\n\n" +"\"3\" means you did extensive verification of the key. For example, this could\n" +" mean that you verified the key fingerprint with the owner of the key in\n" +" person, and that you checked, by means of a hard to forge document with a\n" +" photo ID (such as a passport) that the name of the key owner matches the\n" +" name in the user ID on the key, and finally that you verified (by exchange\n" +" of email) that the email address on the key belongs to the key owner.\n\n" +"Note that the examples given above for levels 2 and 3 are *only* examples.\n" +"In the end, it is up to you to decide just what \"casual\" and \"extensive\"\n" +"mean to you when you sign other keys.\n\n" +"If you don't know what the right answer is, answer \"0\"." +)}, + +{ "change_passwd.empty.okay", N_( + "Answer \"yes\" or \"no\"" +)}, + + +{ "keyedit.save.okay", N_( + "Answer \"yes\" or \"no\"" +)}, + + +{ "keyedit.cancel.okay", N_( + "Answer \"yes\" or \"no\"" +)}, + +{ "keyedit.sign_all.okay", N_( + "Answer \"yes\" if you want to sign ALL the user IDs" +)}, + +{ "keyedit.remove.uid.okay", N_( + "Answer \"yes\" if you really want to delete this user ID.\n" + "All certificates are then also lost!" +)}, + +{ "keyedit.remove.subkey.okay", N_( + "Answer \"yes\" if it is okay to delete the subkey" +)}, + + +{ "keyedit.delsig.valid", N_( + "This is a valid signature on the key; you normally don't want\n" + "to delete this signature because it may be important to establish a\n" + "trust connection to the key or another key certified by this key." +)}, +{ "keyedit.delsig.unknown", N_( + "This signature can't be checked because you don't have the\n" + "corresponding key. You should postpone its deletion until you\n" + "know which key was used because this signing key might establish\n" + "a trust connection through another already certified key." +)}, +{ "keyedit.delsig.invalid", N_( + "The signature is not valid. It does make sense to remove it from\n" + "your keyring." +)}, +{ "keyedit.delsig.selfsig", N_( + "This is a signature which binds the user ID to the key. It is\n" + "usually not a good idea to remove such a signature. Actually\n" + "GnuPG might not be able to use this key anymore. So do this\n" + "only if this self-signature is for some reason not valid and\n" + "a second one is available." +)}, + +{ "keyedit.updpref.okay", N_( + "Change the preferences of all user IDs (or just of the selected ones)\n" + "to the current list of preferences. The timestamp of all affected\n" + "self-signatures will be advanced by one second.\n" +)}, + + +{ "passphrase.enter", N_( + "" +"Please enter the passhrase; this is a secret sentence \n" +)}, + + +{ "passphrase.repeat", N_( + "Please repeat the last passphrase, so you are sure what you typed in." +)}, + +{ "detached_signature.filename", N_( + "Give the name of the file to which the signature applies" +)}, + +/* openfile.c (overwrite_filep) */ +{ "openfile.overwrite.okay", N_( + "Answer \"yes\" if it is okay to overwrite the file" +)}, + +/* openfile.c (ask_outfile_name) */ +{ "openfile.askoutname", N_( + "Please enter a new filename. If you just hit RETURN the default\n" + "file (which is shown in brackets) will be used." +)}, + +/* revoke.c (ask_revocation_reason) */ +{ "ask_revocation_reason.code", N_( + "You should specify a reason for the certification. Depending on the\n" + "context you have the ability to choose from this list:\n" + " \"Key has been compromised\"\n" + " Use this if you have a reason to believe that unauthorized persons\n" + " got access to your secret key.\n" + " \"Key is superseded\"\n" + " Use this if you have replaced this key with a newer one.\n" + " \"Key is no longer used\"\n" + " Use this if you have retired this key.\n" + " \"User ID is no longer valid\"\n" + " Use this to state that the user ID should not longer be used;\n" + " this is normally used to mark an email address invalid.\n" +)}, + +/* revoke.c (ask_revocation_reason) */ +{ "ask_revocation_reason.text", N_( + "If you like, you can enter a text describing why you issue this\n" + "revocation certificate. Please keep this text concise.\n" + "An empty line ends the text.\n" +)}, + +/* end of list */ +{ NULL, NULL } }; + + +void +display_online_help( const char *keyword ) +{ + + tty_kill_prompt(); + if( !keyword ) + tty_printf(_("No help available") ); + else { + const char *p; + int i; + + for(i=0; (p=helptexts[i].key) && strcmp( p, keyword ); i++ ) + ; + if( !p || !*helptexts[i].help ) + tty_printf(_("No help available for `%s'"), keyword ); + else + tty_printf("%s", _(helptexts[i].help) ); + } + tty_printf("\n"); +} diff --git a/g10/import.c b/g10/import.c new file mode 100644 index 0000000..7260301 --- /dev/null +++ b/g10/import.c @@ -0,0 +1,2396 @@ +/* import.c - import a key into our key storage. + * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, + * 2006 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <assert.h> + +#include "options.h" +#include "packet.h" +#include "errors.h" +#include "keydb.h" +#include "memory.h" +#include "util.h" +#include "trustdb.h" +#include "main.h" +#include "i18n.h" +#include "ttyio.h" +#include "status.h" +#include "keyserver-internal.h" + +struct stats_s { + ulong count; + ulong no_user_id; + ulong imported; + ulong imported_rsa; + ulong n_uids; + ulong n_sigs; + ulong n_subk; + ulong unchanged; + ulong n_revoc; + ulong secret_read; + ulong secret_imported; + ulong secret_dups; + ulong skipped_new_keys; + ulong not_imported; + ulong n_sigs_cleaned; + ulong n_uids_cleaned; +}; + + +static int import( IOBUF inp, const char* fname,struct stats_s *stats, + unsigned char **fpr,size_t *fpr_len,unsigned int options ); +static int read_block( IOBUF a, PACKET **pending_pkt, KBNODE *ret_root ); +static void revocation_present(KBNODE keyblock); +static int import_one(const char *fname, KBNODE keyblock,struct stats_s *stats, + unsigned char **fpr,size_t *fpr_len, + unsigned int options,int from_sk); +static int import_secret_one( const char *fname, KBNODE keyblock, + struct stats_s *stats, unsigned int options); +static int import_revoke_cert( const char *fname, KBNODE node, + struct stats_s *stats); +static int chk_self_sigs( const char *fname, KBNODE keyblock, + PKT_public_key *pk, u32 *keyid, int *non_self ); +static int delete_inv_parts( const char *fname, KBNODE keyblock, + u32 *keyid, unsigned int options ); +static int merge_blocks( const char *fname, KBNODE keyblock_orig, + KBNODE keyblock, u32 *keyid, + int *n_uids, int *n_sigs, int *n_subk ); +static int append_uid( KBNODE keyblock, KBNODE node, int *n_sigs, + const char *fname, u32 *keyid ); +static int append_key( KBNODE keyblock, KBNODE node, int *n_sigs, + const char *fname, u32 *keyid ); +static int merge_sigs( KBNODE dst, KBNODE src, int *n_sigs, + const char *fname, u32 *keyid ); +static int merge_keysigs( KBNODE dst, KBNODE src, int *n_sigs, + const char *fname, u32 *keyid ); + +int +parse_import_options(char *str,unsigned int *options,int noisy) +{ + struct parse_options import_opts[]= + { + {"import-local-sigs",IMPORT_LOCAL_SIGS,NULL, + N_("import signatures that are marked as local-only")}, + {"repair-pks-subkey-bug",IMPORT_REPAIR_PKS_SUBKEY_BUG,NULL, + N_("repair damage from the pks keyserver during import")}, + {"fast-import",IMPORT_FAST,NULL, + N_("do not update the trustdb after import")}, + {"convert-sk-to-pk",IMPORT_SK2PK,NULL, + N_("create a public key when importing a secret key")}, + {"merge-only",IMPORT_MERGE_ONLY,NULL, + N_("only accept updates to existing keys")}, + {"import-clean",IMPORT_CLEAN,NULL, + N_("remove unusable parts from key after import")}, + {"import-minimal",IMPORT_MINIMAL|IMPORT_CLEAN,NULL, + N_("remove as much as possible from key after import")}, + /* Aliases for backward compatibility */ + {"allow-local-sigs",IMPORT_LOCAL_SIGS,NULL,NULL}, + {"repair-hkp-subkey-bug",IMPORT_REPAIR_PKS_SUBKEY_BUG,NULL,NULL}, + /* dummy */ + {"import-unusable-sigs",0,NULL,NULL}, + {"import-clean-sigs",0,NULL,NULL}, + {"import-clean-uids",0,NULL,NULL}, + {NULL,0,NULL,NULL} + }; + + return parse_options(str,options,import_opts,noisy); +} + +void * +import_new_stats_handle (void) +{ + return xmalloc_clear ( sizeof (struct stats_s) ); +} + +void +import_release_stats_handle (void *p) +{ + xfree (p); +} + +/**************** + * Import the public keys from the given filename. Input may be armored. + * This function rejects all keys which are not validly self signed on at + * least one userid. Only user ids which are self signed will be imported. + * Other signatures are not checked. + * + * Actually this function does a merge. It works like this: + * + * - get the keyblock + * - check self-signatures and remove all userids and their signatures + * without/invalid self-signatures. + * - reject the keyblock, if we have no valid userid. + * - See whether we have this key already in one of our pubrings. + * If not, simply add it to the default keyring. + * - Compare the key and the self-signatures of the new and the one in + * our keyring. If they are different something weird is going on; + * ask what to do. + * - See whether we have only non-self-signature on one user id; if not + * ask the user what to do. + * - compare the signatures: If we already have this signature, check + * that they compare okay; if not, issue a warning and ask the user. + * (consider looking at the timestamp and use the newest?) + * - Simply add the signature. Can't verify here because we may not have + * the signature's public key yet; verification is done when putting it + * into the trustdb, which is done automagically as soon as this pubkey + * is used. + * - Proceed with next signature. + * + * Key revocation certificates have special handling. + * + */ +static int +import_keys_internal( IOBUF inp, char **fnames, int nnames, + void *stats_handle, unsigned char **fpr, size_t *fpr_len, + unsigned int options ) +{ + int i, rc = 0; + struct stats_s *stats = stats_handle; + + if (!stats) + stats = import_new_stats_handle (); + + if (inp) { + rc = import( inp, "[stream]", stats, fpr, fpr_len, options); + } + else { + if( !fnames && !nnames ) + nnames = 1; /* Ohh what a ugly hack to jump into the loop */ + + for(i=0; i < nnames; i++ ) { + const char *fname = fnames? fnames[i] : NULL; + IOBUF inp2 = iobuf_open(fname); + if( !fname ) + fname = "[stdin]"; + if (inp2 && is_secured_file (iobuf_get_fd (inp2))) + { + iobuf_close (inp2); + inp2 = NULL; + errno = EPERM; + } + if( !inp2 ) + log_error(_("can't open `%s': %s\n"), fname, strerror(errno) ); + else + { + rc = import( inp2, fname, stats, fpr, fpr_len, options ); + iobuf_close(inp2); + /* Must invalidate that ugly cache to actually close it. */ + iobuf_ioctl (NULL, 2, 0, (char*)fname); + if( rc ) + log_error("import from `%s' failed: %s\n", fname, + g10_errstr(rc) ); + } + if( !fname ) + break; + } + } + if (!stats_handle) { + import_print_stats (stats); + import_release_stats_handle (stats); + } + + /* If no fast import and the trustdb is dirty (i.e. we added a key + or userID that had something other than a selfsig, a signature + that was other than a selfsig, or any revocation), then + update/check the trustdb if the user specified by setting + interactive or by not setting no-auto-check-trustdb */ + + if(!(options&IMPORT_FAST)) + trustdb_check_or_update(); + + return rc; +} + +void +import_keys( char **fnames, int nnames, + void *stats_handle, unsigned int options ) +{ + import_keys_internal(NULL,fnames,nnames,stats_handle,NULL,NULL,options); +} + +int +import_keys_stream( IOBUF inp, void *stats_handle, + unsigned char **fpr, size_t *fpr_len,unsigned int options ) +{ + return import_keys_internal(inp,NULL,0,stats_handle,fpr,fpr_len,options); +} + +static int +import( IOBUF inp, const char* fname,struct stats_s *stats, + unsigned char **fpr,size_t *fpr_len,unsigned int options ) +{ + PACKET *pending_pkt = NULL; + KBNODE keyblock = NULL; + int rc = 0; + + getkey_disable_caches(); + + if( !opt.no_armor ) { /* armored reading is not disabled */ + armor_filter_context_t *afx = new_armor_context (); + afx->only_keyblocks = 1; + push_armor_filter (afx, inp); + release_armor_context (afx); + } + + while( !(rc = read_block( inp, &pending_pkt, &keyblock) )) { + if( keyblock->pkt->pkttype == PKT_PUBLIC_KEY ) + rc = import_one( fname, keyblock, stats, fpr, fpr_len, options, 0); + else if( keyblock->pkt->pkttype == PKT_SECRET_KEY ) + rc = import_secret_one( fname, keyblock, stats, options ); + else if( keyblock->pkt->pkttype == PKT_SIGNATURE + && keyblock->pkt->pkt.signature->sig_class == 0x20 ) + rc = import_revoke_cert( fname, keyblock, stats ); + else { + log_info( _("skipping block of type %d\n"), + keyblock->pkt->pkttype ); + } + release_kbnode(keyblock); + /* fixme: we should increment the not imported counter but this + does only make sense if we keep on going despite of errors. */ + if( rc ) + break; + if( !(++stats->count % 100) && !opt.quiet ) + log_info(_("%lu keys processed so far\n"), stats->count ); + } + if( rc == -1 ) + rc = 0; + else if( rc && rc != G10ERR_INV_KEYRING ) + log_error( _("error reading `%s': %s\n"), fname, g10_errstr(rc)); + + return rc; +} + + +void +import_print_stats (void *hd) +{ + struct stats_s *stats = hd; + + if( !opt.quiet ) { + log_info(_("Total number processed: %lu\n"), stats->count ); + if( stats->skipped_new_keys ) + log_info(_(" skipped new keys: %lu\n"), + stats->skipped_new_keys ); + if( stats->no_user_id ) + log_info(_(" w/o user IDs: %lu\n"), stats->no_user_id ); + if( stats->imported || stats->imported_rsa ) { + log_info(_(" imported: %lu"), stats->imported ); + if( stats->imported_rsa ) + fprintf(stderr, " (RSA: %lu)", stats->imported_rsa ); + putc('\n', stderr); + } + if( stats->unchanged ) + log_info(_(" unchanged: %lu\n"), stats->unchanged ); + if( stats->n_uids ) + log_info(_(" new user IDs: %lu\n"), stats->n_uids ); + if( stats->n_subk ) + log_info(_(" new subkeys: %lu\n"), stats->n_subk ); + if( stats->n_sigs ) + log_info(_(" new signatures: %lu\n"), stats->n_sigs ); + if( stats->n_revoc ) + log_info(_(" new key revocations: %lu\n"), stats->n_revoc ); + if( stats->secret_read ) + log_info(_(" secret keys read: %lu\n"), stats->secret_read ); + if( stats->secret_imported ) + log_info(_(" secret keys imported: %lu\n"), stats->secret_imported ); + if( stats->secret_dups ) + log_info(_(" secret keys unchanged: %lu\n"), stats->secret_dups ); + if( stats->not_imported ) + log_info(_(" not imported: %lu\n"), stats->not_imported ); + if( stats->n_sigs_cleaned) + log_info(_(" signatures cleaned: %lu\n"),stats->n_sigs_cleaned); + if( stats->n_uids_cleaned) + log_info(_(" user IDs cleaned: %lu\n"),stats->n_uids_cleaned); + } + + if( is_status_enabled() ) { + char buf[14*20]; + sprintf(buf, "%lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu", + stats->count, + stats->no_user_id, + stats->imported, + stats->imported_rsa, + stats->unchanged, + stats->n_uids, + stats->n_subk, + stats->n_sigs, + stats->n_revoc, + stats->secret_read, + stats->secret_imported, + stats->secret_dups, + stats->skipped_new_keys, + stats->not_imported ); + write_status_text( STATUS_IMPORT_RES, buf ); + } +} + + +/**************** + * Read the next keyblock from stream A. + * PENDING_PKT should be initialzed to NULL + * and not chnaged form the caller. + * Retunr: 0 = okay, -1 no more blocks or another errorcode. + */ +static int +read_block( IOBUF a, PACKET **pending_pkt, KBNODE *ret_root ) +{ + int rc; + PACKET *pkt; + KBNODE root = NULL; + int in_cert; + + if( *pending_pkt ) { + root = new_kbnode( *pending_pkt ); + *pending_pkt = NULL; + in_cert = 1; + } + else + in_cert = 0; + pkt = xmalloc( sizeof *pkt ); + init_packet(pkt); + while( (rc=parse_packet(a, pkt)) != -1 ) { + if( rc ) { /* ignore errors */ + if( rc != G10ERR_UNKNOWN_PACKET ) { + log_error("read_block: read error: %s\n", g10_errstr(rc) ); + rc = G10ERR_INV_KEYRING; + goto ready; + } + free_packet( pkt ); + init_packet(pkt); + continue; + } + + if( !root && pkt->pkttype == PKT_SIGNATURE + && pkt->pkt.signature->sig_class == 0x20 ) { + /* this is a revocation certificate which is handled + * in a special way */ + root = new_kbnode( pkt ); + pkt = NULL; + goto ready; + } + + /* make a linked list of all packets */ + switch( pkt->pkttype ) { + case PKT_COMPRESSED: + if(check_compress_algo(pkt->pkt.compressed->algorithm)) + { + rc = G10ERR_COMPR_ALGO; + goto ready; + } + else + { + compress_filter_context_t *cfx = xmalloc_clear( sizeof *cfx ); + pkt->pkt.compressed->buf = NULL; + push_compress_filter2(a,cfx,pkt->pkt.compressed->algorithm,1); + } + free_packet( pkt ); + init_packet(pkt); + break; + + case PKT_RING_TRUST: + /* skip those packets */ + free_packet( pkt ); + init_packet(pkt); + break; + + case PKT_PUBLIC_KEY: + case PKT_SECRET_KEY: + if( in_cert ) { /* store this packet */ + *pending_pkt = pkt; + pkt = NULL; + goto ready; + } + in_cert = 1; + default: + if( in_cert ) { + if( !root ) + root = new_kbnode( pkt ); + else + add_kbnode( root, new_kbnode( pkt ) ); + pkt = xmalloc( sizeof *pkt ); + } + init_packet(pkt); + break; + } + } + ready: + if( rc == -1 && root ) + rc = 0; + + if( rc ) + release_kbnode( root ); + else + *ret_root = root; + free_packet( pkt ); + xfree( pkt ); + return rc; +} + +/* Walk through the subkeys on a pk to find if we have the PKS + disease: multiple subkeys with their binding sigs stripped, and the + sig for the first subkey placed after the last subkey. That is, + instead of "pk uid sig sub1 bind1 sub2 bind2 sub3 bind3" we have + "pk uid sig sub1 sub2 sub3 bind1". We can't do anything about sub2 + and sub3, as they are already lost, but we can try and rescue sub1 + by reordering the keyblock so that it reads "pk uid sig sub1 bind1 + sub2 sub3". Returns TRUE if the keyblock was modified. */ + +static int +fix_pks_corruption(KBNODE keyblock) +{ + int changed=0,keycount=0; + KBNODE node,last=NULL,sknode=NULL; + + /* First determine if we have the problem at all. Look for 2 or + more subkeys in a row, followed by a single binding sig. */ + for(node=keyblock;node;last=node,node=node->next) + { + if(node->pkt->pkttype==PKT_PUBLIC_SUBKEY) + { + keycount++; + if(!sknode) + sknode=node; + } + else if(node->pkt->pkttype==PKT_SIGNATURE && + node->pkt->pkt.signature->sig_class==0x18 && + keycount>=2 && node->next==NULL) + { + /* We might have the problem, as this key has two subkeys in + a row without any intervening packets. */ + + /* Sanity check */ + if(last==NULL) + break; + + /* Temporarily attach node to sknode. */ + node->next=sknode->next; + sknode->next=node; + last->next=NULL; + + /* Note we aren't checking whether this binding sig is a + selfsig. This is not necessary here as the subkey and + binding sig will be rejected later if that is the + case. */ + if(check_key_signature(keyblock,node,NULL)) + { + /* Not a match, so undo the changes. */ + sknode->next=node->next; + last->next=node; + node->next=NULL; + break; + } + else + { + sknode->flag |= 1; /* Mark it good so we don't need to + check it again */ + changed=1; + break; + } + } + else + keycount=0; + } + + return changed; +} + + +static void +print_import_ok (PKT_public_key *pk, PKT_secret_key *sk, unsigned int reason) +{ + byte array[MAX_FINGERPRINT_LEN], *s; + char buf[MAX_FINGERPRINT_LEN*2+30], *p; + size_t i, n; + + sprintf (buf, "%u ", reason); + p = buf + strlen (buf); + + if (pk) + fingerprint_from_pk (pk, array, &n); + else + fingerprint_from_sk (sk, array, &n); + s = array; + for (i=0; i < n ; i++, s++, p += 2) + sprintf (p, "%02X", *s); + + write_status_text (STATUS_IMPORT_OK, buf); +} + +static void +print_import_check (PKT_public_key * pk, PKT_user_id * id) +{ + char * buf; + byte fpr[24]; + u32 keyid[2]; + size_t i, pos = 0, n; + + buf = xmalloc (17+41+id->len+32); + keyid_from_pk (pk, keyid); + sprintf (buf, "%08X%08X ", keyid[0], keyid[1]); + pos = 17; + fingerprint_from_pk (pk, fpr, &n); + for (i = 0; i < n; i++, pos += 2) + sprintf (buf+pos, "%02X", fpr[i]); + strcat (buf, " "); + pos += 1; + strcat (buf, id->name); + write_status_text (STATUS_IMPORT_CHECK, buf); + xfree (buf); +} + +static void +check_prefs_warning(PKT_public_key *pk) +{ + log_info(_("WARNING: key %s contains preferences for unavailable\n"), + keystr_from_pk(pk)); + /* TRANSLATORS: This string is belongs to the previous one. They are + only split up to allow printing of a common prefix. */ + log_info(_(" algorithms on these user IDs:\n")); +} + +static void +check_prefs(KBNODE keyblock) +{ + KBNODE node; + PKT_public_key *pk; + int problem=0; + + merge_keys_and_selfsig(keyblock); + pk=keyblock->pkt->pkt.public_key; + + for(node=keyblock;node;node=node->next) + { + if(node->pkt->pkttype==PKT_USER_ID + && node->pkt->pkt.user_id->created + && node->pkt->pkt.user_id->prefs) + { + PKT_user_id *uid=node->pkt->pkt.user_id; + prefitem_t *prefs=uid->prefs; + char *user=utf8_to_native(uid->name,strlen(uid->name),0); + + for(;prefs->type;prefs++) + { + char num[10]; /* prefs->value is a byte, so we're over + safe here */ + + sprintf(num,"%u",prefs->value); + + if(prefs->type==PREFTYPE_SYM) + { + if(check_cipher_algo(prefs->value)) + { + const char *algo=cipher_algo_to_string(prefs->value); + if(!problem) + check_prefs_warning(pk); + log_info(_(" \"%s\": preference for cipher" + " algorithm %s\n"),user,algo?algo:num); + problem=1; + } + } + else if(prefs->type==PREFTYPE_HASH) + { + if(check_digest_algo(prefs->value)) + { + const char *algo=digest_algo_to_string(prefs->value); + if(!problem) + check_prefs_warning(pk); + log_info(_(" \"%s\": preference for digest" + " algorithm %s\n"),user,algo?algo:num); + problem=1; + } + } + else if(prefs->type==PREFTYPE_ZIP) + { + if(check_compress_algo(prefs->value)) + { + const char *algo=compress_algo_to_string(prefs->value); + if(!problem) + check_prefs_warning(pk); + log_info(_(" \"%s\": preference for compression" + " algorithm %s\n"),user,algo?algo:num); + problem=1; + } + } + } + + xfree(user); + } + } + + if(problem) + { + log_info(_("it is strongly suggested that you update" + " your preferences and\n")); + log_info(_("re-distribute this key to avoid potential algorithm" + " mismatch problems\n")); + + if(!opt.batch) + { + STRLIST sl=NULL,locusr=NULL; + size_t fprlen=0; + byte fpr[MAX_FINGERPRINT_LEN],*p; + char username[(MAX_FINGERPRINT_LEN*2)+1]; + unsigned int i; + + p=fingerprint_from_pk(pk,fpr,&fprlen); + for(i=0;i<fprlen;i++,p++) + sprintf(username+2*i,"%02X",*p); + add_to_strlist(&locusr,username); + + append_to_strlist(&sl,"updpref"); + append_to_strlist(&sl,"save"); + + keyedit_menu( username, locusr, sl, 1, 1 ); + free_strlist(sl); + free_strlist(locusr); + } + else if(!opt.quiet) + log_info(_("you can update your preferences with:" + " gpg --edit-key %s updpref save\n"),keystr_from_pk(pk)); + } +} + +/**************** + * Try to import one keyblock. Return an error only in serious cases, but + * never for an invalid keyblock. It uses log_error to increase the + * internal errorcount, so that invalid input can be detected by programs + * which called g10. + */ +static int +import_one( const char *fname, KBNODE keyblock, struct stats_s *stats, + unsigned char **fpr,size_t *fpr_len,unsigned int options, + int from_sk ) +{ + PKT_public_key *pk; + PKT_public_key *pk_orig; + KBNODE node, uidnode; + KBNODE keyblock_orig = NULL; + u32 keyid[2]; + int rc = 0; + int new_key = 0; + int mod_key = 0; + int non_self = 0; + + /* get the key and print some info about it */ + node = find_kbnode( keyblock, PKT_PUBLIC_KEY ); + if( !node ) + BUG(); + + pk = node->pkt->pkt.public_key; + + keyid_from_pk( pk, keyid ); + uidnode = find_next_kbnode( keyblock, PKT_USER_ID ); + + if( opt.verbose && !opt.interactive ) + { + log_info( "pub %4u%c/%s %s ", + nbits_from_pk( pk ), + pubkey_letter( pk->pubkey_algo ), + keystr_from_pk(pk), datestr_from_pk(pk) ); + if( uidnode ) + print_utf8_string( stderr, uidnode->pkt->pkt.user_id->name, + uidnode->pkt->pkt.user_id->len ); + putc('\n', stderr); + } + + if( !uidnode ) + { + log_error( _("key %s: no user ID\n"), keystr_from_pk(pk)); + return 0; + } + + if (opt.interactive) { + if(is_status_enabled()) + print_import_check (pk, uidnode->pkt->pkt.user_id); + merge_keys_and_selfsig (keyblock); + tty_printf ("\n"); + show_basic_key_info (keyblock); + tty_printf ("\n"); + if (!cpr_get_answer_is_yes ("import.okay", + "Do you want to import this key? (y/N) ")) + return 0; + } + + collapse_uids(&keyblock); + + /* Clean the key that we're about to import, to cut down on things + that we have to clean later. This has no practical impact on + the end result, but does result in less logging which might + confuse the user. */ + if(options&IMPORT_CLEAN) + clean_key(keyblock,opt.verbose,options&IMPORT_MINIMAL,NULL,NULL); + + clear_kbnode_flags( keyblock ); + + if((options&IMPORT_REPAIR_PKS_SUBKEY_BUG) && fix_pks_corruption(keyblock) + && opt.verbose) + log_info(_("key %s: PKS subkey corruption repaired\n"), + keystr_from_pk(pk)); + + rc = chk_self_sigs( fname, keyblock , pk, keyid, &non_self ); + if( rc ) + return rc== -1? 0:rc; + + /* If we allow such a thing, mark unsigned uids as valid */ + if( opt.allow_non_selfsigned_uid ) + for( node=keyblock; node; node = node->next ) + if( node->pkt->pkttype == PKT_USER_ID && !(node->flag & 1) ) + { + char *user=utf8_to_native(node->pkt->pkt.user_id->name, + node->pkt->pkt.user_id->len,0); + node->flag |= 1; + log_info( _("key %s: accepted non self-signed user ID \"%s\"\n"), + keystr_from_pk(pk),user); + xfree(user); + } + + if( !delete_inv_parts( fname, keyblock, keyid, options ) ) { + log_error( _("key %s: no valid user IDs\n"), keystr_from_pk(pk)); + if( !opt.quiet ) + log_info(_("this may be caused by a missing self-signature\n")); + stats->no_user_id++; + return 0; + } + + /* do we have this key already in one of our pubrings ? */ + pk_orig = xmalloc_clear( sizeof *pk_orig ); + rc = get_pubkey_fast ( pk_orig, keyid ); + if( rc && rc != G10ERR_NO_PUBKEY && rc != G10ERR_UNU_PUBKEY ) + { + log_error( _("key %s: public key not found: %s\n"), + keystr(keyid), g10_errstr(rc)); + } + else if ( rc && (opt.import_options&IMPORT_MERGE_ONLY) ) + { + if( opt.verbose ) + log_info( _("key %s: new key - skipped\n"), keystr(keyid)); + rc = 0; + stats->skipped_new_keys++; + } + else if( rc ) { /* insert this key */ + KEYDB_HANDLE hd = keydb_new (0); + + rc = keydb_locate_writable (hd, NULL); + if (rc) { + log_error (_("no writable keyring found: %s\n"), g10_errstr (rc)); + keydb_release (hd); + return G10ERR_GENERAL; + } + if( opt.verbose > 1 ) + log_info (_("writing to `%s'\n"), keydb_get_resource_name (hd) ); + + rc = keydb_insert_keyblock (hd, keyblock ); + if (rc) + log_error (_("error writing keyring `%s': %s\n"), + keydb_get_resource_name (hd), g10_errstr(rc)); + else + { + /* This should not be possible since we delete the + ownertrust when a key is deleted, but it can happen if + the keyring and trustdb are out of sync. It can also + be made to happen with the trusted-key command. */ + + clear_ownertrusts (pk); + if(non_self) + revalidation_mark (); + } + keydb_release (hd); + + /* we are ready */ + if( !opt.quiet ) + { + char *p=get_user_id_native (keyid); + log_info( _("key %s: public key \"%s\" imported\n"), + keystr(keyid),p); + xfree(p); + } + if( is_status_enabled() ) + { + char *us = get_long_user_id_string( keyid ); + write_status_text( STATUS_IMPORTED, us ); + xfree(us); + print_import_ok (pk,NULL, 1); + } + stats->imported++; + if( is_RSA( pk->pubkey_algo ) ) + stats->imported_rsa++; + new_key = 1; + } + else { /* merge */ + KEYDB_HANDLE hd; + int n_uids, n_sigs, n_subk, n_sigs_cleaned, n_uids_cleaned; + + /* Compare the original against the new key; just to be sure nothing + * weird is going on */ + if( cmp_public_keys( pk_orig, pk ) ) + { + log_error( _("key %s: doesn't match our copy\n"),keystr(keyid)); + goto leave; + } + + /* now read the original keyblock */ + hd = keydb_new (0); + { + byte afp[MAX_FINGERPRINT_LEN]; + size_t an; + + fingerprint_from_pk (pk_orig, afp, &an); + while (an < MAX_FINGERPRINT_LEN) + afp[an++] = 0; + rc = keydb_search_fpr (hd, afp); + } + if( rc ) + { + log_error (_("key %s: can't locate original keyblock: %s\n"), + keystr(keyid), g10_errstr(rc)); + keydb_release (hd); + goto leave; + } + rc = keydb_get_keyblock (hd, &keyblock_orig ); + if (rc) + { + log_error (_("key %s: can't read original keyblock: %s\n"), + keystr(keyid), g10_errstr(rc)); + keydb_release (hd); + goto leave; + } + + /* and try to merge the block */ + clear_kbnode_flags( keyblock_orig ); + clear_kbnode_flags( keyblock ); + n_uids = n_sigs = n_subk = n_sigs_cleaned = n_uids_cleaned = 0; + rc = merge_blocks( fname, keyblock_orig, keyblock, + keyid, &n_uids, &n_sigs, &n_subk ); + if( rc ) + { + keydb_release (hd); + goto leave; + } + + if(options&IMPORT_CLEAN) + clean_key(keyblock_orig,opt.verbose,options&IMPORT_MINIMAL, + &n_uids_cleaned,&n_sigs_cleaned); + + if( n_uids || n_sigs || n_subk || n_sigs_cleaned || n_uids_cleaned) { + mod_key = 1; + /* keyblock_orig has been updated; write */ + rc = keydb_update_keyblock (hd, keyblock_orig); + if (rc) + log_error (_("error writing keyring `%s': %s\n"), + keydb_get_resource_name (hd), g10_errstr(rc) ); + else if(non_self) + revalidation_mark (); + + /* we are ready */ + if( !opt.quiet ) + { + char *p=get_user_id_native(keyid); + if( n_uids == 1 ) + log_info( _("key %s: \"%s\" 1 new user ID\n"), + keystr(keyid),p); + else if( n_uids ) + log_info( _("key %s: \"%s\" %d new user IDs\n"), + keystr(keyid),p,n_uids); + if( n_sigs == 1 ) + log_info( _("key %s: \"%s\" 1 new signature\n"), + keystr(keyid), p); + else if( n_sigs ) + log_info( _("key %s: \"%s\" %d new signatures\n"), + keystr(keyid), p, n_sigs ); + if( n_subk == 1 ) + log_info( _("key %s: \"%s\" 1 new subkey\n"), + keystr(keyid), p); + else if( n_subk ) + log_info( _("key %s: \"%s\" %d new subkeys\n"), + keystr(keyid), p, n_subk ); + if(n_sigs_cleaned==1) + log_info(_("key %s: \"%s\" %d signature cleaned\n"), + keystr(keyid),p,n_sigs_cleaned); + else if(n_sigs_cleaned) + log_info(_("key %s: \"%s\" %d signatures cleaned\n"), + keystr(keyid),p,n_sigs_cleaned); + if(n_uids_cleaned==1) + log_info(_("key %s: \"%s\" %d user ID cleaned\n"), + keystr(keyid),p,n_uids_cleaned); + else if(n_uids_cleaned) + log_info(_("key %s: \"%s\" %d user IDs cleaned\n"), + keystr(keyid),p,n_uids_cleaned); + xfree(p); + } + + stats->n_uids +=n_uids; + stats->n_sigs +=n_sigs; + stats->n_subk +=n_subk; + stats->n_sigs_cleaned +=n_sigs_cleaned; + stats->n_uids_cleaned +=n_uids_cleaned; + + if (is_status_enabled ()) + print_import_ok (pk, NULL, + ((n_uids?2:0)|(n_sigs?4:0)|(n_subk?8:0))); + } + else + { + if (is_status_enabled ()) + print_import_ok (pk, NULL, 0); + + if( !opt.quiet ) + { + char *p=get_user_id_native(keyid); + log_info( _("key %s: \"%s\" not changed\n"),keystr(keyid),p); + xfree(p); + } + + stats->unchanged++; + } + + keydb_release (hd); hd = NULL; + } + + leave: + + /* Now that the key is definitely incorporated into the keydb, we + need to check if a designated revocation is present or if the + prefs are not rational so we can warn the user. */ + + if(mod_key) + { + revocation_present(keyblock_orig); + if(!from_sk && seckey_available(keyid)==0) + check_prefs(keyblock_orig); + } + else if(new_key) + { + /* A little explanation for this: we fill in the fingerprint + when importing keys as it can be useful to know the + fingerprint in certain keyserver-related cases (a keyserver + asked for a particular name, but the key doesn't have that + name). However, in cases where we're importing more than + one key at a time, we cannot know which key to fingerprint. + In these cases, rather than guessing, we do not fingerpring + at all, and we must hope the user ID on the keys are + useful. */ + if(fpr) + { + xfree(*fpr); + if(stats->imported==1) + *fpr=fingerprint_from_pk(pk,NULL,fpr_len); + else + *fpr=NULL; + } + + revocation_present(keyblock); + if(!from_sk && seckey_available(keyid)==0) + check_prefs(keyblock); + } + + release_kbnode( keyblock_orig ); + free_public_key( pk_orig ); + + return rc; +} + +/* Walk a secret keyblock and produce a public keyblock out of it. */ +static KBNODE +sec_to_pub_keyblock(KBNODE sec_keyblock) +{ + KBNODE secnode,pub_keyblock=NULL,ctx=NULL; + + while((secnode=walk_kbnode(sec_keyblock,&ctx,0))) + { + KBNODE pubnode; + + if(secnode->pkt->pkttype==PKT_SECRET_KEY || + secnode->pkt->pkttype==PKT_SECRET_SUBKEY) + { + /* Make a public key. We only need to convert enough to + write the keyblock out. */ + + PKT_secret_key *sk=secnode->pkt->pkt.secret_key; + PACKET *pkt=xmalloc_clear(sizeof(PACKET)); + PKT_public_key *pk=xmalloc_clear(sizeof(PKT_public_key)); + int n; + + if(secnode->pkt->pkttype==PKT_SECRET_KEY) + pkt->pkttype=PKT_PUBLIC_KEY; + else + pkt->pkttype=PKT_PUBLIC_SUBKEY; + + pkt->pkt.public_key=pk; + + pk->version=sk->version; + pk->timestamp=sk->timestamp; + pk->expiredate=sk->expiredate; + pk->pubkey_algo=sk->pubkey_algo; + + n=pubkey_get_npkey(pk->pubkey_algo); + if(n==0) + { + /* we can't properly extract the pubkey without knowing + the number of MPIs */ + release_kbnode(pub_keyblock); + return NULL; + } + else + { + int i; + + for(i=0;i<n;i++) + pk->pkey[i]=mpi_copy(sk->skey[i]); + } + + pubnode=new_kbnode(pkt); + } + else + { + pubnode=clone_kbnode(secnode); + } + + if(pub_keyblock==NULL) + pub_keyblock=pubnode; + else + add_kbnode(pub_keyblock,pubnode); + } + + return pub_keyblock; +} + +/**************** + * Ditto for secret keys. Handling is simpler than for public keys. + * We allow secret key importing only when allow is true, this is so + * that a secret key can not be imported accidently and thereby tampering + * with the trust calculation. + */ +static int +import_secret_one( const char *fname, KBNODE keyblock, + struct stats_s *stats, unsigned int options) +{ + PKT_secret_key *sk; + KBNODE node, uidnode; + u32 keyid[2]; + int rc = 0; + + /* get the key and print some info about it */ + node = find_kbnode( keyblock, PKT_SECRET_KEY ); + if( !node ) + BUG(); + + sk = node->pkt->pkt.secret_key; + keyid_from_sk( sk, keyid ); + uidnode = find_next_kbnode( keyblock, PKT_USER_ID ); + + if( opt.verbose ) + { + log_info( "sec %4u%c/%s %s ", + nbits_from_sk( sk ), + pubkey_letter( sk->pubkey_algo ), + keystr_from_sk(sk), datestr_from_sk(sk) ); + if( uidnode ) + print_utf8_string( stderr, uidnode->pkt->pkt.user_id->name, + uidnode->pkt->pkt.user_id->len ); + putc('\n', stderr); + } + stats->secret_read++; + + if( !uidnode ) + { + log_error( _("key %s: no user ID\n"), keystr_from_sk(sk)); + return 0; + } + + if(sk->protect.algo>110) + { + log_error(_("key %s: secret key with invalid cipher %d" + " - skipped\n"),keystr_from_sk(sk),sk->protect.algo); + return 0; + } + +#ifdef ENABLE_SELINUX_HACKS + if (1) + { + /* We don't allow to import secret keys because that may be used + to put a secret key into the keyring and the user might later + be tricked into signing stuff with that key. */ + log_error (_("importing secret keys not allowed\n")); + return 0; + } +#endif + + clear_kbnode_flags( keyblock ); + + /* do we have this key already in one of our secrings ? */ + rc = seckey_available( keyid ); + if( rc == G10ERR_NO_SECKEY && !(opt.import_options&IMPORT_MERGE_ONLY) ) + { + /* simply insert this key */ + KEYDB_HANDLE hd = keydb_new (1); + + /* get default resource */ + rc = keydb_locate_writable (hd, NULL); + if (rc) { + log_error (_("no default secret keyring: %s\n"), g10_errstr (rc)); + keydb_release (hd); + return G10ERR_GENERAL; + } + rc = keydb_insert_keyblock (hd, keyblock ); + if (rc) + log_error (_("error writing keyring `%s': %s\n"), + keydb_get_resource_name (hd), g10_errstr(rc) ); + keydb_release (hd); + /* we are ready */ + if( !opt.quiet ) + log_info( _("key %s: secret key imported\n"), keystr_from_sk(sk)); + stats->secret_imported++; + if (is_status_enabled ()) + print_import_ok (NULL, sk, 1|16); + + if(options&IMPORT_SK2PK) + { + /* Try and make a public key out of this. */ + + KBNODE pub_keyblock=sec_to_pub_keyblock(keyblock); + if(pub_keyblock) + { + import_one(fname,pub_keyblock,stats, + NULL,NULL,opt.import_options,1); + release_kbnode(pub_keyblock); + } + } + + /* Now that the key is definitely incorporated into the keydb, + if we have the public part of this key, we need to check if + the prefs are rational. */ + node=get_pubkeyblock(keyid); + if(node) + { + check_prefs(node); + release_kbnode(node); + } + } + else if( !rc ) + { /* we can't merge secret keys */ + log_error( _("key %s: already in secret keyring\n"), + keystr_from_sk(sk)); + stats->secret_dups++; + if (is_status_enabled ()) + print_import_ok (NULL, sk, 16); + + /* TODO: if we ever do merge secret keys, make sure to handle + the sec_to_pub_keyblock feature as well. */ + } + else + log_error( _("key %s: secret key not found: %s\n"), + keystr_from_sk(sk), g10_errstr(rc)); + + return rc; +} + + +/**************** + * Import a revocation certificate; this is a single signature packet. + */ +static int +import_revoke_cert( const char *fname, KBNODE node, struct stats_s *stats ) +{ + PKT_public_key *pk=NULL; + KBNODE onode, keyblock = NULL; + KEYDB_HANDLE hd = NULL; + u32 keyid[2]; + int rc = 0; + + assert( !node->next ); + assert( node->pkt->pkttype == PKT_SIGNATURE ); + assert( node->pkt->pkt.signature->sig_class == 0x20 ); + + keyid[0] = node->pkt->pkt.signature->keyid[0]; + keyid[1] = node->pkt->pkt.signature->keyid[1]; + + pk = xmalloc_clear( sizeof *pk ); + rc = get_pubkey( pk, keyid ); + if( rc == G10ERR_NO_PUBKEY ) + { + log_error(_("key %s: no public key -" + " can't apply revocation certificate\n"), keystr(keyid)); + rc = 0; + goto leave; + } + else if( rc ) + { + log_error(_("key %s: public key not found: %s\n"), + keystr(keyid), g10_errstr(rc)); + goto leave; + } + + /* read the original keyblock */ + hd = keydb_new (0); + { + byte afp[MAX_FINGERPRINT_LEN]; + size_t an; + + fingerprint_from_pk (pk, afp, &an); + while (an < MAX_FINGERPRINT_LEN) + afp[an++] = 0; + rc = keydb_search_fpr (hd, afp); + } + if (rc) + { + log_error (_("key %s: can't locate original keyblock: %s\n"), + keystr(keyid), g10_errstr(rc)); + goto leave; + } + rc = keydb_get_keyblock (hd, &keyblock ); + if (rc) + { + log_error (_("key %s: can't read original keyblock: %s\n"), + keystr(keyid), g10_errstr(rc)); + goto leave; + } + + /* it is okay, that node is not in keyblock because + * check_key_signature works fine for sig_class 0x20 in this + * special case. */ + rc = check_key_signature( keyblock, node, NULL); + if( rc ) + { + log_error( _("key %s: invalid revocation certificate" + ": %s - rejected\n"), keystr(keyid), g10_errstr(rc)); + goto leave; + } + + /* check whether we already have this */ + for(onode=keyblock->next; onode; onode=onode->next ) { + if( onode->pkt->pkttype == PKT_USER_ID ) + break; + else if( onode->pkt->pkttype == PKT_SIGNATURE + && !cmp_signatures(node->pkt->pkt.signature, + onode->pkt->pkt.signature)) + { + rc = 0; + goto leave; /* yes, we already know about it */ + } + } + + + /* insert it */ + insert_kbnode( keyblock, clone_kbnode(node), 0 ); + + /* and write the keyblock back */ + rc = keydb_update_keyblock (hd, keyblock ); + if (rc) + log_error (_("error writing keyring `%s': %s\n"), + keydb_get_resource_name (hd), g10_errstr(rc) ); + keydb_release (hd); hd = NULL; + /* we are ready */ + if( !opt.quiet ) + { + char *p=get_user_id_native (keyid); + log_info( _("key %s: \"%s\" revocation certificate imported\n"), + keystr(keyid),p); + xfree(p); + } + stats->n_revoc++; + + /* If the key we just revoked was ultimately trusted, remove its + ultimate trust. This doesn't stop the user from putting the + ultimate trust back, but is a reasonable solution for now. */ + if(get_ownertrust(pk)==TRUST_ULTIMATE) + clear_ownertrusts(pk); + + revalidation_mark (); + + leave: + keydb_release (hd); + release_kbnode( keyblock ); + free_public_key( pk ); + return rc; +} + + +/**************** + * loop over the keyblock and check all self signatures. + * Mark all user-ids with a self-signature by setting flag bit 0. + * Mark all user-ids with an invalid self-signature by setting bit 1. + * This works also for subkeys, here the subkey is marked. Invalid or + * extra subkey sigs (binding or revocation) are marked for deletion. + * non_self is set to true if there are any sigs other than self-sigs + * in this keyblock. + */ +static int +chk_self_sigs( const char *fname, KBNODE keyblock, + PKT_public_key *pk, u32 *keyid, int *non_self ) +{ + KBNODE n,knode=NULL; + PKT_signature *sig; + int rc; + u32 bsdate=0,rsdate=0; + KBNODE bsnode=NULL,rsnode=NULL; + + for( n=keyblock; (n = find_next_kbnode(n, 0)); ) { + if(n->pkt->pkttype==PKT_PUBLIC_SUBKEY) + { + knode=n; + bsdate=0; + rsdate=0; + bsnode=NULL; + rsnode=NULL; + continue; + } + else if( n->pkt->pkttype != PKT_SIGNATURE ) + continue; + sig = n->pkt->pkt.signature; + if( keyid[0] == sig->keyid[0] && keyid[1] == sig->keyid[1] ) { + + /* This just caches the sigs for later use. That way we + import a fully-cached key which speeds things up. */ + if(!opt.no_sig_cache) + check_key_signature(keyblock,n,NULL); + + if( IS_UID_SIG(sig) || IS_UID_REV(sig) ) + { + KBNODE unode = find_prev_kbnode( keyblock, n, PKT_USER_ID ); + if( !unode ) + { + log_error( _("key %s: no user ID for signature\n"), + keystr(keyid)); + return -1; /* the complete keyblock is invalid */ + } + + /* If it hasn't been marked valid yet, keep trying */ + if(!(unode->flag&1)) { + rc = check_key_signature( keyblock, n, NULL); + if( rc ) + { + if( opt.verbose ) + { + char *p=utf8_to_native(unode->pkt->pkt.user_id->name, + strlen(unode->pkt->pkt.user_id->name),0); + log_info( rc == G10ERR_PUBKEY_ALGO ? + _("key %s: unsupported public key " + "algorithm on user ID \"%s\"\n"): + _("key %s: invalid self-signature " + "on user ID \"%s\"\n"), + keystr(keyid),p); + xfree(p); + } + } + else + unode->flag |= 1; /* mark that signature checked */ + } + } + else if( sig->sig_class == 0x18 ) { + /* Note that this works based solely on the timestamps + like the rest of gpg. If the standard gets + revocation targets, this may need to be revised. */ + + if( !knode ) + { + if(opt.verbose) + log_info( _("key %s: no subkey for key binding\n"), + keystr(keyid)); + n->flag |= 4; /* delete this */ + } + else + { + rc = check_key_signature( keyblock, n, NULL); + if( rc ) + { + if(opt.verbose) + log_info(rc == G10ERR_PUBKEY_ALGO ? + _("key %s: unsupported public key" + " algorithm\n"): + _("key %s: invalid subkey binding\n"), + keystr(keyid)); + n->flag|=4; + } + else + { + /* It's valid, so is it newer? */ + if(sig->timestamp>=bsdate) { + knode->flag |= 1; /* the subkey is valid */ + if(bsnode) + { + bsnode->flag|=4; /* Delete the last binding + sig since this one is + newer */ + if(opt.verbose) + log_info(_("key %s: removed multiple subkey" + " binding\n"),keystr(keyid)); + } + + bsnode=n; + bsdate=sig->timestamp; + } + else + n->flag|=4; /* older */ + } + } + } + else if( sig->sig_class == 0x28 ) { + /* We don't actually mark the subkey as revoked right + now, so just check that the revocation sig is the + most recent valid one. Note that we don't care if + the binding sig is newer than the revocation sig. + See the comment in getkey.c:merge_selfsigs_subkey for + more */ + if( !knode ) + { + if(opt.verbose) + log_info( _("key %s: no subkey for key revocation\n"), + keystr(keyid)); + n->flag |= 4; /* delete this */ + } + else + { + rc = check_key_signature( keyblock, n, NULL); + if( rc ) + { + if(opt.verbose) + log_info(rc == G10ERR_PUBKEY_ALGO ? + _("key %s: unsupported public" + " key algorithm\n"): + _("key %s: invalid subkey revocation\n"), + keystr(keyid)); + n->flag|=4; + } + else + { + /* It's valid, so is it newer? */ + if(sig->timestamp>=rsdate) + { + if(rsnode) + { + rsnode->flag|=4; /* Delete the last revocation + sig since this one is + newer */ + if(opt.verbose) + log_info(_("key %s: removed multiple subkey" + " revocation\n"),keystr(keyid)); + } + + rsnode=n; + rsdate=sig->timestamp; + } + else + n->flag|=4; /* older */ + } + } + } + } + else + *non_self=1; + } + + return 0; +} + +/**************** + * delete all parts which are invalid and those signatures whose + * public key algorithm is not available in this implemenation; + * but consider RSA as valid, because parse/build_packets knows + * about it. + * returns: true if at least one valid user-id is left over. + */ +static int +delete_inv_parts( const char *fname, KBNODE keyblock, + u32 *keyid, unsigned int options) +{ + KBNODE node; + int nvalid=0, uid_seen=0, subkey_seen=0; + + for(node=keyblock->next; node; node = node->next ) { + if( node->pkt->pkttype == PKT_USER_ID ) { + uid_seen = 1; + if( (node->flag & 2) || !(node->flag & 1) ) { + if( opt.verbose ) + { + char *p=utf8_to_native(node->pkt->pkt.user_id->name, + node->pkt->pkt.user_id->len,0); + log_info( _("key %s: skipped user ID \"%s\"\n"), + keystr(keyid),p); + xfree(p); + } + delete_kbnode( node ); /* the user-id */ + /* and all following packets up to the next user-id */ + while( node->next + && node->next->pkt->pkttype != PKT_USER_ID + && node->next->pkt->pkttype != PKT_PUBLIC_SUBKEY + && node->next->pkt->pkttype != PKT_SECRET_SUBKEY ){ + delete_kbnode( node->next ); + node = node->next; + } + } + else + nvalid++; + } + else if( node->pkt->pkttype == PKT_PUBLIC_SUBKEY + || node->pkt->pkttype == PKT_SECRET_SUBKEY ) { + if( (node->flag & 2) || !(node->flag & 1) ) { + if( opt.verbose ) + log_info( _("key %s: skipped subkey\n"),keystr(keyid)); + + delete_kbnode( node ); /* the subkey */ + /* and all following signature packets */ + while( node->next + && node->next->pkt->pkttype == PKT_SIGNATURE ) { + delete_kbnode( node->next ); + node = node->next; + } + } + else + subkey_seen = 1; + } + else if( node->pkt->pkttype == PKT_SIGNATURE + && check_pubkey_algo( node->pkt->pkt.signature->pubkey_algo) + && node->pkt->pkt.signature->pubkey_algo != PUBKEY_ALGO_RSA ) + delete_kbnode( node ); /* build_packet() can't handle this */ + else if( node->pkt->pkttype == PKT_SIGNATURE && + !node->pkt->pkt.signature->flags.exportable && + !(options&IMPORT_LOCAL_SIGS) && + seckey_available( node->pkt->pkt.signature->keyid ) ) + { + /* here we violate the rfc a bit by still allowing + * to import non-exportable signature when we have the + * the secret key used to create this signature - it + * seems that this makes sense */ + if(opt.verbose) + log_info( _("key %s: non exportable signature" + " (class 0x%02X) - skipped\n"), + keystr(keyid), node->pkt->pkt.signature->sig_class ); + delete_kbnode( node ); + } + else if( node->pkt->pkttype == PKT_SIGNATURE + && node->pkt->pkt.signature->sig_class == 0x20 ) { + if( uid_seen ) + { + if(opt.verbose) + log_info( _("key %s: revocation certificate" + " at wrong place - skipped\n"),keystr(keyid)); + delete_kbnode( node ); + } + else { + /* If the revocation cert is from a different key than + the one we're working on don't check it - it's + probably from a revocation key and won't be + verifiable with this key anyway. */ + + if(node->pkt->pkt.signature->keyid[0]==keyid[0] && + node->pkt->pkt.signature->keyid[1]==keyid[1]) + { + int rc = check_key_signature( keyblock, node, NULL); + if( rc ) + { + if(opt.verbose) + log_info( _("key %s: invalid revocation" + " certificate: %s - skipped\n"), + keystr(keyid), g10_errstr(rc)); + delete_kbnode( node ); + } + } + } + } + else if( node->pkt->pkttype == PKT_SIGNATURE && + (node->pkt->pkt.signature->sig_class == 0x18 || + node->pkt->pkt.signature->sig_class == 0x28) && + !subkey_seen ) + { + if(opt.verbose) + log_info( _("key %s: subkey signature" + " in wrong place - skipped\n"), keystr(keyid)); + delete_kbnode( node ); + } + else if( node->pkt->pkttype == PKT_SIGNATURE + && !IS_CERT(node->pkt->pkt.signature)) + { + if(opt.verbose) + log_info(_("key %s: unexpected signature class (0x%02X) -" + " skipped\n"),keystr(keyid), + node->pkt->pkt.signature->sig_class); + delete_kbnode(node); + } + else if( (node->flag & 4) ) /* marked for deletion */ + delete_kbnode( node ); + } + + /* note: because keyblock is the public key, it is never marked + * for deletion and so keyblock cannot change */ + commit_kbnode( &keyblock ); + return nvalid; +} + + +/**************** + * It may happen that the imported keyblock has duplicated user IDs. + * We check this here and collapse those user IDs together with their + * sigs into one. + * Returns: True if the keyblock hash changed. + */ +int +collapse_uids( KBNODE *keyblock ) +{ + KBNODE n, n2; + int in_uid; + int any=0; + + restart: + for( n = *keyblock; n; n = n->next ) { + if( n->pkt->pkttype != PKT_USER_ID ) + continue; + for( n2 = n->next; n2; n2 = n2->next ) { + if( n2->pkt->pkttype == PKT_USER_ID + && !cmp_user_ids( n->pkt->pkt.user_id, + n2->pkt->pkt.user_id ) ) { + /* found a duplicate */ + any = 1; + if( !n2->next + || n2->next->pkt->pkttype == PKT_USER_ID + || n2->next->pkt->pkttype == PKT_PUBLIC_SUBKEY + || n2->next->pkt->pkttype == PKT_SECRET_SUBKEY ) { + /* no more signatures: delete the user ID + * and start over */ + remove_kbnode( keyblock, n2 ); + } + else { + /* The simple approach: Move one signature and + * then start over to delete the next one :-( */ + move_kbnode( keyblock, n2->next, n->next ); + } + goto restart; + } + } + } + if( !any ) + return 0; + + restart_sig: + /* now we may have duplicate signatures on one user ID: fix this */ + for( in_uid = 0, n = *keyblock; n; n = n->next ) { + if( n->pkt->pkttype == PKT_USER_ID ) + in_uid = 1; + else if( n->pkt->pkttype == PKT_PUBLIC_SUBKEY + || n->pkt->pkttype == PKT_SECRET_SUBKEY ) + in_uid = 0; + else if( in_uid ) { + n2 = n; + do { + KBNODE ncmp = NULL; + for( ; n2; n2 = n2->next ) { + if( n2->pkt->pkttype == PKT_USER_ID + || n2->pkt->pkttype == PKT_PUBLIC_SUBKEY + || n2->pkt->pkttype == PKT_SECRET_SUBKEY ) + break; + if( n2->pkt->pkttype != PKT_SIGNATURE ) + ; + else if( !ncmp ) + ncmp = n2; + else if( !cmp_signatures( ncmp->pkt->pkt.signature, + n2->pkt->pkt.signature )) { + remove_kbnode( keyblock, n2 ); + goto restart_sig; + } + } + n2 = ncmp? ncmp->next : NULL; + } while( n2 ); + } + } + + if(!opt.quiet) + { + const char *key="???"; + + if( (n = find_kbnode( *keyblock, PKT_PUBLIC_KEY )) ) + key=keystr_from_pk(n->pkt->pkt.public_key); + else if( (n = find_kbnode( *keyblock, PKT_SECRET_KEY )) ) + key=keystr_from_sk(n->pkt->pkt.secret_key); + + log_info(_("key %s: duplicated user ID detected - merged\n"),key); + } + + return 1; +} + +/* Check for a 0x20 revocation from a revocation key that is not + present. This may be called without the benefit of merge_xxxx so + you can't rely on pk->revkey and friends. */ +static void +revocation_present(KBNODE keyblock) +{ + KBNODE onode,inode; + PKT_public_key *pk=keyblock->pkt->pkt.public_key; + + for(onode=keyblock->next;onode;onode=onode->next) + { + /* If we reach user IDs, we're done. */ + if(onode->pkt->pkttype==PKT_USER_ID) + break; + + if(onode->pkt->pkttype==PKT_SIGNATURE && + onode->pkt->pkt.signature->sig_class==0x1F && + onode->pkt->pkt.signature->revkey) + { + int idx; + PKT_signature *sig=onode->pkt->pkt.signature; + + for(idx=0;idx<sig->numrevkeys;idx++) + { + u32 keyid[2]; + + keyid_from_fingerprint(sig->revkey[idx]->fpr, + MAX_FINGERPRINT_LEN,keyid); + + for(inode=keyblock->next;inode;inode=inode->next) + { + /* If we reach user IDs, we're done. */ + if(inode->pkt->pkttype==PKT_USER_ID) + break; + + if(inode->pkt->pkttype==PKT_SIGNATURE && + inode->pkt->pkt.signature->sig_class==0x20 && + inode->pkt->pkt.signature->keyid[0]==keyid[0] && + inode->pkt->pkt.signature->keyid[1]==keyid[1]) + { + /* Okay, we have a revocation key, and a + revocation issued by it. Do we have the key + itself? */ + int rc; + + rc=get_pubkey_byfprint_fast (NULL,sig->revkey[idx]->fpr, + MAX_FINGERPRINT_LEN); + if(rc==G10ERR_NO_PUBKEY || rc==G10ERR_UNU_PUBKEY) + { + char *tempkeystr=xstrdup(keystr_from_pk(pk)); + + /* No, so try and get it */ + if(opt.keyserver + && (opt.keyserver_options.options + & KEYSERVER_AUTO_KEY_RETRIEVE)) + { + log_info(_("WARNING: key %s may be revoked:" + " fetching revocation key %s\n"), + tempkeystr,keystr(keyid)); + keyserver_import_fprint(sig->revkey[idx]->fpr, + MAX_FINGERPRINT_LEN, + opt.keyserver); + + /* Do we have it now? */ + rc=get_pubkey_byfprint_fast (NULL, + sig->revkey[idx]->fpr, + MAX_FINGERPRINT_LEN); + } + + if(rc==G10ERR_NO_PUBKEY || rc==G10ERR_UNU_PUBKEY) + log_info(_("WARNING: key %s may be revoked:" + " revocation key %s not present.\n"), + tempkeystr,keystr(keyid)); + + xfree(tempkeystr); + } + } + } + } + } + } +} + +/**************** + * compare and merge the blocks + * + * o compare the signatures: If we already have this signature, check + * that they compare okay; if not, issue a warning and ask the user. + * o Simply add the signature. Can't verify here because we may not have + * the signature's public key yet; verification is done when putting it + * into the trustdb, which is done automagically as soon as this pubkey + * is used. + * Note: We indicate newly inserted packets with flag bit 0 + */ +static int +merge_blocks( const char *fname, KBNODE keyblock_orig, KBNODE keyblock, + u32 *keyid, int *n_uids, int *n_sigs, int *n_subk ) +{ + KBNODE onode, node; + int rc, found; + + /* 1st: handle revocation certificates */ + for(node=keyblock->next; node; node=node->next ) { + if( node->pkt->pkttype == PKT_USER_ID ) + break; + else if( node->pkt->pkttype == PKT_SIGNATURE + && node->pkt->pkt.signature->sig_class == 0x20 ) { + /* check whether we already have this */ + found = 0; + for(onode=keyblock_orig->next; onode; onode=onode->next ) { + if( onode->pkt->pkttype == PKT_USER_ID ) + break; + else if( onode->pkt->pkttype == PKT_SIGNATURE + && onode->pkt->pkt.signature->sig_class == 0x20 + && !cmp_signatures(onode->pkt->pkt.signature, + node->pkt->pkt.signature)) + { + found = 1; + break; + } + } + if( !found ) { + KBNODE n2 = clone_kbnode(node); + insert_kbnode( keyblock_orig, n2, 0 ); + n2->flag |= 1; + ++*n_sigs; + if(!opt.quiet) + { + char *p=get_user_id_native (keyid); + log_info(_("key %s: \"%s\" revocation" + " certificate added\n"), keystr(keyid),p); + xfree(p); + } + } + } + } + + /* 2nd: merge in any direct key (0x1F) sigs */ + for(node=keyblock->next; node; node=node->next ) { + if( node->pkt->pkttype == PKT_USER_ID ) + break; + else if( node->pkt->pkttype == PKT_SIGNATURE + && node->pkt->pkt.signature->sig_class == 0x1F ) { + /* check whether we already have this */ + found = 0; + for(onode=keyblock_orig->next; onode; onode=onode->next ) { + if( onode->pkt->pkttype == PKT_USER_ID ) + break; + else if( onode->pkt->pkttype == PKT_SIGNATURE + && onode->pkt->pkt.signature->sig_class == 0x1F + && !cmp_signatures(onode->pkt->pkt.signature, + node->pkt->pkt.signature)) { + found = 1; + break; + } + } + if( !found ) + { + KBNODE n2 = clone_kbnode(node); + insert_kbnode( keyblock_orig, n2, 0 ); + n2->flag |= 1; + ++*n_sigs; + if(!opt.quiet) + log_info( _("key %s: direct key signature added\n"), + keystr(keyid)); + } + } + } + + /* 3rd: try to merge new certificates in */ + for(onode=keyblock_orig->next; onode; onode=onode->next ) { + if( !(onode->flag & 1) && onode->pkt->pkttype == PKT_USER_ID) { + /* find the user id in the imported keyblock */ + for(node=keyblock->next; node; node=node->next ) + if( node->pkt->pkttype == PKT_USER_ID + && !cmp_user_ids( onode->pkt->pkt.user_id, + node->pkt->pkt.user_id ) ) + break; + if( node ) { /* found: merge */ + rc = merge_sigs( onode, node, n_sigs, fname, keyid ); + if( rc ) + return rc; + } + } + } + + /* 4th: add new user-ids */ + for(node=keyblock->next; node; node=node->next ) { + if( node->pkt->pkttype == PKT_USER_ID) { + /* do we have this in the original keyblock */ + for(onode=keyblock_orig->next; onode; onode=onode->next ) + if( onode->pkt->pkttype == PKT_USER_ID + && !cmp_user_ids( onode->pkt->pkt.user_id, + node->pkt->pkt.user_id ) ) + break; + if( !onode ) { /* this is a new user id: append */ + rc = append_uid( keyblock_orig, node, n_sigs, fname, keyid); + if( rc ) + return rc; + ++*n_uids; + } + } + } + + /* 5th: add new subkeys */ + for(node=keyblock->next; node; node=node->next ) { + onode = NULL; + if( node->pkt->pkttype == PKT_PUBLIC_SUBKEY ) { + /* do we have this in the original keyblock? */ + for(onode=keyblock_orig->next; onode; onode=onode->next ) + if( onode->pkt->pkttype == PKT_PUBLIC_SUBKEY + && !cmp_public_keys( onode->pkt->pkt.public_key, + node->pkt->pkt.public_key ) ) + break; + if( !onode ) { /* this is a new subkey: append */ + rc = append_key( keyblock_orig, node, n_sigs, fname, keyid); + if( rc ) + return rc; + ++*n_subk; + } + } + else if( node->pkt->pkttype == PKT_SECRET_SUBKEY ) { + /* do we have this in the original keyblock? */ + for(onode=keyblock_orig->next; onode; onode=onode->next ) + if( onode->pkt->pkttype == PKT_SECRET_SUBKEY + && !cmp_secret_keys( onode->pkt->pkt.secret_key, + node->pkt->pkt.secret_key ) ) + break; + if( !onode ) { /* this is a new subkey: append */ + rc = append_key( keyblock_orig, node, n_sigs, fname, keyid); + if( rc ) + return rc; + ++*n_subk; + } + } + } + + /* 6th: merge subkey certificates */ + for(onode=keyblock_orig->next; onode; onode=onode->next ) { + if( !(onode->flag & 1) + && ( onode->pkt->pkttype == PKT_PUBLIC_SUBKEY + || onode->pkt->pkttype == PKT_SECRET_SUBKEY) ) { + /* find the subkey in the imported keyblock */ + for(node=keyblock->next; node; node=node->next ) { + if( node->pkt->pkttype == PKT_PUBLIC_SUBKEY + && !cmp_public_keys( onode->pkt->pkt.public_key, + node->pkt->pkt.public_key ) ) + break; + else if( node->pkt->pkttype == PKT_SECRET_SUBKEY + && !cmp_secret_keys( onode->pkt->pkt.secret_key, + node->pkt->pkt.secret_key ) ) + break; + } + if( node ) { /* found: merge */ + rc = merge_keysigs( onode, node, n_sigs, fname, keyid ); + if( rc ) + return rc; + } + } + } + + + return 0; +} + + +/**************** + * append the userid starting with NODE and all signatures to KEYBLOCK. + */ +static int +append_uid( KBNODE keyblock, KBNODE node, int *n_sigs, + const char *fname, u32 *keyid ) +{ + KBNODE n, n_where=NULL; + + assert(node->pkt->pkttype == PKT_USER_ID ); + + /* find the position */ + for( n = keyblock; n; n_where = n, n = n->next ) { + if( n->pkt->pkttype == PKT_PUBLIC_SUBKEY + || n->pkt->pkttype == PKT_SECRET_SUBKEY ) + break; + } + if( !n ) + n_where = NULL; + + /* and append/insert */ + while( node ) { + /* we add a clone to the original keyblock, because this + * one is released first */ + n = clone_kbnode(node); + if( n_where ) { + insert_kbnode( n_where, n, 0 ); + n_where = n; + } + else + add_kbnode( keyblock, n ); + n->flag |= 1; + node->flag |= 1; + if( n->pkt->pkttype == PKT_SIGNATURE ) + ++*n_sigs; + + node = node->next; + if( node && node->pkt->pkttype != PKT_SIGNATURE ) + break; + } + + return 0; +} + + +/**************** + * Merge the sigs from SRC onto DST. SRC and DST are both a PKT_USER_ID. + * (how should we handle comment packets here?) + */ +static int +merge_sigs( KBNODE dst, KBNODE src, int *n_sigs, + const char *fname, u32 *keyid ) +{ + KBNODE n, n2; + int found=0; + + assert(dst->pkt->pkttype == PKT_USER_ID ); + assert(src->pkt->pkttype == PKT_USER_ID ); + + for(n=src->next; n && n->pkt->pkttype != PKT_USER_ID; n = n->next ) { + if( n->pkt->pkttype != PKT_SIGNATURE ) + continue; + if( n->pkt->pkt.signature->sig_class == 0x18 + || n->pkt->pkt.signature->sig_class == 0x28 ) + continue; /* skip signatures which are only valid on subkeys */ + found = 0; + for(n2=dst->next; n2 && n2->pkt->pkttype != PKT_USER_ID; n2 = n2->next) + if(!cmp_signatures(n->pkt->pkt.signature,n2->pkt->pkt.signature)) + { + found++; + break; + } + if( !found ) { + /* This signature is new or newer, append N to DST. + * We add a clone to the original keyblock, because this + * one is released first */ + n2 = clone_kbnode(n); + insert_kbnode( dst, n2, PKT_SIGNATURE ); + n2->flag |= 1; + n->flag |= 1; + ++*n_sigs; + } + } + + return 0; +} + +/**************** + * Merge the sigs from SRC onto DST. SRC and DST are both a PKT_xxx_SUBKEY. + */ +static int +merge_keysigs( KBNODE dst, KBNODE src, int *n_sigs, + const char *fname, u32 *keyid ) +{ + KBNODE n, n2; + int found=0; + + assert( dst->pkt->pkttype == PKT_PUBLIC_SUBKEY + || dst->pkt->pkttype == PKT_SECRET_SUBKEY ); + + for(n=src->next; n ; n = n->next ) { + if( n->pkt->pkttype == PKT_PUBLIC_SUBKEY + || n->pkt->pkttype == PKT_PUBLIC_KEY ) + break; + if( n->pkt->pkttype != PKT_SIGNATURE ) + continue; + found = 0; + for(n2=dst->next; n2; n2 = n2->next){ + if( n2->pkt->pkttype == PKT_PUBLIC_SUBKEY + || n2->pkt->pkttype == PKT_PUBLIC_KEY ) + break; + if( n2->pkt->pkttype == PKT_SIGNATURE + && n->pkt->pkt.signature->keyid[0] + == n2->pkt->pkt.signature->keyid[0] + && n->pkt->pkt.signature->keyid[1] + == n2->pkt->pkt.signature->keyid[1] + && n->pkt->pkt.signature->timestamp + <= n2->pkt->pkt.signature->timestamp + && n->pkt->pkt.signature->sig_class + == n2->pkt->pkt.signature->sig_class ) { + found++; + break; + } + } + if( !found ) { + /* This signature is new or newer, append N to DST. + * We add a clone to the original keyblock, because this + * one is released first */ + n2 = clone_kbnode(n); + insert_kbnode( dst, n2, PKT_SIGNATURE ); + n2->flag |= 1; + n->flag |= 1; + ++*n_sigs; + } + } + + return 0; +} + +/**************** + * append the subkey starting with NODE and all signatures to KEYBLOCK. + * Mark all new and copied packets by setting flag bit 0. + */ +static int +append_key( KBNODE keyblock, KBNODE node, int *n_sigs, + const char *fname, u32 *keyid ) +{ + KBNODE n; + + assert( node->pkt->pkttype == PKT_PUBLIC_SUBKEY + || node->pkt->pkttype == PKT_SECRET_SUBKEY ); + + while( node ) { + /* we add a clone to the original keyblock, because this + * one is released first */ + n = clone_kbnode(node); + add_kbnode( keyblock, n ); + n->flag |= 1; + node->flag |= 1; + if( n->pkt->pkttype == PKT_SIGNATURE ) + ++*n_sigs; + + node = node->next; + if( node && node->pkt->pkttype != PKT_SIGNATURE ) + break; + } + + return 0; +} + + + +/* Walk a public keyblock and produce a secret keyblock out of it. + Instead of inserting the secret key parameters (which we don't + have), we insert a stub. */ +static KBNODE +pub_to_sec_keyblock (KBNODE pub_keyblock) +{ + KBNODE pubnode, secnode; + KBNODE sec_keyblock = NULL; + KBNODE walkctx = NULL; + + while((pubnode = walk_kbnode (pub_keyblock,&walkctx,0))) + { + if (pubnode->pkt->pkttype == PKT_PUBLIC_KEY + || pubnode->pkt->pkttype == PKT_PUBLIC_SUBKEY) + { + /* Make a secret key. We only need to convert enough to + write the keyblock out. */ + PKT_public_key *pk = pubnode->pkt->pkt.public_key; + PACKET *pkt = xmalloc_clear (sizeof *pkt); + PKT_secret_key *sk = xmalloc_clear (sizeof *sk); + int i, n; + + if (pubnode->pkt->pkttype == PKT_PUBLIC_KEY) + pkt->pkttype = PKT_SECRET_KEY; + else + pkt->pkttype = PKT_SECRET_SUBKEY; + + pkt->pkt.secret_key = sk; + + copy_public_parts_to_secret_key ( pk, sk ); + sk->version = pk->version; + sk->timestamp = pk->timestamp; + + n = pubkey_get_npkey (pk->pubkey_algo); + if (!n) + n = 1; /* Unknown number of parameters, however the data + is stored in the first mpi. */ + for (i=0; i < n; i++ ) + sk->skey[i] = mpi_copy (pk->pkey[i]); + + sk->is_protected = 1; + sk->protect.s2k.mode = 1001; + + secnode = new_kbnode (pkt); + } + else + { + secnode = clone_kbnode (pubnode); + } + + if(!sec_keyblock) + sec_keyblock = secnode; + else + add_kbnode (sec_keyblock, secnode); + } + + return sec_keyblock; +} + + +/* Walk over the secret keyring SEC_KEYBLOCK and update any simple + stub keys with the serial number SNNUM of the card if one of the + fingerprints FPR1, FPR2 or FPR3 match. Print a note if the key is + a duplicate (may happen in case of backed uped keys). + + Returns: True if anything changed. +*/ +static int +update_sec_keyblock_with_cardinfo (KBNODE sec_keyblock, + const unsigned char *fpr1, + const unsigned char *fpr2, + const unsigned char *fpr3, + const char *serialnostr) +{ + KBNODE node; + KBNODE walkctx = NULL; + PKT_secret_key *sk; + byte array[MAX_FINGERPRINT_LEN]; + size_t n; + int result = 0; + const char *s; + + while((node = walk_kbnode (sec_keyblock, &walkctx, 0))) + { + if (node->pkt->pkttype != PKT_SECRET_KEY + && node->pkt->pkttype != PKT_SECRET_SUBKEY) + continue; + sk = node->pkt->pkt.secret_key; + + fingerprint_from_sk (sk, array, &n); + if (n != 20) + continue; /* Can't be a card key. */ + if ( !((fpr1 && !memcmp (array, fpr1, 20)) + || (fpr2 && !memcmp (array, fpr2, 20)) + || (fpr3 && !memcmp (array, fpr3, 20))) ) + continue; /* No match. */ + + if (sk->is_protected == 1 && sk->protect.s2k.mode == 1001) + { + /* Standard case: migrate that stub to a key stub. */ + sk->protect.s2k.mode = 1002; + s = serialnostr; + for (sk->protect.ivlen=0; sk->protect.ivlen < 16 && *s && s[1]; + sk->protect.ivlen++, s += 2) + sk->protect.iv[sk->protect.ivlen] = xtoi_2 (s); + result = 1; + } + else if (sk->is_protected == 1 && sk->protect.s2k.mode == 1002) + { + s = serialnostr; + for (sk->protect.ivlen=0; sk->protect.ivlen < 16 && *s && s[1]; + sk->protect.ivlen++, s += 2) + if (sk->protect.iv[sk->protect.ivlen] != xtoi_2 (s)) + { + log_info (_("NOTE: a key's S/N does not " + "match the card's one\n")); + break; + } + } + else + { + if (node->pkt->pkttype != PKT_SECRET_KEY) + log_info (_("NOTE: primary key is online and stored on card\n")); + else + log_info (_("NOTE: secondary key is online and stored on card\n")); + } + } + + return result; +} + + + +/* Check whether a secret key stub exists for the public key PK. If + not create such a stub key and store it into the secring. If it + exists, add appropriate subkey stubs and update the secring. + Return 0 if the key could be created. */ +int +auto_create_card_key_stub ( const char *serialnostr, + const unsigned char *fpr1, + const unsigned char *fpr2, + const unsigned char *fpr3) +{ + KBNODE pub_keyblock; + KBNODE sec_keyblock; + KEYDB_HANDLE hd; + int rc; + + /* We only want to do this for an OpenPGP card. */ + if (!serialnostr || strncmp (serialnostr, "D27600012401", 12) + || strlen (serialnostr) != 32 ) + return G10ERR_GENERAL; + + /* First get the public keyring from any of the provided fingerprints. */ + if ( (fpr1 && !get_keyblock_byfprint (&pub_keyblock, fpr1, 20)) + || (fpr2 && !get_keyblock_byfprint (&pub_keyblock, fpr2, 20)) + || (fpr3 && !get_keyblock_byfprint (&pub_keyblock, fpr3, 20))) + ; + else + return G10ERR_GENERAL; + + hd = keydb_new (1); + + /* Now check whether there is a secret keyring. */ + { + PKT_public_key *pk = pub_keyblock->pkt->pkt.public_key; + byte afp[MAX_FINGERPRINT_LEN]; + size_t an; + + fingerprint_from_pk (pk, afp, &an); + memset (afp, 0, MAX_FINGERPRINT_LEN); + rc = keydb_search_fpr (hd, afp); + } + + if (!rc) + { + rc = keydb_get_keyblock (hd, &sec_keyblock); + if (rc) + { + log_error (_("error reading keyblock: %s\n"), g10_errstr(rc) ); + rc = G10ERR_GENERAL; + } + else + { + merge_keys_and_selfsig (sec_keyblock); + + /* FIXME: We need to add new subkeys first. */ + if (update_sec_keyblock_with_cardinfo (sec_keyblock, + fpr1, fpr2, fpr3, + serialnostr)) + { + rc = keydb_update_keyblock (hd, sec_keyblock ); + if (rc) + log_error (_("error writing keyring `%s': %s\n"), + keydb_get_resource_name (hd), g10_errstr(rc) ); + } + } + } + else /* A secret key does not exists - create it. */ + { + sec_keyblock = pub_to_sec_keyblock (pub_keyblock); + update_sec_keyblock_with_cardinfo (sec_keyblock, + fpr1, fpr2, fpr3, + serialnostr); + + rc = keydb_locate_writable (hd, NULL); + if (rc) + { + log_error (_("no default secret keyring: %s\n"), g10_errstr (rc)); + rc = G10ERR_GENERAL; + } + else + { + rc = keydb_insert_keyblock (hd, sec_keyblock ); + if (rc) + log_error (_("error writing keyring `%s': %s\n"), + keydb_get_resource_name (hd), g10_errstr(rc) ); + } + } + + release_kbnode (sec_keyblock); + release_kbnode (pub_keyblock); + keydb_release (hd); + return rc; +} + diff --git a/g10/iso7816.c b/g10/iso7816.c new file mode 100644 index 0000000..35d0aa9 --- /dev/null +++ b/g10/iso7816.c @@ -0,0 +1,685 @@ +/* iso7816.c - ISO 7816 commands + * Copyright (C) 2003, 2004 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * $Id: iso7816.c 3886 2005-09-07 17:05:42Z wk $ + */ + +#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 "util.h" +#include "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 0x20 +#define CMD_CHANGE_REFERENCE_DATA 0x24 +#define CMD_RESET_RETRY_COUNTER 0x2C +#define CMD_GET_DATA 0xCA +#define CMD_PUT_DATA 0xDA +#define CMD_MSE 0x22 +#define CMD_PSO 0x2A +#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_WRONG_LENGTH: ec = GPG_ERR_INV_VALUE; break; + case SW_CHV_WRONG: ec = GPG_ERR_BAD_PIN; break; + case SW_CHV_BLOCKED: ec = GPG_ERR_PIN_BLOCKED; break; + case SW_USE_CONDITIONS: ec = GPG_ERR_USE_CONDITIONS; break; + case SW_NOT_SUPPORTED: ec = GPG_ERR_NOT_SUPPORTED; break; + case SW_BAD_PARAMETER: ec = GPG_ERR_INV_VALUE; break; + case SW_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_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_CANCELED; break; + + default: + if ((sw & 0x010000)) + ec = GPG_ERR_GENERAL; /* Should not happen. */ + else if ((sw & 0xff00) == SW_MORE_DATA) + ec = 0; /* This should actually never been seen here. */ + else + ec = GPG_ERR_CARD; + } + return gpg_error (ec); +} + +/* 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. */ +gpg_error_t +iso7816_select_application (int slot, const char *aid, size_t aidlen) +{ + int sw; + sw = apdu_send_simple (slot, 0x00, CMD_SELECT_FILE, 4, 0, aidlen, aid); + return map_sw (sw); +} + + +gpg_error_t +iso7816_select_file (int slot, int tag, int is_dir, + unsigned char **result, size_t *resultlen) +{ + int sw, p0, p1; + unsigned char tagbuf[2]; + + tagbuf[0] = (tag >> 8) & 0xff; + tagbuf[1] = tag & 0xff; + + if (result || resultlen) + { + *result = NULL; + *resultlen = 0; + return gpg_error (GPG_ERR_NOT_IMPLEMENTED); + } + else + { + p0 = (tag == 0x3F00)? 0: is_dir? 1:2; + p1 = 0x0c; /* No FC return. */ + sw = apdu_send_simple (slot, 0x00, CMD_SELECT_FILE, + p0, p1, 2, (char*)tagbuf ); + return map_sw (sw); + } + + return 0; +} + + +/* Do a select file command with a direct path. */ +gpg_error_t +iso7816_select_path (int slot, const unsigned short *path, size_t pathlen, + unsigned char **result, size_t *resultlen) +{ + int sw, p0, p1; + unsigned char buffer[100]; + int buflen; + + if (result || resultlen) + { + *result = NULL; + *resultlen = 0; + return gpg_error (GPG_ERR_NOT_IMPLEMENTED); + } + + if (pathlen/2 >= sizeof buffer) + return gpg_error (GPG_ERR_TOO_LARGE); + + for (buflen = 0; pathlen; pathlen--, path++) + { + buffer[buflen++] = (*path >> 8); + buffer[buflen++] = *path; + } + + p0 = 0x08; + p1 = 0x0c; /* No FC return. */ + sw = apdu_send_simple (slot, 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, 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); +} + + + +/* Perform a VERIFY command on SLOT using the card holder verification + vector CHVNO with a CHV of lenght CHVLEN. Returns 0 on success. */ +gpg_error_t +iso7816_verify (int slot, int chvno, const char *chv, size_t chvlen) +{ + int sw; + + sw = apdu_send_simple (slot, 0x00, CMD_VERIFY, 0, chvno, chvlen, chv); + return map_sw (sw); +} + +/* Perform a CHANGE_REFERENCE_DATA command on SLOT for the card holder + verification vector CHVNO. If the OLDCHV is NULL (and OLDCHVLEN + 0), a "change reference data" is done, otherwise an "exchange + reference data". The new reference data is expected in NEWCHV of + length NEWCHVLEN. */ +gpg_error_t +iso7816_change_reference_data (int slot, int chvno, + const char *oldchv, size_t oldchvlen, + const char *newchv, size_t newchvlen) +{ + int sw; + char *buf; + + if ((!oldchv && oldchvlen) + || (oldchv && !oldchvlen) + || !newchv || !newchvlen ) + return gpg_error (GPG_ERR_INV_VALUE); + + buf = xtrymalloc (oldchvlen + newchvlen); + if (!buf) + return gpg_error (gpg_err_code_from_errno (errno)); + if (oldchvlen) + memcpy (buf, oldchv, oldchvlen); + memcpy (buf+oldchvlen, newchv, newchvlen); + + sw = apdu_send_simple (slot, 0x00, CMD_CHANGE_REFERENCE_DATA, + oldchvlen? 0 : 1, chvno, oldchvlen+newchvlen, buf); + xfree (buf); + return map_sw (sw); + +} + +gpg_error_t +iso7816_reset_retry_counter (int slot, int chvno, + const char *newchv, size_t newchvlen) +{ + int sw; + + if (!newchv || !newchvlen ) + return gpg_error (GPG_ERR_INV_VALUE); + + sw = apdu_send_simple (slot, 0x00, CMD_RESET_RETRY_COUNTER, + 2, chvno, newchvlen, newchv); + return map_sw (sw); +} + + +/* Perform a GET DATA command requesting TAG and storing the result in + a newly allocated buffer at the address passed by RESULT. Return + the length of this data at the address of RESULTLEN. */ +gpg_error_t +iso7816_get_data (int slot, int tag, + unsigned char **result, size_t *resultlen) +{ + int sw; + + if (!result || !resultlen) + return gpg_error (GPG_ERR_INV_VALUE); + *result = NULL; + *resultlen = 0; + + sw = apdu_send (slot, 0x00, CMD_GET_DATA, + ((tag >> 8) & 0xff), (tag & 0xff), -1, NULL, + result, resultlen); + if (sw != SW_SUCCESS) + { + /* Make sure that pending buffers are released. */ + xfree (*result); + *result = NULL; + *resultlen = 0; + return map_sw (sw); + } + + return 0; +} + + +/* Perform a PUT DATA command on card in SLOT. Write DATA of length + DATALEN to TAG. */ +gpg_error_t +iso7816_put_data (int slot, int tag, + const unsigned char *data, size_t datalen) +{ + int sw; + + sw = apdu_send_simple (slot, 0x00, CMD_PUT_DATA, + ((tag >> 8) & 0xff), (tag & 0xff), + datalen, (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, 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. */ +gpg_error_t +iso7816_compute_ds (int slot, const unsigned char *data, size_t datalen, + unsigned char **result, size_t *resultlen) +{ + int sw; + + if (!data || !datalen || !result || !resultlen) + return gpg_error (GPG_ERR_INV_VALUE); + *result = NULL; + *resultlen = 0; + + sw = apdu_send (slot, 0x00, CMD_PSO, 0x9E, 0x9A, datalen, (const char*)data, + result, resultlen); + if (sw != SW_SUCCESS) + { + /* Make sure that pending buffers are released. */ + xfree (*result); + *result = NULL; + *resultlen = 0; + return map_sw (sw); + } + + return 0; +} + + +/* Perform the security operation DECIPHER. 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. */ +gpg_error_t +iso7816_decipher (int slot, const unsigned char *data, size_t datalen, + 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 (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 (slot, 0x00, CMD_PSO, 0x80, 0x86, + datalen+1, (char*)buf, + result, resultlen); + xfree (buf); + } + else + { + sw = apdu_send (slot, 0x00, CMD_PSO, 0x80, 0x86, + datalen, (const char *)data, + result, resultlen); + } + if (sw != SW_SUCCESS) + { + /* Make sure that pending buffers are released. */ + xfree (*result); + *result = NULL; + *resultlen = 0; + return map_sw (sw); + } + + return 0; +} + + +gpg_error_t +iso7816_internal_authenticate (int slot, + const unsigned char *data, size_t datalen, + unsigned char **result, size_t *resultlen) +{ + int sw; + + if (!data || !datalen || !result || !resultlen) + return gpg_error (GPG_ERR_INV_VALUE); + *result = NULL; + *resultlen = 0; + + sw = apdu_send (slot, 0x00, CMD_INTERNAL_AUTHENTICATE, 0, 0, + datalen, (const char*)data, result, resultlen); + if (sw != SW_SUCCESS) + { + /* Make sure that pending buffers are released. */ + xfree (*result); + *result = NULL; + *resultlen = 0; + return map_sw (sw); + } + + return 0; +} + + +static gpg_error_t +do_generate_keypair (int slot, int readonly, + const unsigned char *data, size_t datalen, + unsigned char **result, size_t *resultlen) +{ + int sw; + + if (!data || !datalen || !result || !resultlen) + return gpg_error (GPG_ERR_INV_VALUE); + *result = NULL; + *resultlen = 0; + + sw = apdu_send (slot, 0x00, CMD_GENERATE_KEYPAIR, readonly? 0x81:0x80, 0, + datalen, (const char*)data, result, resultlen); + if (sw != SW_SUCCESS) + { + /* Make sure that pending buffers are released. */ + xfree (*result); + *result = NULL; + *resultlen = 0; + return map_sw (sw); + } + + return 0; +} + + +gpg_error_t +iso7816_generate_keypair (int slot, + const unsigned char *data, size_t datalen, + unsigned char **result, size_t *resultlen) +{ + return do_generate_keypair (slot, 0, data, datalen, result, resultlen); +} + + +gpg_error_t +iso7816_read_public_key (int slot, + const unsigned char *data, size_t datalen, + unsigned char **result, size_t *resultlen) +{ + return do_generate_keypair (slot, 1, data, datalen, result, resultlen); +} + + + +gpg_error_t +iso7816_get_challenge (int slot, int length, unsigned char *buffer) +{ + int sw; + unsigned char *result; + size_t resultlen, n; + + if (!buffer || length < 1) + return gpg_error (GPG_ERR_INV_VALUE); + + do + { + result = NULL; + n = length > 254? 254 : length; + sw = apdu_send_le (slot, 0x00, CMD_GET_CHALLENGE, 0, 0, -1, NULL, + n, + &result, &resultlen); + if (sw != SW_SUCCESS) + { + /* Make sure that pending buffers are released. */ + xfree (result); + return map_sw (sw); + } + if (resultlen > n) + resultlen = n; + memcpy (buffer, result, resultlen); + buffer += resultlen; + length -= resultlen; + xfree (result); + } + while (length > 0); + + return 0; +} + +/* 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. */ +gpg_error_t +iso7816_read_binary (int slot, size_t offset, size_t nmax, + unsigned char **result, size_t *resultlen) +{ + int sw; + unsigned char *buffer; + size_t bufferlen; + int read_all = !nmax; + size_t n; + + 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; + /* Note, that we to set N to 254 due to problems either with the + ccid driver or some TCOS cards. It actually should be 0 + which is the official ISO value to read a variable length + object. */ + if (read_all || nmax > 254) + n = 254; + else + n = nmax; + sw = apdu_send_le (slot, 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, 0x00, CMD_READ_BINARY, + ((offset>>8) & 0xff), (offset & 0xff) , -1, NULL, + n, &buffer, &bufferlen); + } + + 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_errno (errno); + 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; +} + +/* 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. */ +gpg_error_t +iso7816_read_record (int slot, int recno, int reccount, int short_ef, + unsigned char **result, size_t *resultlen) +{ + int sw; + unsigned char *buffer; + size_t bufferlen; + + 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; + /* Fixme: Either the ccid driver or the TCOS cards have problems + with an Le of 0. */ + sw = apdu_send_le (slot, 0x00, CMD_READ_RECORD, + recno, + short_ef? short_ef : 0x04, + -1, NULL, + 254, &buffer, &bufferlen); + + 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; +} + diff --git a/g10/iso7816.h b/g10/iso7816.h new file mode 100644 index 0000000..c2e742a --- /dev/null +++ b/g10/iso7816.h @@ -0,0 +1,81 @@ +/* iso7816.h - ISO 7816 commands + * Copyright (C) 2003 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * $Id: iso7816.h 3886 2005-09-07 17:05:42Z wk $ + */ + +#ifndef ISO7816_H +#define ISO7816_H + +#if GNUPG_MAJOR_VERSION == 1 +#include "cardglue.h" +#endif + +gpg_error_t iso7816_map_sw (int sw); + +gpg_error_t iso7816_select_application (int slot, + const char *aid, size_t aidlen); +gpg_error_t iso7816_select_file (int slot, int tag, int is_dir, + unsigned char **result, size_t *resultlen); +gpg_error_t iso7816_select_path (int slot, + const unsigned short *path, size_t pathlen, + unsigned char **result, size_t *resultlen); +gpg_error_t iso7816_list_directory (int slot, int list_dirs, + unsigned char **result, size_t *resultlen); +gpg_error_t iso7816_verify (int slot, + int chvno, const char *chv, size_t chvlen); +gpg_error_t iso7816_change_reference_data (int slot, int chvno, + const char *oldchv, size_t oldchvlen, + const char *newchv, size_t newchvlen); +gpg_error_t iso7816_reset_retry_counter (int slot, int chvno, + const char *newchv, size_t newchvlen); +gpg_error_t iso7816_get_data (int slot, int tag, + unsigned char **result, size_t *resultlen); +gpg_error_t iso7816_put_data (int slot, int tag, + const unsigned char *data, size_t datalen); +gpg_error_t iso7816_manage_security_env (int slot, int p1, int p2, + const unsigned char *data, + size_t datalen); +gpg_error_t iso7816_compute_ds (int slot, + const unsigned char *data, size_t datalen, + unsigned char **result, size_t *resultlen); +gpg_error_t iso7816_decipher (int slot, + const unsigned char *data, size_t datalen, + int padind, + unsigned char **result, size_t *resultlen); +gpg_error_t iso7816_internal_authenticate (int slot, + const unsigned char *data, size_t datalen, + unsigned char **result, size_t *resultlen); +gpg_error_t iso7816_generate_keypair (int slot, + const unsigned char *data, size_t datalen, + unsigned char **result, size_t *resultlen); +gpg_error_t iso7816_read_public_key (int slot, + const unsigned char *data, size_t datalen, + unsigned char **result, size_t *resultlen); +gpg_error_t iso7816_get_challenge (int slot, + int length, unsigned char *buffer); + +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 (int slot, int recno, int reccount, + int short_ef, + unsigned char **result, size_t *resultlen); + +#endif /*ISO7816_H*/ diff --git a/g10/kbnode.c b/g10/kbnode.c new file mode 100644 index 0000000..9038f5b --- /dev/null +++ b/g10/kbnode.c @@ -0,0 +1,399 @@ +/* kbnode.c - keyblock node utility functions + * Copyright (C) 1998, 1999, 2000, 2001, 2002, + * 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 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include "util.h" +#include "memory.h" +#include "packet.h" +#include "keydb.h" + +#define USE_UNUSED_NODES 1 + +static KBNODE unused_nodes; + +static KBNODE +alloc_node(void) +{ + KBNODE n; + + n = unused_nodes; + if( n ) + unused_nodes = n->next; + else + n = xmalloc( sizeof *n ); + n->next = NULL; + n->pkt = NULL; + n->flag = 0; + n->private_flag=0; + n->recno = 0; + return n; +} + +static void +free_node( KBNODE n ) +{ + if( n ) { +#if USE_UNUSED_NODES + n->next = unused_nodes; + unused_nodes = n; +#else + xfree( n ); +#endif + } +} + + + +KBNODE +new_kbnode( PACKET *pkt ) +{ + KBNODE n = alloc_node(); + n->pkt = pkt; + return n; +} + + +KBNODE +clone_kbnode( KBNODE node ) +{ + KBNODE n = alloc_node(); + + n->pkt = node->pkt; + n->private_flag = node->private_flag | 2; /* mark cloned */ + return n; +} + + +void +release_kbnode( KBNODE n ) +{ + KBNODE n2; + + while( n ) { + n2 = n->next; + if( !is_cloned_kbnode(n) ) { + free_packet( n->pkt ); + xfree( n->pkt ); + } + free_node( n ); + n = n2; + } +} + + +/**************** + * Delete NODE. + * Note: This only works with walk_kbnode!! + */ +void +delete_kbnode( KBNODE node ) +{ + node->private_flag |= 1; +} + +/**************** + * Append NODE to ROOT. ROOT must exist! + */ +void +add_kbnode( KBNODE root, KBNODE node ) +{ + KBNODE n1; + + for(n1=root; n1->next; n1 = n1->next) + ; + n1->next = node; +} + +/**************** + * Insert NODE into the list after root but before a packet which is not of + * type PKTTYPE + * (only if PKTTYPE != 0) + */ +void +insert_kbnode( KBNODE root, KBNODE node, int pkttype ) +{ + if( !pkttype ) { + node->next = root->next; + root->next = node; + } + else { + KBNODE n1; + + for(n1=root; n1->next; n1 = n1->next) + if( pkttype != n1->next->pkt->pkttype ) { + node->next = n1->next; + n1->next = node; + return; + } + /* no such packet, append */ + node->next = NULL; + n1->next = node; + } +} + + +/**************** + * Find the previous node (if PKTTYPE = 0) or the previous node + * with pkttype PKTTYPE in the list starting with ROOT of NODE. + */ +KBNODE +find_prev_kbnode( KBNODE root, KBNODE node, int pkttype ) +{ + KBNODE n1; + + for (n1=NULL; root && root != node; root = root->next ) { + if (!pkttype ||root->pkt->pkttype == pkttype) + n1 = root; + } + return n1; +} + +/**************** + * Ditto, but find the next packet. The behaviour is trivial if + * PKTTYPE is 0 but if it is specified, the next node with a packet + * of this type is returned. The function has some knowledge about + * the valid ordering of packets: e.g. if the next signature packet + * is requested, the function will not return one if it encounters + * a user-id. + */ +KBNODE +find_next_kbnode( KBNODE node, int pkttype ) +{ + for( node=node->next ; node; node = node->next ) { + if( !pkttype ) + return node; + else if( pkttype == PKT_USER_ID + && ( node->pkt->pkttype == PKT_PUBLIC_KEY + || node->pkt->pkttype == PKT_SECRET_KEY ) ) + return NULL; + else if( pkttype == PKT_SIGNATURE + && ( node->pkt->pkttype == PKT_USER_ID + || node->pkt->pkttype == PKT_PUBLIC_KEY + || node->pkt->pkttype == PKT_SECRET_KEY ) ) + return NULL; + else if( node->pkt->pkttype == pkttype ) + return node; + } + return NULL; +} + + +KBNODE +find_kbnode( KBNODE node, int pkttype ) +{ + for( ; node; node = node->next ) { + if( node->pkt->pkttype == pkttype ) + return node; + } + return NULL; +} + + + +/**************** + * Walk through a list of kbnodes. This function returns + * the next kbnode for each call; before using the function the first + * time, the caller must set CONTEXT to NULL (This has simply the effect + * to start with ROOT). + */ +KBNODE +walk_kbnode( KBNODE root, KBNODE *context, int all ) +{ + KBNODE n; + + do { + if( !*context ) { + *context = root; + n = root; + } + else { + n = (*context)->next; + *context = n; + } + } while( !all && n && is_deleted_kbnode(n) ); + + return n; +} + +void +clear_kbnode_flags( KBNODE n ) +{ + for( ; n; n = n->next ) { + n->flag = 0; + } +} + + +/**************** + * Commit changes made to the kblist at ROOT. Note that ROOT my change, + * and it is therefore passed by reference. + * The function has the effect of removing all nodes marked as deleted. + * returns true if any node has been changed + */ +int +commit_kbnode( KBNODE *root ) +{ + KBNODE n, nl; + int changed = 0; + + for( n = *root, nl=NULL; n; n = nl->next ) { + if( is_deleted_kbnode(n) ) { + if( n == *root ) + *root = nl = n->next; + else + nl->next = n->next; + if( !is_cloned_kbnode(n) ) { + free_packet( n->pkt ); + xfree( n->pkt ); + } + free_node( n ); + changed = 1; + } + else + nl = n; + } + return changed; +} + +void +remove_kbnode( KBNODE *root, KBNODE node ) +{ + KBNODE n, nl; + + for( n = *root, nl=NULL; n; n = nl->next ) { + if( n == node ) { + if( n == *root ) + *root = nl = n->next; + else + nl->next = n->next; + if( !is_cloned_kbnode(n) ) { + free_packet( n->pkt ); + xfree( n->pkt ); + } + free_node( n ); + } + else + nl = n; + } +} + + +/**************** + * Move NODE behind right after WHERE or to the beginning if WHERE is NULL. + */ +void +move_kbnode( KBNODE *root, KBNODE node, KBNODE where ) +{ + KBNODE tmp, prev; + + if( !root || !*root || !node ) + return; /* sanity check */ + for( prev = *root; prev && prev->next != node; prev = prev->next ) + ; + if( !prev ) + return; /* node is not in the list */ + + if( !where ) { /* move node before root */ + if( node == *root ) /* move to itself */ + return; + prev->next = node->next; + node->next = *root; + *root = node; + return; + } + /* move it after where */ + if( node == where ) + return; + tmp = node->next; + node->next = where->next; + where->next = node; + prev->next = tmp; +} + + + + +void +dump_kbnode( KBNODE node ) +{ + for(; node; node = node->next ) { + const char *s; + switch( node->pkt->pkttype ) { + case 0: s="empty"; break; + case PKT_PUBLIC_KEY: s="public-key"; break; + case PKT_SECRET_KEY: s="secret-key"; break; + case PKT_SECRET_SUBKEY: s= "secret-subkey"; break; + case PKT_PUBKEY_ENC: s="public-enc"; break; + case PKT_SIGNATURE: s="signature"; break; + case PKT_ONEPASS_SIG: s="onepass-sig"; break; + case PKT_USER_ID: s="user-id"; break; + case PKT_PUBLIC_SUBKEY: s="public-subkey"; break; + case PKT_COMMENT: s="comment"; break; + case PKT_RING_TRUST: s="trust"; break; + case PKT_PLAINTEXT: s="plaintext"; break; + case PKT_COMPRESSED: s="compressed"; break; + case PKT_ENCRYPTED: s="encrypted"; break; + case PKT_GPG_CONTROL: s="gpg-control"; break; + default: s="unknown"; break; + } + fprintf(stderr, "node %p %02x/%02x type=%s", + node, node->flag, node->private_flag, s); + if( node->pkt->pkttype == PKT_USER_ID ) { + PKT_user_id *uid = node->pkt->pkt.user_id; + fputs(" \"", stderr); + print_string( stderr, uid->name, uid->len, 0 ); + fprintf (stderr, "\" %c%c%c%c\n", + uid->is_expired? 'e':'.', + uid->is_revoked? 'r':'.', + uid->created? 'v':'.', + uid->is_primary? 'p':'.' ); + } + else if( node->pkt->pkttype == PKT_SIGNATURE ) { + fprintf(stderr, " class=%02x keyid=%08lX ts=%lu\n", + node->pkt->pkt.signature->sig_class, + (ulong)node->pkt->pkt.signature->keyid[1], + (ulong)node->pkt->pkt.signature->timestamp); + } + else if( node->pkt->pkttype == PKT_GPG_CONTROL ) { + fprintf(stderr, " ctrl=%d len=%u\n", + node->pkt->pkt.gpg_control->control, + (unsigned int)node->pkt->pkt.gpg_control->datalen); + } + else if( node->pkt->pkttype == PKT_PUBLIC_KEY + || node->pkt->pkttype == PKT_PUBLIC_SUBKEY ) { + PKT_public_key *pk = node->pkt->pkt.public_key; + fprintf(stderr, " keyid=%08lX a=%d u=%d %c%c%c%c\n", + (ulong)keyid_from_pk( pk, NULL ), + pk->pubkey_algo, pk->pubkey_usage, + pk->has_expired? 'e':'.', + pk->is_revoked? 'r':'.', + pk->is_valid? 'v':'.', + pk->mdc_feature? 'm':'.'); + } + else + fputs("\n", stderr); + } +} diff --git a/g10/keydb.c b/g10/keydb.c new file mode 100644 index 0000000..b1a7268 --- /dev/null +++ b/g10/keydb.c @@ -0,0 +1,807 @@ +/* keydb.c - key database dispatcher + * Copyright (C) 2001, 2002, 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 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <assert.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> + +#include "util.h" +#include "options.h" +#include "main.h" /*try_make_homedir ()*/ +#include "packet.h" +#include "keyring.h" +#include "keydb.h" +#include "i18n.h" + +static int active_handles; + +typedef enum { + KEYDB_RESOURCE_TYPE_NONE = 0, + KEYDB_RESOURCE_TYPE_KEYRING +} KeydbResourceType; +#define MAX_KEYDB_RESOURCES 40 + +struct resource_item { + KeydbResourceType type; + union { + KEYRING_HANDLE kr; + } u; + void *token; + int secret; +}; + +static struct resource_item all_resources[MAX_KEYDB_RESOURCES]; +static int used_resources; +static void *primary_keyring=NULL; + +struct keydb_handle { + int locked; + int found; + int current; + int used; /* items in active */ + struct resource_item active[MAX_KEYDB_RESOURCES]; +}; + + +static int lock_all (KEYDB_HANDLE hd); +static void unlock_all (KEYDB_HANDLE hd); + + +/* Handle the creation of a keyring if it does not yet exist. Take + into acount that other processes might have the keyring already + locked. This lock check does not work if the directory itself is + not yet available. */ +static int +maybe_create_keyring (char *filename, int force) +{ + DOTLOCK lockhd = NULL; + IOBUF iobuf; + int rc; + mode_t oldmask; + char *last_slash_in_filename; + + /* A quick test whether the filename already exists. */ + if (!access (filename, F_OK)) + return 0; + + /* If we don't want to create a new file at all, there is no need to + go any further - bail out right here. */ + if (!force) + return G10ERR_OPEN_FILE; + + /* First of all we try to create the home directory. Note, that we + don't do any locking here because any sane application of gpg + would create the home directory by itself and not rely on gpg's + tricky auto-creation which is anyway only done for some home + directory name patterns. */ + last_slash_in_filename = strrchr (filename, DIRSEP_C); + *last_slash_in_filename = 0; + if (access(filename, F_OK)) + { + static int tried; + + if (!tried) + { + tried = 1; + try_make_homedir (filename); + } + if (access (filename, F_OK)) + { + rc = G10ERR_OPEN_FILE; + *last_slash_in_filename = DIRSEP_C; + goto leave; + } + } + *last_slash_in_filename = DIRSEP_C; + + + /* To avoid races with other instances of gpg trying to create or + update the keyring (it is removed during an update for a short + time), we do the next stuff in a locked state. */ + lockhd = create_dotlock (filename); + if (!lockhd) + { + /* A reason for this to fail is that the directory is not + writable. However, this whole locking stuff does not make + sense if this is the case. An empty non-writable directory + with no keyring is not really useful at all. */ + if (opt.verbose) + log_info ("can't allocate lock for `%s'\n", filename ); + + if (!force) + return G10ERR_OPEN_FILE; + else + return G10ERR_GENERAL; + } + + if ( make_dotlock (lockhd, -1) ) + { + /* This is something bad. Probably a stale lockfile. */ + log_info ("can't lock `%s'\n", filename ); + rc = G10ERR_GENERAL; + goto leave; + } + + /* Now the real test while we are locked. */ + if (!access(filename, F_OK)) + { + rc = 0; /* Okay, we may access the file now. */ + goto leave; + } + + /* The file does not yet exist, create it now. */ + oldmask = umask (077); + if (is_secured_filename (filename)) + { + iobuf = NULL; + errno = EPERM; + } + else + iobuf = iobuf_create (filename); + umask (oldmask); + if (!iobuf) + { + log_error ( _("error creating keyring `%s': %s\n"), + filename, strerror(errno)); + rc = G10ERR_OPEN_FILE; + goto leave; + } + + if (!opt.quiet) + log_info (_("keyring `%s' created\n"), filename); + + iobuf_close (iobuf); + /* Must invalidate that ugly cache */ + iobuf_ioctl (NULL, 2, 0, filename); + rc = 0; + + leave: + if (lockhd) + { + release_dotlock (lockhd); + destroy_dotlock (lockhd); + } + return rc; +} + + +/* + * Register a resource (which currently may only be a keyring file). + * The first keyring which is added by this function is + * created if it does not exist. + * Note: this function may be called before secure memory is + * available. + * Flag 1 == force + * Flag 2 == mark resource as primary + * Flag 4 == This is a default resources + */ +int +keydb_add_resource (const char *url, int flags, int secret) +{ + static int any_secret, any_public; + const char *resname = url; + char *filename = NULL; + int force=(flags&1); + int rc = 0; + KeydbResourceType rt = KEYDB_RESOURCE_TYPE_NONE; + void *token; + + /* Do we have an URL? + * gnupg-ring:filename := this is a plain keyring + * filename := See what is is, but create as plain keyring. + */ + if (strlen (resname) > 11) { + if (!strncmp( resname, "gnupg-ring:", 11) ) { + rt = KEYDB_RESOURCE_TYPE_KEYRING; + resname += 11; + } +#if !defined(HAVE_DRIVE_LETTERS) && !defined(__riscos__) + else if (strchr (resname, ':')) { + log_error ("invalid key resource URL `%s'\n", url ); + rc = G10ERR_GENERAL; + goto leave; + } +#endif /* !HAVE_DRIVE_LETTERS && !__riscos__ */ + } + + if (*resname != DIRSEP_C ) { /* do tilde expansion etc */ + if (strchr(resname, DIRSEP_C) ) + filename = make_filename (resname, NULL); + else + filename = make_filename (opt.homedir, resname, NULL); + } + else + filename = xstrdup (resname); + + if (!force) + force = secret? !any_secret : !any_public; + + /* see whether we can determine the filetype */ + if (rt == KEYDB_RESOURCE_TYPE_NONE) { + FILE *fp = fopen( filename, "rb" ); + + if (fp) { + u32 magic; + + if (fread( &magic, 4, 1, fp) == 1 ) { + if (magic == 0x13579ace || magic == 0xce9a5713) + ; /* GDBM magic - no more support */ + else + rt = KEYDB_RESOURCE_TYPE_KEYRING; + } + else /* maybe empty: assume ring */ + rt = KEYDB_RESOURCE_TYPE_KEYRING; + fclose( fp ); + } + else /* no file yet: create ring */ + rt = KEYDB_RESOURCE_TYPE_KEYRING; + } + + switch (rt) { + case KEYDB_RESOURCE_TYPE_NONE: + log_error ("unknown type of key resource `%s'\n", url ); + rc = G10ERR_GENERAL; + goto leave; + + case KEYDB_RESOURCE_TYPE_KEYRING: + rc = maybe_create_keyring (filename, force); + if (rc) + goto leave; + + if(keyring_register_filename (filename, secret, &token)) + { + if (used_resources >= MAX_KEYDB_RESOURCES) + rc = G10ERR_RESOURCE_LIMIT; + else + { + if(flags&2) + primary_keyring=token; + all_resources[used_resources].type = rt; + all_resources[used_resources].u.kr = NULL; /* Not used here */ + all_resources[used_resources].token = token; + all_resources[used_resources].secret = secret; + used_resources++; + } + } + else + { + /* This keyring was already registered, so ignore it. + However, we can still mark it as primary even if it was + already registered. */ + if(flags&2) + primary_keyring=token; + } + break; + + default: + log_error ("resource type of `%s' not supported\n", url); + rc = G10ERR_GENERAL; + goto leave; + } + + /* fixme: check directory permissions and print a warning */ + + leave: + if (rc) + { + /* Secret keyrings are not required in all cases. To avoid + having gpg return failure we use log_info here if the + rewsource is a secret one and marked as default + resource. */ + if ((flags&4) && secret) + log_info (_("keyblock resource `%s': %s\n"), + filename, g10_errstr(rc)); + else + log_error (_("keyblock resource `%s': %s\n"), + filename, g10_errstr(rc)); + } + else if (secret) + any_secret = 1; + else + any_public = 1; + xfree (filename); + return rc; +} + + + + +KEYDB_HANDLE +keydb_new (int secret) +{ + KEYDB_HANDLE hd; + int i, j; + + hd = xmalloc_clear (sizeof *hd); + hd->found = -1; + + assert (used_resources <= MAX_KEYDB_RESOURCES); + for (i=j=0; i < used_resources; i++) + { + if (!all_resources[i].secret != !secret) + continue; + switch (all_resources[i].type) + { + case KEYDB_RESOURCE_TYPE_NONE: /* ignore */ + break; + case KEYDB_RESOURCE_TYPE_KEYRING: + hd->active[j].type = all_resources[i].type; + hd->active[j].token = all_resources[i].token; + hd->active[j].secret = all_resources[i].secret; + hd->active[j].u.kr = keyring_new (all_resources[i].token, secret); + if (!hd->active[j].u.kr) { + xfree (hd); + return NULL; /* fixme: release all previously allocated handles*/ + } + j++; + break; + } + } + hd->used = j; + + active_handles++; + return hd; +} + +void +keydb_release (KEYDB_HANDLE hd) +{ + int i; + + if (!hd) + return; + assert (active_handles > 0); + active_handles--; + + unlock_all (hd); + for (i=0; i < hd->used; i++) { + switch (hd->active[i].type) { + case KEYDB_RESOURCE_TYPE_NONE: + break; + case KEYDB_RESOURCE_TYPE_KEYRING: + keyring_release (hd->active[i].u.kr); + break; + } + } + + xfree (hd); +} + + +/* + * Return the name of the current resource. This is function first + * looks for the last found found, then for the current search + * position, and last returns the first available resource. The + * returned string is only valid as long as the handle exists. This + * function does only return NULL if no handle is specified, in all + * other error cases an empty string is returned. + */ +const char * +keydb_get_resource_name (KEYDB_HANDLE hd) +{ + int idx; + const char *s = NULL; + + if (!hd) + return NULL; + + if ( hd->found >= 0 && hd->found < hd->used) + idx = hd->found; + else if ( hd->current >= 0 && hd->current < hd->used) + idx = hd->current; + else + idx = 0; + + switch (hd->active[idx].type) { + case KEYDB_RESOURCE_TYPE_NONE: + s = NULL; + break; + case KEYDB_RESOURCE_TYPE_KEYRING: + s = keyring_get_resource_name (hd->active[idx].u.kr); + break; + } + + return s? s: ""; +} + + + +static int +lock_all (KEYDB_HANDLE hd) +{ + int i, rc = 0; + + for (i=0; !rc && i < hd->used; i++) { + switch (hd->active[i].type) { + case KEYDB_RESOURCE_TYPE_NONE: + break; + case KEYDB_RESOURCE_TYPE_KEYRING: + rc = keyring_lock (hd->active[i].u.kr, 1); + break; + } + } + + if (rc) { + /* revert the already set locks */ + for (i--; i >= 0; i--) { + switch (hd->active[i].type) { + case KEYDB_RESOURCE_TYPE_NONE: + break; + case KEYDB_RESOURCE_TYPE_KEYRING: + keyring_lock (hd->active[i].u.kr, 0); + break; + } + } + } + else + hd->locked = 1; + + return rc; +} + +static void +unlock_all (KEYDB_HANDLE hd) +{ + int i; + + if (!hd->locked) + return; + + for (i=hd->used-1; i >= 0; i--) { + switch (hd->active[i].type) { + case KEYDB_RESOURCE_TYPE_NONE: + break; + case KEYDB_RESOURCE_TYPE_KEYRING: + keyring_lock (hd->active[i].u.kr, 0); + break; + } + } + hd->locked = 0; +} + + +/* + * Return the last found keyring. Caller must free it. + * The returned keyblock has the kbode flag bit 0 set for the node with + * the public key used to locate the keyblock or flag bit 1 set for + * the user ID node. + */ +int +keydb_get_keyblock (KEYDB_HANDLE hd, KBNODE *ret_kb) +{ + int rc = 0; + + if (!hd) + return G10ERR_INV_ARG; + + if ( hd->found < 0 || hd->found >= hd->used) + return -1; /* nothing found */ + + switch (hd->active[hd->found].type) { + case KEYDB_RESOURCE_TYPE_NONE: + rc = G10ERR_GENERAL; /* oops */ + break; + case KEYDB_RESOURCE_TYPE_KEYRING: + rc = keyring_get_keyblock (hd->active[hd->found].u.kr, ret_kb); + break; + } + + return rc; +} + +/* + * update the current keyblock with KB + */ +int +keydb_update_keyblock (KEYDB_HANDLE hd, KBNODE kb) +{ + int rc = 0; + + if (!hd) + return G10ERR_INV_ARG; + + if ( hd->found < 0 || hd->found >= hd->used) + return -1; /* nothing found */ + + if( opt.dry_run ) + return 0; + + rc = lock_all (hd); + if (rc) + return rc; + + switch (hd->active[hd->found].type) { + case KEYDB_RESOURCE_TYPE_NONE: + rc = G10ERR_GENERAL; /* oops */ + break; + case KEYDB_RESOURCE_TYPE_KEYRING: + rc = keyring_update_keyblock (hd->active[hd->found].u.kr, kb); + break; + } + + unlock_all (hd); + return rc; +} + + +/* + * Insert a new KB into one of the resources. + */ +int +keydb_insert_keyblock (KEYDB_HANDLE hd, KBNODE kb) +{ + int rc = -1; + int idx; + + if (!hd) + return G10ERR_INV_ARG; + + if( opt.dry_run ) + return 0; + + if ( hd->found >= 0 && hd->found < hd->used) + idx = hd->found; + else if ( hd->current >= 0 && hd->current < hd->used) + idx = hd->current; + else + return G10ERR_GENERAL; + + rc = lock_all (hd); + if (rc) + return rc; + + switch (hd->active[idx].type) { + case KEYDB_RESOURCE_TYPE_NONE: + rc = G10ERR_GENERAL; /* oops */ + break; + case KEYDB_RESOURCE_TYPE_KEYRING: + rc = keyring_insert_keyblock (hd->active[idx].u.kr, kb); + break; + } + + unlock_all (hd); + return rc; +} + + +/* + * The current keyblock will be deleted. + */ +int +keydb_delete_keyblock (KEYDB_HANDLE hd) +{ + int rc = -1; + + if (!hd) + return G10ERR_INV_ARG; + + if ( hd->found < 0 || hd->found >= hd->used) + return -1; /* nothing found */ + + if( opt.dry_run ) + return 0; + + rc = lock_all (hd); + if (rc) + return rc; + + switch (hd->active[hd->found].type) { + case KEYDB_RESOURCE_TYPE_NONE: + rc = G10ERR_GENERAL; /* oops */ + break; + case KEYDB_RESOURCE_TYPE_KEYRING: + rc = keyring_delete_keyblock (hd->active[hd->found].u.kr); + break; + } + + unlock_all (hd); + return rc; +} + + +/* + * Locate the default writable key resource, so that the next + * operation (which is only relevant for inserts) will be done on this + * resource. + */ +int +keydb_locate_writable (KEYDB_HANDLE hd, const char *reserved) +{ + int rc; + + if (!hd) + return G10ERR_INV_ARG; + + rc = keydb_search_reset (hd); /* this does reset hd->current */ + if (rc) + return rc; + + /* If we have a primary set, try that one first */ + if(primary_keyring) + { + for ( ; hd->current >= 0 && hd->current < hd->used; hd->current++) + { + if(hd->active[hd->current].token==primary_keyring) + { + if(keyring_is_writable (hd->active[hd->current].token)) + return 0; + else + break; + } + } + + rc = keydb_search_reset (hd); /* this does reset hd->current */ + if (rc) + return rc; + } + + for ( ; hd->current >= 0 && hd->current < hd->used; hd->current++) + { + switch (hd->active[hd->current].type) + { + case KEYDB_RESOURCE_TYPE_NONE: + BUG(); + break; + case KEYDB_RESOURCE_TYPE_KEYRING: + if (keyring_is_writable (hd->active[hd->current].token)) + return 0; /* found (hd->current is set to it) */ + break; + } + } + + return -1; +} + +/* + * Rebuild the caches of all key resources. + */ +void +keydb_rebuild_caches (int noisy) +{ + int i, rc; + + for (i=0; i < used_resources; i++) + { + if (all_resources[i].secret) + continue; + switch (all_resources[i].type) + { + case KEYDB_RESOURCE_TYPE_NONE: /* ignore */ + break; + case KEYDB_RESOURCE_TYPE_KEYRING: + rc = keyring_rebuild_cache (all_resources[i].token,noisy); + if (rc) + log_error (_("failed to rebuild keyring cache: %s\n"), + g10_errstr (rc)); + break; + } + } +} + + + +/* + * Start the next search on this handle right at the beginning + */ +int +keydb_search_reset (KEYDB_HANDLE hd) +{ + int i, rc = 0; + + if (!hd) + return G10ERR_INV_ARG; + + hd->current = 0; + hd->found = -1; + /* and reset all resources */ + for (i=0; !rc && i < hd->used; i++) { + switch (hd->active[i].type) { + case KEYDB_RESOURCE_TYPE_NONE: + break; + case KEYDB_RESOURCE_TYPE_KEYRING: + rc = keyring_search_reset (hd->active[i].u.kr); + break; + } + } + return rc; +} + + +/* + * Search through all keydb resources, starting at the current position, + * for a keyblock which contains one of the keys described in the DESC array. + */ +int +keydb_search2 (KEYDB_HANDLE hd, KEYDB_SEARCH_DESC *desc, + size_t ndesc, size_t *descindex) +{ + int rc = -1; + + if (!hd) + return G10ERR_INV_ARG; + + while (rc == -1 && hd->current >= 0 && hd->current < hd->used) { + switch (hd->active[hd->current].type) { + case KEYDB_RESOURCE_TYPE_NONE: + BUG(); /* we should never see it here */ + break; + case KEYDB_RESOURCE_TYPE_KEYRING: + rc = keyring_search (hd->active[hd->current].u.kr, desc, + ndesc, descindex); + break; + } + if (rc == -1) /* EOF -> switch to next resource */ + hd->current++; + else if (!rc) + hd->found = hd->current; + } + + return rc; +} + +int +keydb_search_first (KEYDB_HANDLE hd) +{ + KEYDB_SEARCH_DESC desc; + + memset (&desc, 0, sizeof desc); + desc.mode = KEYDB_SEARCH_MODE_FIRST; + return keydb_search (hd, &desc, 1); +} + +int +keydb_search_next (KEYDB_HANDLE hd) +{ + KEYDB_SEARCH_DESC desc; + + memset (&desc, 0, sizeof desc); + desc.mode = KEYDB_SEARCH_MODE_NEXT; + return keydb_search (hd, &desc, 1); +} + +int +keydb_search_kid (KEYDB_HANDLE hd, u32 *kid) +{ + KEYDB_SEARCH_DESC desc; + + memset (&desc, 0, sizeof desc); + desc.mode = KEYDB_SEARCH_MODE_LONG_KID; + desc.u.kid[0] = kid[0]; + desc.u.kid[1] = kid[1]; + return keydb_search (hd, &desc, 1); +} + +int +keydb_search_fpr (KEYDB_HANDLE hd, const byte *fpr) +{ + KEYDB_SEARCH_DESC desc; + + memset (&desc, 0, sizeof desc); + desc.mode = KEYDB_SEARCH_MODE_FPR; + memcpy (desc.u.fpr, fpr, MAX_FINGERPRINT_LEN); + return keydb_search (hd, &desc, 1); +} diff --git a/g10/keydb.h b/g10/keydb.h new file mode 100644 index 0000000..b588381 --- /dev/null +++ b/g10/keydb.h @@ -0,0 +1,317 @@ +/* keydb.h - Key database + * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, + * 2006 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#ifndef G10_KEYDB_H +#define G10_KEYDB_H + +#include "types.h" +#include "global.h" +#include "packet.h" +#include "cipher.h" +#ifdef ENABLE_AGENT_SUPPORT +#include "assuan.h" +#endif + +/* What qualifies as a certification (rather than a signature?) */ +#define IS_CERT(s) (IS_KEY_SIG(s) || IS_UID_SIG(s) || IS_SUBKEY_SIG(s) \ + || IS_KEY_REV(s) || IS_UID_REV(s) || IS_SUBKEY_REV(s)) +#define IS_SIG(s) (!IS_CERT(s)) +#define IS_KEY_SIG(s) ((s)->sig_class == 0x1f) +#define IS_UID_SIG(s) (((s)->sig_class & ~3) == 0x10) +#define IS_SUBKEY_SIG(s) ((s)->sig_class == 0x18) +#define IS_KEY_REV(s) ((s)->sig_class == 0x20) +#define IS_UID_REV(s) ((s)->sig_class == 0x30) +#define IS_SUBKEY_REV(s) ((s)->sig_class == 0x28) + +struct getkey_ctx_s; +typedef struct getkey_ctx_s *GETKEY_CTX; + +/**************** + * A Keyblock is all packets which form an entire certificate; + * i.e. the public key, certificate, trust packets, user ids, + * signatures, and subkey. + * + * This structure is also used to bind arbitrary packets together. + */ + +struct kbnode_struct { + KBNODE next; + PACKET *pkt; + int flag; + int private_flag; + ulong recno; /* used while updating the trustdb */ +}; + +#define is_deleted_kbnode(a) ((a)->private_flag & 1) +#define is_cloned_kbnode(a) ((a)->private_flag & 2) + + +enum resource_type { + rt_UNKNOWN = 0, + rt_RING = 1 +}; + + +/**************** + * A data structre to hold information about the external position + * of a keyblock. + */ +struct keyblock_pos_struct { + int resno; /* resource number */ + enum resource_type rt; + off_t offset; /* position information */ + unsigned count; /* length of the keyblock in packets */ + IOBUF fp; /* used by enum_keyblocks */ + int secret; /* working on a secret keyring */ + PACKET *pkt; /* ditto */ + int valid; +}; +typedef struct keyblock_pos_struct KBPOS; + +/* structure to hold a couple of public key certificates */ +typedef struct pk_list *PK_LIST; +struct pk_list { + PK_LIST next; + PKT_public_key *pk; + int flags; /* flag bit 1==throw_keyid */ +}; + +/* structure to hold a couple of secret key certificates */ +typedef struct sk_list *SK_LIST; +struct sk_list { + SK_LIST next; + PKT_secret_key *sk; + int mark; /* not used */ +}; + +/* structure to collect all information which can be used to + * identify a public key */ +typedef struct pubkey_find_info *PUBKEY_FIND_INFO; +struct pubkey_find_info { + u32 keyid[2]; + unsigned nbits; + byte pubkey_algo; + byte fingerprint[MAX_FINGERPRINT_LEN]; + char userid[1]; +}; + + +typedef struct keydb_handle *KEYDB_HANDLE; + +typedef enum { + KEYDB_SEARCH_MODE_NONE, + KEYDB_SEARCH_MODE_EXACT, + KEYDB_SEARCH_MODE_SUBSTR, + KEYDB_SEARCH_MODE_MAIL, + KEYDB_SEARCH_MODE_MAILSUB, + KEYDB_SEARCH_MODE_MAILEND, + KEYDB_SEARCH_MODE_WORDS, + KEYDB_SEARCH_MODE_SHORT_KID, + KEYDB_SEARCH_MODE_LONG_KID, + KEYDB_SEARCH_MODE_FPR16, + KEYDB_SEARCH_MODE_FPR20, + KEYDB_SEARCH_MODE_FPR, + KEYDB_SEARCH_MODE_FIRST, + KEYDB_SEARCH_MODE_NEXT +} KeydbSearchMode; + +struct keydb_search_desc { + KeydbSearchMode mode; + int (*skipfnc)(void *,u32*,PKT_user_id*); + void *skipfncvalue; + union { + const char *name; + byte fpr[MAX_FINGERPRINT_LEN]; + u32 kid[2]; + } u; + int exact; +}; + +/*-- keydb.c --*/ + +/* + Flag 1 == force + Flag 2 == default +*/ +int keydb_add_resource (const char *url, int flags, int secret); +KEYDB_HANDLE keydb_new (int secret); +void keydb_release (KEYDB_HANDLE hd); +const char *keydb_get_resource_name (KEYDB_HANDLE hd); +int keydb_get_keyblock (KEYDB_HANDLE hd, KBNODE *ret_kb); +int keydb_update_keyblock (KEYDB_HANDLE hd, KBNODE kb); +int keydb_insert_keyblock (KEYDB_HANDLE hd, KBNODE kb); +int keydb_delete_keyblock (KEYDB_HANDLE hd); +int keydb_locate_writable (KEYDB_HANDLE hd, const char *reserved); +void keydb_rebuild_caches (int noisy); +int keydb_search_reset (KEYDB_HANDLE hd); +#define keydb_search(a,b,c) keydb_search2((a),(b),(c),NULL) +int keydb_search2 (KEYDB_HANDLE hd, KEYDB_SEARCH_DESC *desc, + size_t ndesc, size_t *descindex); +int keydb_search_first (KEYDB_HANDLE hd); +int keydb_search_next (KEYDB_HANDLE hd); +int keydb_search_kid (KEYDB_HANDLE hd, u32 *kid); +int keydb_search_fpr (KEYDB_HANDLE hd, const byte *fpr); + + +/*-- pkclist.c --*/ +void show_revocation_reason( PKT_public_key *pk, int mode ); +int check_signatures_trust( PKT_signature *sig ); +void release_pk_list( PK_LIST pk_list ); +int build_pk_list( STRLIST rcpts, PK_LIST *ret_pk_list, unsigned use ); +union pref_hint +{ + int digest_length; +}; +int algo_available( preftype_t preftype, int algo, + const union pref_hint *hint ); +int select_algo_from_prefs( PK_LIST pk_list, int preftype, + int request, const union pref_hint *hint ); +int select_mdc_from_pklist (PK_LIST pk_list); + +/*-- skclist.c --*/ +void release_sk_list( SK_LIST sk_list ); +int build_sk_list( STRLIST locusr, SK_LIST *ret_sk_list, + int unlock, unsigned use ); + +/*-- passphrase.h --*/ +#ifdef ENABLE_AGENT_SUPPORT +assuan_context_t agent_open (int try, const char *orig_codeset); +void agent_close (assuan_context_t ctx); +#else +/* If we build w/o agent support, assuan.h won't get included and thus + we need to define a replacement for some Assuan types. */ +typedef int assuan_error_t; +typedef void *assuan_context_t; +#endif +int have_static_passphrase(void); +void set_passphrase_from_string(const char *pass); +void read_passphrase_from_fd( int fd ); +void passphrase_clear_cache ( u32 *keyid, const char *cacheid, int algo ); +char *ask_passphrase (const char *description, + const char *tryagain_text, + const char *promptid, + const char *prompt, + const char *cacheid, int *canceled); +DEK *passphrase_to_dek( u32 *keyid, int pubkey_algo, + int cipher_algo, STRING2KEY *s2k, int mode, + const char *tryagain_text, int *canceled); +void set_next_passphrase( const char *s ); +char *get_last_passphrase(void); +void next_to_last_passphrase(void); + +/*-- getkey.c --*/ +int classify_user_id( const char *name, KEYDB_SEARCH_DESC *desc); +void cache_public_key( PKT_public_key *pk ); +void getkey_disable_caches(void); +int get_pubkey( PKT_public_key *pk, u32 *keyid ); +int get_pubkey_fast ( PKT_public_key *pk, u32 *keyid ); +KBNODE get_pubkeyblock( u32 *keyid ); +int get_pubkey_byname( PKT_public_key *pk, const char *name, + KBNODE *ret_keyblock, KEYDB_HANDLE *ret_kdbhd, + int include_unusable ); +int get_pubkey_bynames( GETKEY_CTX *rx, PKT_public_key *pk, + STRLIST names, KBNODE *ret_keyblock ); +int get_pubkey_next( GETKEY_CTX ctx, PKT_public_key *pk, KBNODE *ret_keyblock ); +void get_pubkey_end( GETKEY_CTX ctx ); +int get_seckey( PKT_secret_key *sk, u32 *keyid ); +int get_primary_seckey( PKT_secret_key *sk, u32 *keyid ); +int get_pubkey_byfprint( PKT_public_key *pk, const byte *fprint, + size_t fprint_len ); +int get_pubkey_byfprint_fast (PKT_public_key *pk, + const byte *fprint, size_t fprint_len); +int get_keyblock_byfprint( KBNODE *ret_keyblock, const byte *fprint, + size_t fprint_len ); +int get_keyblock_bylid( KBNODE *ret_keyblock, ulong lid ); +int seckey_available( u32 *keyid ); +int get_seckey_byname( PKT_secret_key *sk, const char *name, int unlock ); +int get_seckey_bynames( GETKEY_CTX *rx, PKT_secret_key *sk, + STRLIST names, KBNODE *ret_keyblock ); +int get_seckey_next (GETKEY_CTX ctx, PKT_secret_key *sk, KBNODE *ret_keyblock); +void get_seckey_end( GETKEY_CTX ctx ); + +int get_seckey_byfprint( PKT_secret_key *sk, + const byte *fprint, size_t fprint_len); +int get_seckeyblock_byfprint (KBNODE *ret_keyblock, const byte *fprint, + size_t fprint_len ); + + +int enum_secret_keys( void **context, PKT_secret_key *sk, + int with_subkeys, int with_spm ); +void merge_keys_and_selfsig( KBNODE keyblock ); +char*get_user_id_string( u32 *keyid ); +char*get_user_id_string_native( u32 *keyid ); +char*get_long_user_id_string( u32 *keyid ); +char*get_user_id( u32 *keyid, size_t *rn ); +char*get_user_id_native( u32 *keyid ); +KEYDB_HANDLE get_ctx_handle(GETKEY_CTX ctx); +void release_akl(void); +int parse_auto_key_locate(char *options); + +/*-- keyid.c --*/ +int pubkey_letter( int algo ); +void hash_public_key( MD_HANDLE md, PKT_public_key *pk ); +size_t keystrlen(void); +const char *keystr(u32 *keyid); +const char *keystr_from_pk(PKT_public_key *pk); +const char *keystr_from_sk(PKT_secret_key *sk); +const char *keystr_from_desc(KEYDB_SEARCH_DESC *desc); +u32 keyid_from_sk( PKT_secret_key *sk, u32 *keyid ); +u32 keyid_from_pk( PKT_public_key *pk, u32 *keyid ); +u32 keyid_from_sig( PKT_signature *sig, u32 *keyid ); +u32 keyid_from_fingerprint(const byte *fprint, size_t fprint_len, u32 *keyid); +byte *namehash_from_uid(PKT_user_id *uid); +unsigned nbits_from_pk( PKT_public_key *pk ); +unsigned nbits_from_sk( PKT_secret_key *sk ); +const char *datestr_from_pk( PKT_public_key *pk ); +const char *datestr_from_sk( PKT_secret_key *sk ); +const char *datestr_from_sig( PKT_signature *sig ); +const char *expirestr_from_pk( PKT_public_key *pk ); +const char *expirestr_from_sk( PKT_secret_key *sk ); +const char *expirestr_from_sig( PKT_signature *sig ); +const char *revokestr_from_pk( PKT_public_key *pk ); +const char *usagestr_from_pk( PKT_public_key *pk ); +const char *colon_strtime (u32 t); +const char *colon_datestr_from_pk (PKT_public_key *pk); +const char *colon_datestr_from_sk (PKT_secret_key *sk); +const char *colon_datestr_from_sig (PKT_signature *sig); +const char *colon_expirestr_from_sig (PKT_signature *sig); +byte *fingerprint_from_sk( PKT_secret_key *sk, byte *buf, size_t *ret_len ); +byte *fingerprint_from_pk( PKT_public_key *pk, byte *buf, size_t *ret_len ); + +/*-- kbnode.c --*/ +KBNODE new_kbnode( PACKET *pkt ); +KBNODE clone_kbnode( KBNODE node ); +void release_kbnode( KBNODE n ); +void delete_kbnode( KBNODE node ); +void add_kbnode( KBNODE root, KBNODE node ); +void insert_kbnode( KBNODE root, KBNODE node, int pkttype ); +void move_kbnode( KBNODE *root, KBNODE node, KBNODE where ); +void remove_kbnode( KBNODE *root, KBNODE node ); +KBNODE find_prev_kbnode( KBNODE root, KBNODE node, int pkttype ); +KBNODE find_next_kbnode( KBNODE node, int pkttype ); +KBNODE find_kbnode( KBNODE node, int pkttype ); +KBNODE walk_kbnode( KBNODE root, KBNODE *context, int all ); +void clear_kbnode_flags( KBNODE n ); +int commit_kbnode( KBNODE *root ); +void dump_kbnode( KBNODE node ); + +#endif /*G10_KEYDB_H*/ diff --git a/g10/keyedit.c b/g10/keyedit.c new file mode 100644 index 0000000..c1e0ec3 --- /dev/null +++ b/g10/keyedit.c @@ -0,0 +1,5094 @@ +/* keyedit.c - keyedit stuff + * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, + * 2006 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <assert.h> +#include <ctype.h> +#ifdef HAVE_LIBREADLINE +#include <stdio.h> +#include <readline/readline.h> +#endif +#include "options.h" +#include "packet.h" +#include "errors.h" +#include "iobuf.h" +#include "keydb.h" +#include "memory.h" +#include "photoid.h" +#include "util.h" +#include "main.h" +#include "trustdb.h" +#include "filter.h" +#include "ttyio.h" +#include "status.h" +#include "i18n.h" +#include "keyserver-internal.h" + +static void show_prefs( PKT_user_id *uid, PKT_signature *selfsig, int verbose); +static void show_names(KBNODE keyblock,PKT_public_key *pk, + unsigned int flag,int with_prefs); +static void show_key_with_all_names( KBNODE keyblock, int only_marked, + int with_revoker, int with_fpr, int with_subkeys, int with_prefs ); +static void show_key_and_fingerprint( KBNODE keyblock ); +static int menu_adduid( KBNODE keyblock, KBNODE sec_keyblock, + int photo, const char *photo_name ); +static void menu_deluid( KBNODE pub_keyblock, KBNODE sec_keyblock ); +static int menu_delsig( KBNODE pub_keyblock ); +static int menu_clean(KBNODE keyblock,int self_only); +static void menu_delkey( KBNODE pub_keyblock, KBNODE sec_keyblock ); +static int menu_addrevoker( KBNODE pub_keyblock, + KBNODE sec_keyblock, int sensitive ); +static int menu_expire( KBNODE pub_keyblock, KBNODE sec_keyblock ); +static int menu_backsign(KBNODE pub_keyblock,KBNODE sec_keyblock); +static int menu_set_primary_uid( KBNODE pub_keyblock, KBNODE sec_keyblock ); +static int menu_set_preferences( KBNODE pub_keyblock, KBNODE sec_keyblock ); +static int menu_set_keyserver_url (const char *url, + KBNODE pub_keyblock, KBNODE sec_keyblock ); +static int menu_set_notation(const char *string, + KBNODE pub_keyblock,KBNODE sec_keyblock); +static int menu_select_uid( KBNODE keyblock, int idx ); +static int menu_select_uid_namehash( KBNODE keyblock, const char *namehash ); +static int menu_select_key( KBNODE keyblock, int idx ); +static int count_uids( KBNODE keyblock ); +static int count_uids_with_flag( KBNODE keyblock, unsigned flag ); +static int count_keys_with_flag( KBNODE keyblock, unsigned flag ); +static int count_selected_uids( KBNODE keyblock ); +static int real_uids_left( KBNODE keyblock ); +static int count_selected_keys( KBNODE keyblock ); +static int menu_revsig( KBNODE keyblock ); +static int menu_revuid( KBNODE keyblock, KBNODE sec_keyblock ); +static int menu_revkey( KBNODE pub_keyblock, KBNODE sec_keyblock ); +static int menu_revsubkey( KBNODE pub_keyblock, KBNODE sec_keyblock ); +static int enable_disable_key( KBNODE keyblock, int disable ); +static void menu_showphoto( KBNODE keyblock ); + +static int update_trust=0; + +#define CONTROL_D ('D' - 'A' + 1) + +#define NODFLG_BADSIG (1<<0) /* bad signature */ +#define NODFLG_NOKEY (1<<1) /* no public key */ +#define NODFLG_SIGERR (1<<2) /* other sig error */ + +#define NODFLG_MARK_A (1<<4) /* temporary mark */ +#define NODFLG_DELSIG (1<<5) /* to be deleted */ + +#define NODFLG_SELUID (1<<8) /* indicate the selected userid */ +#define NODFLG_SELKEY (1<<9) /* indicate the selected key */ +#define NODFLG_SELSIG (1<<10) /* indicate a selected signature */ + +struct sign_attrib { + int non_exportable,non_revocable; + struct revocation_reason_info *reason; + byte trust_depth,trust_value; + char *trust_regexp; +}; + + +#ifdef ENABLE_CARD_SUPPORT +/* Given a node SEC_NODE with a secret key or subkey, locate the + corresponding public key from pub_keyblock. */ +static PKT_public_key * +find_pk_from_sknode (KBNODE pub_keyblock, KBNODE sec_node) +{ + KBNODE node = pub_keyblock; + PKT_secret_key *sk; + PKT_public_key *pk; + + if (sec_node->pkt->pkttype == PKT_SECRET_KEY + && node->pkt->pkttype == PKT_PUBLIC_KEY) + return node->pkt->pkt.public_key; + if (sec_node->pkt->pkttype != PKT_SECRET_SUBKEY) + return NULL; + sk = sec_node->pkt->pkt.secret_key; + for (; node; node = node->next) + if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY) + { + pk = node->pkt->pkt.public_key; + if (pk->keyid[0] == sk->keyid[0] && pk->keyid[1] == sk->keyid[1]) + return pk; + } + + return NULL; +} +#endif /* ENABLE_CARD_SUPPORT */ + + +/* TODO: Fix duplicated code between here and the check-sigs/list-sigs + code in keylist.c. */ +static int +print_and_check_one_sig_colon( KBNODE keyblock, KBNODE node, + int *inv_sigs, int *no_key, int *oth_err, + int *is_selfsig, int print_without_key ) +{ + PKT_signature *sig = node->pkt->pkt.signature; + int rc, sigrc; + + /* TODO: Make sure a cached sig record here still has the pk that + issued it. See also keylist.c:list_keyblock_print */ + + switch((rc=check_key_signature(keyblock,node,is_selfsig))) + { + case 0: + node->flag &= ~(NODFLG_BADSIG|NODFLG_NOKEY|NODFLG_SIGERR); + sigrc = '!'; + break; + case G10ERR_BAD_SIGN: + node->flag = NODFLG_BADSIG; + sigrc = '-'; + if( inv_sigs ) + ++*inv_sigs; + break; + case G10ERR_NO_PUBKEY: + case G10ERR_UNU_PUBKEY: + node->flag = NODFLG_NOKEY; + sigrc = '?'; + if( no_key ) + ++*no_key; + break; + default: + node->flag = NODFLG_SIGERR; + sigrc = '%'; + if( oth_err ) + ++*oth_err; + break; + } + + if( sigrc != '?' || print_without_key ) + { + printf("sig:%c::%d:%08lX%08lX:%lu:%lu:", + sigrc,sig->pubkey_algo,(ulong)sig->keyid[0],(ulong)sig->keyid[1], + (ulong)sig->timestamp,(ulong)sig->expiredate); + + if(sig->trust_depth || sig->trust_value) + printf("%d %d",sig->trust_depth,sig->trust_value); + + printf(":"); + + if(sig->trust_regexp) + print_string(stdout,sig->trust_regexp,strlen(sig->trust_regexp),':'); + + printf("::%02x%c\n",sig->sig_class,sig->flags.exportable?'x':'l'); + + if(opt.show_subpackets) + print_subpackets_colon(sig); + } + + return (sigrc == '!'); +} + + +/**************** + * Print information about a signature, check it and return true + * if the signature is okay. NODE must be a signature packet. + */ +static int +print_and_check_one_sig( KBNODE keyblock, KBNODE node, + int *inv_sigs, int *no_key, int *oth_err, + int *is_selfsig, int print_without_key ) +{ + PKT_signature *sig = node->pkt->pkt.signature; + int rc, sigrc; + int is_rev = sig->sig_class == 0x30; + + /* TODO: Make sure a cached sig record here still has the pk that + issued it. See also keylist.c:list_keyblock_print */ + + switch( (rc = check_key_signature( keyblock, node, is_selfsig)) ) { + case 0: + node->flag &= ~(NODFLG_BADSIG|NODFLG_NOKEY|NODFLG_SIGERR); + sigrc = '!'; + break; + case G10ERR_BAD_SIGN: + node->flag = NODFLG_BADSIG; + sigrc = '-'; + if( inv_sigs ) + ++*inv_sigs; + break; + case G10ERR_NO_PUBKEY: + case G10ERR_UNU_PUBKEY: + node->flag = NODFLG_NOKEY; + sigrc = '?'; + if( no_key ) + ++*no_key; + break; + default: + node->flag = NODFLG_SIGERR; + sigrc = '%'; + if( oth_err ) + ++*oth_err; + break; + } + if( sigrc != '?' || print_without_key ) { + tty_printf("%s%c%c %c%c%c%c%c%c %s %s", + is_rev? "rev":"sig",sigrc, + (sig->sig_class-0x10>0 && + sig->sig_class-0x10<4)?'0'+sig->sig_class-0x10:' ', + sig->flags.exportable?' ':'L', + sig->flags.revocable?' ':'R', + sig->flags.policy_url?'P':' ', + sig->flags.notation?'N':' ', + sig->flags.expired?'X':' ', + (sig->trust_depth>9)?'T': + (sig->trust_depth>0)?'0'+sig->trust_depth:' ', + keystr(sig->keyid),datestr_from_sig(sig)); + if(opt.list_options&LIST_SHOW_SIG_EXPIRE) + tty_printf(" %s",expirestr_from_sig(sig)); + tty_printf(" "); + if( sigrc == '%' ) + tty_printf("[%s] ", g10_errstr(rc) ); + else if( sigrc == '?' ) + ; + else if( *is_selfsig ) { + tty_printf( is_rev? _("[revocation]") + : _("[self-signature]") ); + } + else + { + size_t n; + char *p = get_user_id( sig->keyid, &n ); + tty_print_utf8_string2(p, n, opt.screen_columns-keystrlen()-26- + ((opt.list_options&LIST_SHOW_SIG_EXPIRE)?11:0)); + xfree(p); + } + tty_printf("\n"); + + if(sig->flags.policy_url && (opt.list_options&LIST_SHOW_POLICY_URLS)) + show_policy_url(sig,3,0); + + if(sig->flags.notation && (opt.list_options&LIST_SHOW_NOTATIONS)) + show_notation(sig,3,0, + ((opt.list_options&LIST_SHOW_STD_NOTATIONS)?1:0)+ + ((opt.list_options&LIST_SHOW_USER_NOTATIONS)?2:0)); + + if(sig->flags.pref_ks && (opt.list_options&LIST_SHOW_KEYSERVER_URLS)) + show_keyserver_url(sig,3,0); + } + + return (sigrc == '!'); +} + + + +/**************** + * Check the keysigs and set the flags to indicate errors. + * Returns true if error found. + */ +static int +check_all_keysigs( KBNODE keyblock, int only_selected ) +{ + KBNODE kbctx; + KBNODE node; + int inv_sigs = 0; + int no_key = 0; + int oth_err = 0; + int has_selfsig = 0; + int mis_selfsig = 0; + int selected = !only_selected; + int anyuid = 0; + + for( kbctx=NULL; (node=walk_kbnode( keyblock, &kbctx, 0)) ; ) { + if( node->pkt->pkttype == PKT_USER_ID ) { + PKT_user_id *uid = node->pkt->pkt.user_id; + + if( only_selected ) + selected = (node->flag & NODFLG_SELUID); + if( selected ) { + tty_printf("uid "); + tty_print_utf8_string( uid->name, uid->len ); + tty_printf("\n"); + if( anyuid && !has_selfsig ) + mis_selfsig++; + has_selfsig = 0; + anyuid = 1; + } + } + else if( selected && node->pkt->pkttype == PKT_SIGNATURE + && ( (node->pkt->pkt.signature->sig_class&~3) == 0x10 + || node->pkt->pkt.signature->sig_class == 0x30 ) ) { + int selfsig; + + if( print_and_check_one_sig( keyblock, node, &inv_sigs, + &no_key, &oth_err, &selfsig, 0 ) ) { + if( selfsig ) + has_selfsig = 1; + } + /* Hmmm: should we update the trustdb here? */ + } + } + if( !has_selfsig ) + mis_selfsig++; + if( inv_sigs == 1 ) + tty_printf(_("1 bad signature\n") ); + else if( inv_sigs ) + tty_printf(_("%d bad signatures\n"), inv_sigs ); + if( no_key == 1 ) + tty_printf(_("1 signature not checked due to a missing key\n") ); + else if( no_key ) + tty_printf(_("%d signatures not checked due to missing keys\n"), no_key ); + if( oth_err == 1 ) + tty_printf(_("1 signature not checked due to an error\n") ); + else if( oth_err ) + tty_printf(_("%d signatures not checked due to errors\n"), oth_err ); + if( mis_selfsig == 1 ) + tty_printf(_("1 user ID without valid self-signature detected\n")); + else if( mis_selfsig ) + tty_printf(_("%d user IDs without valid self-signatures detected\n"), + mis_selfsig); + + return inv_sigs || no_key || oth_err || mis_selfsig; +} + + +static int +sign_mk_attrib( PKT_signature *sig, void *opaque ) +{ + struct sign_attrib *attrib = opaque; + byte buf[8]; + + if( attrib->non_exportable ) { + buf[0] = 0; /* not exportable */ + build_sig_subpkt( sig, SIGSUBPKT_EXPORTABLE, buf, 1 ); + } + + if( attrib->non_revocable ) { + buf[0] = 0; /* not revocable */ + build_sig_subpkt( sig, SIGSUBPKT_REVOCABLE, buf, 1 ); + } + + if( attrib->reason ) + revocation_reason_build_cb( sig, attrib->reason ); + + if(attrib->trust_depth) + { + /* Not critical. If someone doesn't understand trust sigs, + this can still be a valid regular signature. */ + buf[0] = attrib->trust_depth; + buf[1] = attrib->trust_value; + build_sig_subpkt(sig,SIGSUBPKT_TRUST,buf,2); + + /* Critical. If someone doesn't understands regexps, this + whole sig should be invalid. Note the +1 for the length - + regexps are null terminated. */ + if(attrib->trust_regexp) + build_sig_subpkt(sig,SIGSUBPKT_FLAG_CRITICAL|SIGSUBPKT_REGEXP, + attrib->trust_regexp, + strlen(attrib->trust_regexp)+1); + } + + return 0; +} + +static void +trustsig_prompt(byte *trust_value,byte *trust_depth,char **regexp) +{ + char *p; + + *trust_value=0; + *trust_depth=0; + *regexp=NULL; + + /* Same string as pkclist.c:do_edit_ownertrust */ + tty_printf(_("Please decide how far you trust this user to correctly verify" + " other users' keys\n(by looking at passports, checking" + " fingerprints from different sources, etc.)\n")); + tty_printf("\n"); + tty_printf (_(" %d = I trust marginally\n"), 1); + tty_printf (_(" %d = I trust fully\n"), 2); + tty_printf("\n"); + + while(*trust_value==0) + { + p = cpr_get("trustsig_prompt.trust_value",_("Your selection? ")); + trim_spaces(p); + cpr_kill_prompt(); + /* 60 and 120 are as per RFC2440 */ + if(p[0]=='1' && !p[1]) + *trust_value=60; + else if(p[0]=='2' && !p[1]) + *trust_value=120; + xfree(p); + } + + tty_printf("\n"); + + tty_printf(_( + "Please enter the depth of this trust signature.\n" + "A depth greater than 1 allows the key you are signing to make\n" + "trust signatures on your behalf.\n")); + tty_printf("\n"); + + while(*trust_depth==0) + { + p = cpr_get("trustsig_prompt.trust_depth",_("Your selection? ")); + trim_spaces(p); + cpr_kill_prompt(); + *trust_depth=atoi(p); + xfree(p); + } + + tty_printf("\n"); + + tty_printf(_("Please enter a domain to restrict this signature, " + "or enter for none.\n")); + + tty_printf("\n"); + + p=cpr_get("trustsig_prompt.trust_regexp",_("Your selection? ")); + trim_spaces(p); + cpr_kill_prompt(); + + if(strlen(p)>0) + { + char *q=p; + int regexplen=100,ind; + + *regexp=xmalloc(regexplen); + + /* Now mangle the domain the user entered into a regexp. To do + this, \-escape everything that isn't alphanumeric, and attach + "<[^>]+[@.]" to the front, and ">$" to the end. */ + + strcpy(*regexp,"<[^>]+[@.]"); + ind=strlen(*regexp); + + while(*q) + { + if(!((*q>='A' && *q<='Z') + || (*q>='a' && *q<='z') || (*q>='0' && *q<='9'))) + (*regexp)[ind++]='\\'; + + (*regexp)[ind++]=*q; + + if((regexplen-ind)<3) + { + regexplen+=100; + *regexp=xrealloc(*regexp,regexplen); + } + + q++; + } + + (*regexp)[ind]='\0'; + strcat(*regexp,">$"); + } + + xfree(p); + tty_printf("\n"); +} + +/**************** + * Loop over all locusr and and sign the uids after asking. + * If no user id is marked, all user ids will be signed; + * if some user_ids are marked those will be signed. + */ +static int +sign_uids( KBNODE keyblock, STRLIST locusr, int *ret_modified, + int local, int nonrevocable, int trust, int interactive ) +{ + int rc = 0; + SK_LIST sk_list = NULL; + SK_LIST sk_rover = NULL; + PKT_secret_key *sk = NULL; + KBNODE node, uidnode; + PKT_public_key *primary_pk=NULL; + int select_all = !count_selected_uids(keyblock) || interactive; + int all_v3=1; + + /* Are there any non-v3 sigs on this key already? */ + if(PGP2) + for(node=keyblock;node;node=node->next) + if(node->pkt->pkttype==PKT_SIGNATURE && + node->pkt->pkt.signature->version>3) + { + all_v3=0; + break; + } + + /* build a list of all signators. + * + * We use the CERT flag to request the primary which must always + * be one which is capable of signing keys. I can't see a reason + * why to sign keys using a subkey. Implementation of USAGE_CERT + * is just a hack in getkey.c and does not mean that a subkey + * marked as certification capable will be used. */ + rc=build_sk_list( locusr, &sk_list, 0, PUBKEY_USAGE_CERT); + if( rc ) + goto leave; + + /* loop over all signators */ + for( sk_rover = sk_list; sk_rover; sk_rover = sk_rover->next ) { + u32 sk_keyid[2],pk_keyid[2]; + char *p,*trust_regexp=NULL; + int force_v4=0,class=0,selfsig=0; + u32 duration=0,timestamp=0; + byte trust_depth=0,trust_value=0; + + if(local || nonrevocable || trust || + opt.cert_policy_url || opt.cert_notations) + force_v4=1; + + /* we have to use a copy of the sk, because make_keysig_packet + * may remove the protection from sk and if we did other + * changes to the secret key, we would save the unprotected + * version */ + if( sk ) + free_secret_key(sk); + sk = copy_secret_key( NULL, sk_rover->sk ); + keyid_from_sk( sk, sk_keyid ); + /* set mark A for all selected user ids */ + for( node=keyblock; node; node = node->next ) { + if( select_all || (node->flag & NODFLG_SELUID) ) + node->flag |= NODFLG_MARK_A; + else + node->flag &= ~NODFLG_MARK_A; + } + /* reset mark for uids which are already signed */ + uidnode = NULL; + for( node=keyblock; node; node = node->next ) { + if( node->pkt->pkttype == PKT_PUBLIC_KEY ) { + primary_pk=node->pkt->pkt.public_key; + keyid_from_pk( primary_pk, pk_keyid ); + + /* Is this a self-sig? */ + if(pk_keyid[0]==sk_keyid[0] && pk_keyid[1]==sk_keyid[1]) + { + selfsig=1; + /* Do not force a v4 sig here, otherwise it would + be difficult to remake a v3 selfsig. If this + is a v3->v4 promotion case, then we set + force_v4 later anyway. */ + force_v4=0; + } + } + else if( node->pkt->pkttype == PKT_USER_ID ) + { + uidnode = (node->flag & NODFLG_MARK_A)? node : NULL; + if(uidnode) + { + int yesreally=0; + char *user=utf8_to_native(uidnode->pkt->pkt.user_id->name, + uidnode->pkt->pkt.user_id->len, + 0); + + if(uidnode->pkt->pkt.user_id->is_revoked) + { + tty_printf(_("User ID \"%s\" is revoked."),user); + + if(selfsig) + tty_printf("\n"); + else if(opt.expert) + { + tty_printf("\n"); + /* No, so remove the mark and continue */ + if(!cpr_get_answer_is_yes("sign_uid.revoke_okay", + _("Are you sure you " + "still want to sign " + "it? (y/N) "))) + { + uidnode->flag &= ~NODFLG_MARK_A; + uidnode=NULL; + } + else if(interactive) + yesreally=1; + } + else + { + uidnode->flag &= ~NODFLG_MARK_A; + uidnode=NULL; + tty_printf(_(" Unable to sign.\n")); + } + } + else if(uidnode->pkt->pkt.user_id->is_expired) + { + tty_printf(_("User ID \"%s\" is expired."),user); + + if(selfsig) + tty_printf("\n"); + else if(opt.expert) + { + tty_printf("\n"); + /* No, so remove the mark and continue */ + if(!cpr_get_answer_is_yes("sign_uid.expire_okay", + _("Are you sure you " + "still want to sign " + "it? (y/N) "))) + { + uidnode->flag &= ~NODFLG_MARK_A; + uidnode=NULL; + } + else if(interactive) + yesreally=1; + } + else + { + uidnode->flag &= ~NODFLG_MARK_A; + uidnode=NULL; + tty_printf(_(" Unable to sign.\n")); + } + } + else if(!uidnode->pkt->pkt.user_id->created && !selfsig) + { + tty_printf(_("User ID \"%s\" is not self-signed."), + user); + + if(opt.expert) + { + tty_printf("\n"); + /* No, so remove the mark and continue */ + if(!cpr_get_answer_is_yes("sign_uid.nosig_okay", + _("Are you sure you " + "still want to sign " + "it? (y/N) "))) + { + uidnode->flag &= ~NODFLG_MARK_A; + uidnode=NULL; + } + else if(interactive) + yesreally=1; + } + else + { + uidnode->flag &= ~NODFLG_MARK_A; + uidnode=NULL; + tty_printf(_(" Unable to sign.\n")); + } + } + + if(uidnode && interactive && !yesreally) + { + tty_printf(_("User ID \"%s\" is signable. "),user); + if(!cpr_get_answer_is_yes("sign_uid.sign_okay", + _("Sign it? (y/N) "))) + { + uidnode->flag &= ~NODFLG_MARK_A; + uidnode=NULL; + } + } + + xfree(user); + } + } + else if( uidnode && node->pkt->pkttype == PKT_SIGNATURE + && (node->pkt->pkt.signature->sig_class&~3) == 0x10 ) { + if( sk_keyid[0] == node->pkt->pkt.signature->keyid[0] + && sk_keyid[1] == node->pkt->pkt.signature->keyid[1] ) { + char buf[50]; + char *user=utf8_to_native(uidnode->pkt->pkt.user_id->name, + uidnode->pkt->pkt.user_id->len, + 0); + + /* It's a v3 self-sig. Make it into a v4 self-sig? */ + if(node->pkt->pkt.signature->version<4 && selfsig) + { + tty_printf(_("The self-signature on \"%s\"\n" + "is a PGP 2.x-style signature.\n"),user); + + /* Note that the regular PGP2 warning below + still applies if there are no v4 sigs on + this key at all. */ + + if(opt.expert) + if(cpr_get_answer_is_yes("sign_uid.v4_promote_okay", + _("Do you want to promote " + "it to an OpenPGP self-" + "signature? (y/N) "))) + { + force_v4=1; + node->flag|=NODFLG_DELSIG; + xfree(user); + continue; + } + } + + /* Is the current signature expired? */ + if(node->pkt->pkt.signature->flags.expired) + { + tty_printf(_("Your current signature on \"%s\"\n" + "has expired.\n"),user); + + if(cpr_get_answer_is_yes("sign_uid.replace_expired_okay", + _("Do you want to issue a " + "new signature to replace " + "the expired one? (y/N) "))) + { + /* Mark these for later deletion. We + don't want to delete them here, just in + case the replacement signature doesn't + happen for some reason. We only delete + these after the replacement is already + in place. */ + + node->flag|=NODFLG_DELSIG; + xfree(user); + continue; + } + } + + if(!node->pkt->pkt.signature->flags.exportable && !local) + { + /* It's a local sig, and we want to make a + exportable sig. */ + tty_printf(_("Your current signature on \"%s\"\n" + "is a local signature.\n"),user); + + if(cpr_get_answer_is_yes("sign_uid.local_promote_okay", + _("Do you want to promote " + "it to a full exportable " + "signature? (y/N) "))) + { + /* Mark these for later deletion. We + don't want to delete them here, just in + case the replacement signature doesn't + happen for some reason. We only delete + these after the replacement is already + in place. */ + + node->flag|=NODFLG_DELSIG; + xfree(user); + continue; + } + } + + /* Fixme: see whether there is a revocation in which + * case we should allow to sign it again. */ + if (!node->pkt->pkt.signature->flags.exportable && local) + tty_printf(_( + "\"%s\" was already locally signed by key %s\n"), + user,keystr_from_sk(sk)); + else + tty_printf(_("\"%s\" was already signed by key %s\n"), + user,keystr_from_sk(sk)); + + if(opt.expert + && cpr_get_answer_is_yes("sign_uid.dupe_okay", + _("Do you want to sign it " + "again anyway? (y/N) "))) + { + /* Don't delete the old sig here since this is + an --expert thing. */ + xfree(user); + continue; + } + + sprintf (buf, "%08lX%08lX", + (ulong)sk->keyid[0], (ulong)sk->keyid[1] ); + write_status_text (STATUS_ALREADY_SIGNED, buf); + uidnode->flag &= ~NODFLG_MARK_A; /* remove mark */ + + xfree(user); + } + } + } + + /* check whether any uids are left for signing */ + if( !count_uids_with_flag(keyblock, NODFLG_MARK_A) ) + { + tty_printf(_("Nothing to sign with key %s\n"),keystr_from_sk(sk)); + continue; + } + + /* Ask whether we really should sign these user id(s) */ + tty_printf("\n"); + show_key_with_all_names( keyblock, 1, 0, 1, 0, 0 ); + tty_printf("\n"); + + if(primary_pk->expiredate && !selfsig) + { + u32 now=make_timestamp(); + + if(primary_pk->expiredate<=now) + { + tty_printf(_("This key has expired!")); + + if(opt.expert) + { + tty_printf(" "); + if(!cpr_get_answer_is_yes("sign_uid.expired_okay", + _("Are you sure you still " + "want to sign it? (y/N) "))) + continue; + } + else + { + tty_printf(_(" Unable to sign.\n")); + continue; + } + } + else + { + tty_printf(_("This key is due to expire on %s.\n"), + expirestr_from_pk(primary_pk)); + + if(opt.ask_cert_expire) + { + char *answer=cpr_get("sign_uid.expire", + _("Do you want your signature to " + "expire at the same time? (Y/n) ")); + if(answer_is_yes_no_default(answer,1)) + { + /* This fixes the signature timestamp we're + going to make as now. This is so the + expiration date is exactly correct, and not + a few seconds off (due to the time it takes + to answer the questions, enter the + passphrase, etc). */ + timestamp=now; + duration=primary_pk->expiredate-now; + force_v4=1; + } + + cpr_kill_prompt(); + xfree(answer); + } + } + } + + /* Only ask for duration if we haven't already set it to match + the expiration of the pk */ + if(!duration && !selfsig) + { + if(opt.ask_cert_expire) + duration=ask_expire_interval(1,opt.def_cert_expire); + else + duration=parse_expire_string(opt.def_cert_expire); + } + + if(duration) + force_v4=1; + + /* Is --pgp2 on, it's a v3 key, all the sigs on the key are + currently v3 and we're about to sign it with a v4 sig? If + so, danger! */ + if(PGP2 && all_v3 && + (sk->version>3 || force_v4) && primary_pk->version<=3) + { + tty_printf(_("You may not make an OpenPGP signature on a " + "PGP 2.x key while in --pgp2 mode.\n")); + tty_printf(_("This would make the key unusable in PGP 2.x.\n")); + + if(opt.expert) + { + if(!cpr_get_answer_is_yes("sign_uid.v4_on_v3_okay", + _("Are you sure you still " + "want to sign it? (y/N) "))) + continue; + + all_v3=0; + } + else + continue; + } + + if(selfsig) + ; + else + { + if(opt.batch || !opt.ask_cert_level) + class=0x10+opt.def_cert_level; + else + { + char *answer; + + tty_printf(_("How carefully have you verified the key you are " + "about to sign actually belongs\nto the person " + "named above? If you don't know what to " + "answer, enter \"0\".\n")); + tty_printf("\n"); + tty_printf(_(" (0) I will not answer.%s\n"), + opt.def_cert_level==0?" (default)":""); + tty_printf(_(" (1) I have not checked at all.%s\n"), + opt.def_cert_level==1?" (default)":""); + tty_printf(_(" (2) I have done casual checking.%s\n"), + opt.def_cert_level==2?" (default)":""); + tty_printf(_(" (3) I have done very careful checking.%s\n"), + opt.def_cert_level==3?" (default)":""); + tty_printf("\n"); + + while(class==0) + { + answer = cpr_get("sign_uid.class",_("Your selection? " + "(enter `?' for more information): ")); + if(answer[0]=='\0') + class=0x10+opt.def_cert_level; /* Default */ + else if(ascii_strcasecmp(answer,"0")==0) + class=0x10; /* Generic */ + else if(ascii_strcasecmp(answer,"1")==0) + class=0x11; /* Persona */ + else if(ascii_strcasecmp(answer,"2")==0) + class=0x12; /* Casual */ + else if(ascii_strcasecmp(answer,"3")==0) + class=0x13; /* Positive */ + else + tty_printf(_("Invalid selection.\n")); + + xfree(answer); + } + } + + if(trust) + trustsig_prompt(&trust_value,&trust_depth,&trust_regexp); + } + + p=get_user_id_native(sk_keyid); + tty_printf(_("Are you sure that you want to sign this key with your\n" + "key \"%s\" (%s)\n"),p,keystr_from_sk(sk)); + xfree(p); + + if(selfsig) + { + tty_printf("\n"); + tty_printf(_("This will be a self-signature.\n")); + + if( local ) + { + tty_printf("\n"); + tty_printf( + _("WARNING: the signature will not be marked " + "as non-exportable.\n")); + } + + if( nonrevocable ) + { + tty_printf("\n"); + tty_printf( + _("WARNING: the signature will not be marked " + "as non-revocable.\n")); + } + } + else + { + if( local ) + { + tty_printf("\n"); + tty_printf( + _("The signature will be marked as non-exportable.\n")); + } + + if( nonrevocable ) + { + tty_printf("\n"); + tty_printf( + _("The signature will be marked as non-revocable.\n")); + } + + switch(class) + { + case 0x11: + tty_printf("\n"); + tty_printf(_("I have not checked this key at all.\n")); + break; + + case 0x12: + tty_printf("\n"); + tty_printf(_("I have checked this key casually.\n")); + break; + + case 0x13: + tty_printf("\n"); + tty_printf(_("I have checked this key very carefully.\n")); + break; + } + } + + tty_printf("\n"); + + if( opt.batch && opt.answer_yes ) + ; + else if( !cpr_get_answer_is_yes("sign_uid.okay", + _("Really sign? (y/N) ")) ) + continue; + + /* now we can sign the user ids */ + reloop: /* (must use this, because we are modifing the list) */ + primary_pk = NULL; + for( node=keyblock; node; node = node->next ) { + if( node->pkt->pkttype == PKT_PUBLIC_KEY ) + primary_pk = node->pkt->pkt.public_key; + else if( node->pkt->pkttype == PKT_USER_ID + && (node->flag & NODFLG_MARK_A) ) { + PACKET *pkt; + PKT_signature *sig; + struct sign_attrib attrib; + + assert( primary_pk ); + memset( &attrib, 0, sizeof attrib ); + attrib.non_exportable = local; + attrib.non_revocable = nonrevocable; + attrib.trust_depth = trust_depth; + attrib.trust_value = trust_value; + attrib.trust_regexp = trust_regexp; + node->flag &= ~NODFLG_MARK_A; + + /* we force creation of a v4 signature for local + * signatures, otherwise we would not generate the + * subpacket with v3 keys and the signature becomes + * exportable */ + + if(selfsig) + rc = make_keysig_packet( &sig, primary_pk, + node->pkt->pkt.user_id, + NULL, + sk, + 0x13, 0, force_v4?4:0, 0, 0, + keygen_add_std_prefs, primary_pk); + else + rc = make_keysig_packet( &sig, primary_pk, + node->pkt->pkt.user_id, + NULL, + sk, + class, 0, force_v4?4:0, + timestamp, duration, + sign_mk_attrib, &attrib ); + if( rc ) { + log_error(_("signing failed: %s\n"), g10_errstr(rc)); + goto leave; + } + + *ret_modified = 1; /* we changed the keyblock */ + update_trust = 1; + + pkt = xmalloc_clear( sizeof *pkt ); + pkt->pkttype = PKT_SIGNATURE; + pkt->pkt.signature = sig; + insert_kbnode( node, new_kbnode(pkt), PKT_SIGNATURE ); + goto reloop; + } + } + + /* Delete any sigs that got promoted */ + for( node=keyblock; node; node = node->next ) + if( node->flag & NODFLG_DELSIG) + delete_kbnode(node); + } /* end loop over signators */ + + leave: + release_sk_list( sk_list ); + if( sk ) + free_secret_key(sk); + return rc; +} + + + +/**************** + * Change the passphrase of the primary and all secondary keys. + * We use only one passphrase for all keys. + */ +static int +change_passphrase( KBNODE keyblock ) +{ + int rc = 0; + int changed=0; + KBNODE node; + PKT_secret_key *sk; + char *passphrase = NULL; + int no_primary_secrets = 0; + int any; + + node = find_kbnode( keyblock, PKT_SECRET_KEY ); + if( !node ) { + log_error("Oops; secret key not found anymore!\n"); + goto leave; + } + sk = node->pkt->pkt.secret_key; + + for (any = 0, node=keyblock; node; node = node->next) { + if (node->pkt->pkttype == PKT_SECRET_KEY + || node->pkt->pkttype == PKT_SECRET_SUBKEY) { + PKT_secret_key *tmpsk = node->pkt->pkt.secret_key; + if (!(tmpsk->is_protected + && (tmpsk->protect.s2k.mode == 1001 + || tmpsk->protect.s2k.mode == 1002))) { + any = 1; + break; + } + } + } + if (!any) { + tty_printf (_("Key has only stub or on-card key items - " + "no passphrase to change.\n")); + goto leave; + } + + /* See how to handle this key. */ + switch( is_secret_key_protected( sk ) ) { + case -1: + rc = G10ERR_PUBKEY_ALGO; + break; + case 0: + tty_printf(_("This key is not protected.\n")); + break; + default: + if( sk->protect.s2k.mode == 1001 ) { + tty_printf(_("Secret parts of primary key are not available.\n")); + no_primary_secrets = 1; + } + else if( sk->protect.s2k.mode == 1002 ) { + tty_printf(_("Secret parts of primary key are stored on-card.\n")); + no_primary_secrets = 1; + } + else { + tty_printf(_("Key is protected.\n")); + rc = check_secret_key( sk, 0 ); + if( !rc ) + passphrase = get_last_passphrase(); + } + break; + } + + /* Unprotect all subkeys (use the supplied passphrase or ask)*/ + for(node=keyblock; !rc && node; node = node->next ) { + if( node->pkt->pkttype == PKT_SECRET_SUBKEY ) { + PKT_secret_key *subsk = node->pkt->pkt.secret_key; + if ( !(subsk->is_protected + && (subsk->protect.s2k.mode == 1001 + || subsk->protect.s2k.mode == 1002))) { + set_next_passphrase( passphrase ); + rc = check_secret_key( subsk, 0 ); + if( !rc && !passphrase ) + passphrase = get_last_passphrase(); + } + } + } + + if( rc ) + tty_printf(_("Can't edit this key: %s\n"), g10_errstr(rc)); + else { + DEK *dek = NULL; + STRING2KEY *s2k = xmalloc_secure( sizeof *s2k ); + const char *errtext = NULL; + + tty_printf(_("Enter the new passphrase for this secret key.\n\n") ); + + set_next_passphrase( NULL ); + for(;;) { + s2k->mode = opt.s2k_mode; + s2k->hash_algo = S2K_DIGEST_ALGO; + dek = passphrase_to_dek( NULL, 0, opt.s2k_cipher_algo, + s2k, 2, errtext, NULL); + if( !dek ) { + errtext = N_("passphrase not correctly repeated; try again"); + tty_printf ("%s.\n", _(errtext)); + } + else if( !dek->keylen ) { + rc = 0; + tty_printf(_( "You don't want a passphrase -" + " this is probably a *bad* idea!\n\n")); + if( cpr_get_answer_is_yes("change_passwd.empty.okay", + _("Do you really want to do this? (y/N) "))) + { + changed++; + break; + } + } + else { /* okay */ + rc = 0; + if( !no_primary_secrets ) { + sk->protect.algo = dek->algo; + sk->protect.s2k = *s2k; + rc = protect_secret_key( sk, dek ); + } + for(node=keyblock; !rc && node; node = node->next ) { + if( node->pkt->pkttype == PKT_SECRET_SUBKEY ) { + PKT_secret_key *subsk = node->pkt->pkt.secret_key; + if ( !(subsk->is_protected + && (subsk->protect.s2k.mode == 1001 + || subsk->protect.s2k.mode == 1002))) { + subsk->protect.algo = dek->algo; + subsk->protect.s2k = *s2k; + rc = protect_secret_key( subsk, dek ); + } + } + } + if( rc ) + log_error("protect_secret_key failed: %s\n", + g10_errstr(rc) ); + else + changed++; + break; + } + } + xfree(s2k); + xfree(dek); + } + + leave: + xfree( passphrase ); + set_next_passphrase( NULL ); + return changed && !rc; +} + + +/**************** + * There are some keys out (due to a bug in gnupg), where the sequence + * of the packets is wrong. This function fixes that. + * Returns: true if the keyblock has been fixed. + * + * Note: This function does not work if there is more than one user ID. + */ +static int +fix_keyblock( KBNODE keyblock ) +{ + KBNODE node, last, subkey; + int fixed=0; + + /* locate key signatures of class 0x10..0x13 behind sub key packets */ + for( subkey=last=NULL, node = keyblock; node; + last=node, node = node->next ) { + switch( node->pkt->pkttype ) { + case PKT_PUBLIC_SUBKEY: + case PKT_SECRET_SUBKEY: + if( !subkey ) + subkey = last; /* actually it is the one before the subkey */ + break; + case PKT_SIGNATURE: + if( subkey ) { + PKT_signature *sig = node->pkt->pkt.signature; + if( sig->sig_class >= 0x10 && sig->sig_class <= 0x13 ) { + log_info(_( + "moving a key signature to the correct place\n")); + last->next = node->next; + node->next = subkey->next; + subkey->next = node; + node = last; + fixed=1; + } + } + break; + default: break; + } + } + + return fixed; +} + +static int +parse_sign_type(const char *str,int *localsig,int *nonrevokesig,int *trustsig) +{ + const char *p=str; + + while(*p) + { + if(ascii_strncasecmp(p,"l",1)==0) + { + *localsig=1; + p++; + } + else if(ascii_strncasecmp(p,"nr",2)==0) + { + *nonrevokesig=1; + p+=2; + } + else if(ascii_strncasecmp(p,"t",1)==0) + { + *trustsig=1; + p++; + } + else + return 0; + } + + return 1; +} + + +/**************** + * Menu driven key editor. If seckey_check is true, then a secret key + * that matches username will be looked for. If it is false, not all + * commands will be available. + * + * Note: to keep track of some selection we use node->mark MARKBIT_xxxx. + */ + +/* Need an SK for this command */ +#define KEYEDIT_NEED_SK 1 +/* Cannot be viewing the SK for this command */ +#define KEYEDIT_NOT_SK 2 +/* Must be viewing the SK for this command */ +#define KEYEDIT_ONLY_SK 4 +/* Match the tail of the string */ +#define KEYEDIT_TAIL_MATCH 8 + +enum cmdids + { + cmdNONE = 0, + cmdQUIT, cmdHELP, cmdFPR, cmdLIST, cmdSELUID, cmdCHECK, cmdSIGN, + cmdREVSIG, cmdREVKEY, cmdREVUID, cmdDELSIG, cmdPRIMARY, cmdDEBUG, + cmdSAVE, cmdADDUID, cmdADDPHOTO, cmdDELUID, cmdADDKEY, cmdDELKEY, + cmdADDREVOKER, cmdTOGGLE, cmdSELKEY, cmdPASSWD, cmdTRUST, cmdPREF, + cmdEXPIRE, cmdBACKSIGN, cmdENABLEKEY, cmdDISABLEKEY, cmdSHOWPREF, + cmdSETPREF, cmdPREFKS, cmdNOTATION, cmdINVCMD, cmdSHOWPHOTO, cmdUPDTRUST, + cmdCHKTRUST, cmdADDCARDKEY, cmdKEYTOCARD, cmdBKUPTOCARD, cmdCLEAN, + cmdMINIMIZE, cmdNOP + }; + +static struct +{ + const char *name; + enum cmdids id; + int flags; + const char *desc; +} cmds[] = + { + { "quit" , cmdQUIT , 0, N_("quit this menu") }, + { "q" , cmdQUIT , 0, NULL }, + { "save" , cmdSAVE , 0, N_("save and quit") }, + { "help" , cmdHELP , 0, N_("show this help") }, + { "?" , cmdHELP , 0, NULL }, + { "fpr" , cmdFPR , 0, N_("show key fingerprint") }, + { "list" , cmdLIST , 0, N_("list key and user IDs") }, + { "l" , cmdLIST , 0, NULL }, + { "uid" , cmdSELUID , 0, N_("select user ID N") }, + { "key" , cmdSELKEY , 0, N_("select subkey N") }, + { "check" , cmdCHECK , 0, N_("check signatures") }, + { "c" , cmdCHECK , 0, NULL }, + { "cross-certify", cmdBACKSIGN , KEYEDIT_NOT_SK|KEYEDIT_NEED_SK, NULL }, + { "backsign", cmdBACKSIGN , KEYEDIT_NOT_SK|KEYEDIT_NEED_SK, NULL }, + { "sign" , cmdSIGN , KEYEDIT_NOT_SK|KEYEDIT_TAIL_MATCH, + N_("sign selected user IDs [* see below for related commands]") }, + { "s" , cmdSIGN , KEYEDIT_NOT_SK, NULL }, + /* "lsign" and friends will never match since "sign" comes first + and it is a tail match. They are just here so they show up in + the help menu. */ + { "lsign" , cmdNOP , 0, N_("sign selected user IDs locally") }, + { "tsign" , cmdNOP , 0, + N_("sign selected user IDs with a trust signature") }, + { "nrsign" , cmdNOP , 0, + N_("sign selected user IDs with a non-revocable signature") }, + + { "debug" , cmdDEBUG , 0, NULL }, + { "adduid" , cmdADDUID , KEYEDIT_NOT_SK|KEYEDIT_NEED_SK, + N_("add a user ID") }, + { "addphoto", cmdADDPHOTO , KEYEDIT_NOT_SK|KEYEDIT_NEED_SK, + N_("add a photo ID") }, + { "deluid" , cmdDELUID , KEYEDIT_NOT_SK, + N_("delete selected user IDs") }, + /* delphoto is really deluid in disguise */ + { "delphoto", cmdDELUID , KEYEDIT_NOT_SK, NULL }, + + { "addkey" , cmdADDKEY , KEYEDIT_NOT_SK|KEYEDIT_NEED_SK, + N_("add a subkey") }, + +#ifdef ENABLE_CARD_SUPPORT + { "addcardkey", cmdADDCARDKEY , KEYEDIT_NOT_SK|KEYEDIT_NEED_SK, + N_("add a key to a smartcard") }, + { "keytocard", cmdKEYTOCARD , KEYEDIT_NEED_SK|KEYEDIT_ONLY_SK, + N_("move a key to a smartcard")}, + { "bkuptocard", cmdBKUPTOCARD , KEYEDIT_NEED_SK|KEYEDIT_ONLY_SK, + N_("move a backup key to a smartcard")}, +#endif /*ENABLE_CARD_SUPPORT*/ + + { "delkey" , cmdDELKEY , KEYEDIT_NOT_SK, + N_("delete selected subkeys") }, + { "addrevoker",cmdADDREVOKER,KEYEDIT_NOT_SK|KEYEDIT_NEED_SK, + N_("add a revocation key") }, + { "delsig" , cmdDELSIG , KEYEDIT_NOT_SK, + N_("delete signatures from the selected user IDs") }, + { "expire" , cmdEXPIRE , KEYEDIT_NOT_SK|KEYEDIT_NEED_SK, + N_("change the expiration date for the key or selected subkeys") }, + { "primary" , cmdPRIMARY , KEYEDIT_NOT_SK|KEYEDIT_NEED_SK, + N_("flag the selected user ID as primary")}, + { "toggle" , cmdTOGGLE , KEYEDIT_NEED_SK, + N_("toggle between the secret and public key listings") }, + { "t" , cmdTOGGLE , KEYEDIT_NEED_SK, NULL }, + { "pref" , cmdPREF , KEYEDIT_NOT_SK, + N_("list preferences (expert)")}, + { "showpref", cmdSHOWPREF , KEYEDIT_NOT_SK, + N_("list preferences (verbose)") }, + { "setpref" , cmdSETPREF , KEYEDIT_NOT_SK|KEYEDIT_NEED_SK, + N_("set preference list for the selected user IDs") }, + /* Alias */ + { "updpref" , cmdSETPREF , KEYEDIT_NOT_SK|KEYEDIT_NEED_SK, NULL }, + + { "keyserver",cmdPREFKS , KEYEDIT_NOT_SK|KEYEDIT_NEED_SK, + N_("set the preferred keyserver URL for the selected user IDs")}, + { "notation", cmdNOTATION , KEYEDIT_NOT_SK|KEYEDIT_NEED_SK, + N_("set a notation for the selected user IDs")}, + { "passwd" , cmdPASSWD , KEYEDIT_NOT_SK|KEYEDIT_NEED_SK, + N_("change the passphrase") }, + /* Alias */ + { "password", cmdPASSWD , KEYEDIT_NOT_SK|KEYEDIT_NEED_SK, NULL }, + + { "trust" , cmdTRUST , KEYEDIT_NOT_SK, N_("change the ownertrust") }, + { "revsig" , cmdREVSIG , KEYEDIT_NOT_SK, + N_("revoke signatures on the selected user IDs") }, + { "revuid" , cmdREVUID , KEYEDIT_NOT_SK|KEYEDIT_NEED_SK, + N_("revoke selected user IDs") }, + /* Alias */ + { "revphoto", cmdREVUID , KEYEDIT_NOT_SK|KEYEDIT_NEED_SK, NULL }, + + { "revkey" , cmdREVKEY , KEYEDIT_NOT_SK|KEYEDIT_NEED_SK, + N_("revoke key or selected subkeys") }, + { "enable" , cmdENABLEKEY , KEYEDIT_NOT_SK, N_("enable key") }, + { "disable" , cmdDISABLEKEY, KEYEDIT_NOT_SK, N_("disable key") }, + { "showphoto",cmdSHOWPHOTO , 0, N_("show selected photo IDs") }, + { "clean", cmdCLEAN , KEYEDIT_NOT_SK, + N_("compact unusable user IDs and remove unusable signatures from key")}, + { "minimize", cmdMINIMIZE , KEYEDIT_NOT_SK, + N_("compact unusable user IDs and remove all signatures from key") }, + { NULL, cmdNONE, 0, NULL } + }; + +#ifdef HAVE_LIBREADLINE + +/* These two functions are used by readline for command completion. */ + +static char * +command_generator(const char *text,int state) +{ + static int list_index,len; + const char *name; + + /* If this is a new word to complete, initialize now. This includes + saving the length of TEXT for efficiency, and initializing the + index variable to 0. */ + if(!state) + { + list_index=0; + len=strlen(text); + } + + /* Return the next partial match */ + while((name=cmds[list_index].name)) + { + /* Only complete commands that have help text */ + if(cmds[list_index++].desc && strncmp(name,text,len)==0) + return strdup(name); + } + + return NULL; +} + +static char ** +keyedit_completion(const char *text, int start, int end) +{ + /* If we are at the start of a line, we try and command-complete. + If not, just do nothing for now. */ + + if(start==0) + return rl_completion_matches(text,command_generator); + + rl_attempted_completion_over=1; + + return NULL; +} +#endif /* HAVE_LIBREADLINE */ + + +void +keyedit_menu( const char *username, STRLIST locusr, + STRLIST commands, int quiet, int seckey_check ) +{ + enum cmdids cmd = 0; + int rc = 0; + KBNODE keyblock = NULL; + KEYDB_HANDLE kdbhd = NULL; + KBNODE sec_keyblock = NULL; + KEYDB_HANDLE sec_kdbhd = NULL; + KBNODE cur_keyblock; + char *answer = NULL; + int redisplay = 1; + int modified = 0; + int sec_modified = 0; + int toggle; + int have_commands = !!commands; + + if ( opt.command_fd != -1 ) + ; + else if( opt.batch && !have_commands ) + { + log_error(_("can't do this in batch mode\n")); + goto leave; + } + +#ifdef HAVE_W32_SYSTEM + /* Due to Windows peculiarities we need to make sure that the + trustdb stale check is done before we open another file + (i.e. by searching for a key). In theory we could make sure + that the files are closed after use but the open/close caches + inhibits that and flushing the cache right before the stale + check is not easy to implement. Thus we take the easy way out + and run the stale check as early as possible. Note, that for + non- W32 platforms it is run indirectly trough a call to + get_validity (). */ + check_trustdb_stale (); +#endif + + /* Get the public key */ + rc = get_pubkey_byname (NULL, username, &keyblock, &kdbhd, 1); + if( rc ) + goto leave; + if( fix_keyblock( keyblock ) ) + modified++; + if( collapse_uids( &keyblock ) ) + modified++; + reorder_keyblock(keyblock); + + if(seckey_check) + {/* see whether we have a matching secret key */ + PKT_public_key *pk = keyblock->pkt->pkt.public_key; + + sec_kdbhd = keydb_new (1); + { + byte afp[MAX_FINGERPRINT_LEN]; + size_t an; + + fingerprint_from_pk (pk, afp, &an); + while (an < MAX_FINGERPRINT_LEN) + afp[an++] = 0; + rc = keydb_search_fpr (sec_kdbhd, afp); + } + if (!rc) + { + rc = keydb_get_keyblock (sec_kdbhd, &sec_keyblock); + if (rc) + { + log_error (_("error reading secret keyblock \"%s\": %s\n"), + username, g10_errstr(rc)); + } + else + { + merge_keys_and_selfsig( sec_keyblock ); + if( fix_keyblock( sec_keyblock ) ) + sec_modified++; + } + } + + if (rc) { + sec_keyblock = NULL; + keydb_release (sec_kdbhd); sec_kdbhd = NULL; + rc = 0; + } + + if( sec_keyblock && !quiet ) + tty_printf(_("Secret key is available.\n")); + } + + toggle = 0; + cur_keyblock = keyblock; + for(;;) { /* main loop */ + int i, arg_number, photo; + const char *arg_string = ""; + char *p; + PKT_public_key *pk=keyblock->pkt->pkt.public_key; + + tty_printf("\n"); + + if( redisplay && !quiet ) + { + show_key_with_all_names( cur_keyblock, 0, 1, 0, 1, 0 ); + tty_printf("\n"); + redisplay = 0; + } + do { + xfree(answer); + if( have_commands ) { + if( commands ) { + answer = xstrdup( commands->d ); + commands = commands->next; + } + else if( opt.batch ) { + answer = xstrdup("quit"); + } + else + have_commands = 0; + } + if( !have_commands ) + { + tty_enable_completion(keyedit_completion); + answer = cpr_get_no_help("keyedit.prompt", _("Command> ")); + cpr_kill_prompt(); + tty_disable_completion(); + } + trim_spaces(answer); + } while( *answer == '#' ); + + arg_number = 0; /* Yes, here is the init which egcc complains about */ + photo = 0; /* This too */ + if( !*answer ) + cmd = cmdLIST; + else if( *answer == CONTROL_D ) + cmd = cmdQUIT; + else if( digitp(answer ) ) { + cmd = cmdSELUID; + arg_number = atoi(answer); + } + else { + if( (p=strchr(answer,' ')) ) { + *p++ = 0; + trim_spaces(answer); + trim_spaces(p); + arg_number = atoi(p); + arg_string = p; + } + + for(i=0; cmds[i].name; i++ ) + { + if(cmds[i].flags & KEYEDIT_TAIL_MATCH) + { + size_t l=strlen(cmds[i].name); + size_t a=strlen(answer); + if(a>=l) + { + if(ascii_strcasecmp(&answer[a-l],cmds[i].name)==0) + { + answer[a-l]='\0'; + break; + } + } + } + else if( !ascii_strcasecmp( answer, cmds[i].name ) ) + break; + } + if((cmds[i].flags & KEYEDIT_NEED_SK) && !sec_keyblock ) + { + tty_printf(_("Need the secret key to do this.\n")); + cmd = cmdNOP; + } + else if(((cmds[i].flags & KEYEDIT_NOT_SK) && sec_keyblock + && toggle) + ||((cmds[i].flags & KEYEDIT_ONLY_SK) && sec_keyblock + && !toggle)) + { + tty_printf(_("Please use the command \"toggle\" first.\n")); + cmd = cmdNOP; + } + else + cmd = cmds[i].id; + } + switch( cmd ) + { + case cmdHELP: + for(i=0; cmds[i].name; i++ ) + { + if((cmds[i].flags & KEYEDIT_NEED_SK) && !sec_keyblock ) + ; /* skip if we do not have the secret key */ + else if( cmds[i].desc ) + tty_printf("%-11s %s\n", cmds[i].name, _(cmds[i].desc) ); + } + + tty_printf("\n"); + tty_printf(_( +"* The `sign' command may be prefixed with an `l' for local " +"signatures (lsign),\n" +" a `t' for trust signatures (tsign), an `nr' for non-revocable signatures\n" +" (nrsign), or any combination thereof (ltsign, tnrsign, etc.).\n")); + + break; + + case cmdLIST: + redisplay = 1; + break; + + case cmdFPR: + show_key_and_fingerprint( keyblock ); + break; + + case cmdSELUID: + if(strlen(arg_string)==NAMEHASH_LEN*2) + redisplay=menu_select_uid_namehash(cur_keyblock,arg_string); + else + redisplay=menu_select_uid(cur_keyblock,arg_number); + break; + + case cmdSELKEY: + if( menu_select_key( cur_keyblock, arg_number ) ) + redisplay = 1; + break; + + case cmdCHECK: + /* we can only do this with the public key becuase the + * check functions can't cope with secret keys and it + * is questionable whether this would make sense at all */ + check_all_keysigs( keyblock, count_selected_uids(keyblock) ); + break; + + case cmdSIGN: /* sign (only the public key) */ + { + int localsig=0,nonrevokesig=0,trustsig=0,interactive=0; + + if( pk->is_revoked ) + { + tty_printf(_("Key is revoked.")); + + if(opt.expert) + { + tty_printf(" "); + if(!cpr_get_answer_is_yes("keyedit.sign_revoked.okay", + _("Are you sure you still want" + " to sign it? (y/N) "))) + break; + } + else + { + tty_printf(_(" Unable to sign.\n")); + break; + } + } + + if(count_uids(keyblock) > 1 && !count_selected_uids(keyblock) + && !cpr_get_answer_is_yes("keyedit.sign_all.okay", + _("Really sign all user IDs?" + " (y/N) "))) + { + if(opt.interactive) + interactive=1; + else + { + tty_printf(_("Hint: Select the user IDs to sign\n")); + have_commands = 0; + break; + } + + } + /* What sort of signing are we doing? */ + if(!parse_sign_type(answer,&localsig,&nonrevokesig,&trustsig)) + { + tty_printf(_("Unknown signature type `%s'\n"),answer); + break; + } + + sign_uids(keyblock, locusr, &modified, + localsig, nonrevokesig, trustsig, interactive); + } + break; + + case cmdDEBUG: + dump_kbnode( cur_keyblock ); + break; + + case cmdTOGGLE: + toggle = !toggle; + cur_keyblock = toggle? sec_keyblock : keyblock; + redisplay = 1; + break; + + case cmdADDPHOTO: + if (RFC2440 || RFC1991 || PGP2) + { + tty_printf( + _("This command is not allowed while in %s mode.\n"), + RFC2440?"OpenPGP":PGP2?"PGP2":"RFC-1991"); + break; + } + photo=1; + /* fall through */ + + case cmdADDUID: + if( menu_adduid( keyblock, sec_keyblock, photo, arg_string ) ) + { + update_trust = 1; + redisplay = 1; + sec_modified = modified = 1; + merge_keys_and_selfsig( sec_keyblock ); + merge_keys_and_selfsig( keyblock ); + } + break; + + case cmdDELUID: { + int n1; + + if( !(n1=count_selected_uids(keyblock)) ) + tty_printf(_("You must select at least one user ID.\n")); + else if( real_uids_left(keyblock) < 1 ) + tty_printf(_("You can't delete the last user ID!\n")); + else if( cpr_get_answer_is_yes("keyedit.remove.uid.okay", + n1 > 1? _("Really remove all selected user IDs? (y/N) ") + : _("Really remove this user ID? (y/N) ") + ) ) { + menu_deluid( keyblock, sec_keyblock ); + redisplay = 1; + modified = 1; + if( sec_keyblock ) + sec_modified = 1; + } + } + break; + + case cmdDELSIG: { + int n1; + + if( !(n1=count_selected_uids(keyblock)) ) + tty_printf(_("You must select at least one user ID.\n")); + else if( menu_delsig( keyblock ) ) { + /* no redisplay here, because it may scroll away some + * status output of delsig */ + modified = 1; + } + } + break; + + case cmdADDKEY: + if( generate_subkeypair( keyblock, sec_keyblock ) ) { + redisplay = 1; + sec_modified = modified = 1; + merge_keys_and_selfsig( sec_keyblock ); + merge_keys_and_selfsig( keyblock ); + } + break; + +#ifdef ENABLE_CARD_SUPPORT + case cmdADDCARDKEY: + if (card_generate_subkey (keyblock, sec_keyblock)) { + redisplay = 1; + sec_modified = modified = 1; + merge_keys_and_selfsig( sec_keyblock ); + merge_keys_and_selfsig( keyblock ); + } + break; + + case cmdKEYTOCARD: + { + KBNODE node=NULL; + switch ( count_selected_keys (sec_keyblock) ) + { + case 0: + if (cpr_get_answer_is_yes("keyedit.keytocard.use_primary", + _("Really move the primary key? (y/N) "))) + node = sec_keyblock; + break; + case 1: + for (node = sec_keyblock; node; node = node->next ) + { + if (node->pkt->pkttype == PKT_SECRET_SUBKEY + && node->flag & NODFLG_SELKEY) + break; + } + break; + default: + tty_printf(_("You must select exactly one key.\n")); + break; + } + if (node) + { + PKT_public_key *xxpk = find_pk_from_sknode (keyblock, node); + if (card_store_subkey (node, xxpk?xxpk->pubkey_usage:0)) + { + redisplay = 1; + sec_modified = 1; + } + } + } + break; + + case cmdBKUPTOCARD: + { + /* Ask for a filename, check whether this is really a + backup key as generated by the card generation, parse + that key and store it on card. */ + KBNODE node; + const char *fname; + PACKET *pkt; + IOBUF a; + + fname = arg_string; + if (!*fname) + { + tty_printf (_("Command expects a filename argument\n")); + break; + } + + /* Open that file. */ + a = iobuf_open (fname); + if (a && is_secured_file (iobuf_get_fd (a))) + { + iobuf_close (a); + a = NULL; + errno = EPERM; + } + if (!a) + { + tty_printf (_("Can't open `%s': %s\n"), + fname, strerror(errno)); + break; + } + + /* Parse and check that file. */ + pkt = xmalloc (sizeof *pkt); + init_packet (pkt); + rc = parse_packet (a, pkt); + iobuf_close (a); + iobuf_ioctl (NULL, 2, 0, (char*)fname); /* (invalidate cache). */ + if (!rc + && pkt->pkttype != PKT_SECRET_KEY + && pkt->pkttype != PKT_SECRET_SUBKEY) + rc = G10ERR_NO_SECKEY; + if (rc) + { + tty_printf(_("Error reading backup key from `%s': %s\n"), + fname, g10_errstr (rc)); + free_packet (pkt); + xfree (pkt); + break; + } + node = new_kbnode (pkt); + + /* Store it. */ + if (card_store_subkey (node, 0)) + { + redisplay = 1; + sec_modified = 1; + } + release_kbnode (node); + } + break; + +#endif /* ENABLE_CARD_SUPPORT */ + + case cmdDELKEY: { + int n1; + + if( !(n1=count_selected_keys( keyblock )) ) + tty_printf(_("You must select at least one key.\n")); + else if( !cpr_get_answer_is_yes( "keyedit.remove.subkey.okay", + n1 > 1? + _("Do you really want to delete the selected keys? (y/N) "): + _("Do you really want to delete this key? (y/N) ") + )) + ; + else { + menu_delkey( keyblock, sec_keyblock ); + redisplay = 1; + modified = 1; + if( sec_keyblock ) + sec_modified = 1; + } + } + break; + + case cmdADDREVOKER: + { + int sensitive=0; + + if(ascii_strcasecmp(arg_string,"sensitive")==0) + sensitive=1; + if( menu_addrevoker( keyblock, sec_keyblock, sensitive ) ) { + redisplay = 1; + sec_modified = modified = 1; + merge_keys_and_selfsig( sec_keyblock ); + merge_keys_and_selfsig( keyblock ); + } + } + break; + + case cmdREVUID: { + int n1; + + if( !(n1=count_selected_uids(keyblock)) ) + tty_printf(_("You must select at least one user ID.\n")); + else if( cpr_get_answer_is_yes( + "keyedit.revoke.uid.okay", + n1 > 1? _("Really revoke all selected user IDs? (y/N) ") + : _("Really revoke this user ID? (y/N) ") + ) ) { + if(menu_revuid(keyblock,sec_keyblock)) + { + modified=1; + redisplay=1; + } + } + } + break; + + case cmdREVKEY: + { + int n1; + + if( !(n1=count_selected_keys( keyblock )) ) + { + if(cpr_get_answer_is_yes("keyedit.revoke.subkey.okay", + _("Do you really want to revoke" + " the entire key? (y/N) "))) + { + if(menu_revkey(keyblock,sec_keyblock)) + modified=1; + + redisplay=1; + } + } + else if(cpr_get_answer_is_yes("keyedit.revoke.subkey.okay", + n1 > 1? + _("Do you really want to revoke" + " the selected subkeys? (y/N) "): + _("Do you really want to revoke" + " this subkey? (y/N) "))) + { + if( menu_revsubkey( keyblock, sec_keyblock ) ) + modified = 1; + + redisplay = 1; + } + + if(modified) + merge_keys_and_selfsig( keyblock ); + } + break; + + case cmdEXPIRE: + if( menu_expire( keyblock, sec_keyblock ) ) + { + merge_keys_and_selfsig( sec_keyblock ); + merge_keys_and_selfsig( keyblock ); + sec_modified = 1; + modified = 1; + redisplay = 1; + } + break; + + case cmdBACKSIGN: + if(menu_backsign(keyblock,sec_keyblock)) + { + sec_modified = 1; + modified = 1; + redisplay = 1; + } + break; + + case cmdPRIMARY: + if( menu_set_primary_uid ( keyblock, sec_keyblock ) ) { + merge_keys_and_selfsig( keyblock ); + modified = 1; + redisplay = 1; + } + break; + + case cmdPASSWD: + if( change_passphrase( sec_keyblock ) ) + sec_modified = 1; + break; + + case cmdTRUST: + if(opt.trust_model==TM_EXTERNAL) + { + tty_printf(_("Owner trust may not be set while " + "using an user provided trust database\n")); + break; + } + + show_key_with_all_names( keyblock, 0, 0, 0, 1, 0 ); + tty_printf("\n"); + if( edit_ownertrust( find_kbnode( keyblock, + PKT_PUBLIC_KEY )->pkt->pkt.public_key, 1 ) ) { + redisplay = 1; + /* No real need to set update_trust here as + edit_ownertrust() calls revalidation_mark() + anyway. */ + update_trust=1; + } + break; + + case cmdPREF: + { + int count=count_selected_uids(keyblock); + assert(keyblock->pkt->pkttype==PKT_PUBLIC_KEY); + show_names(keyblock,keyblock->pkt->pkt.public_key, + count?NODFLG_SELUID:0,1); + } + break; + + case cmdSHOWPREF: + { + int count=count_selected_uids(keyblock); + assert(keyblock->pkt->pkttype==PKT_PUBLIC_KEY); + show_names(keyblock,keyblock->pkt->pkt.public_key, + count?NODFLG_SELUID:0,2); + } + break; + + case cmdSETPREF: + { + PKT_user_id *tempuid; + + keygen_set_std_prefs(!*arg_string?"default" : arg_string, 0); + + tempuid=keygen_get_std_prefs(); + tty_printf(_("Set preference list to:\n")); + show_prefs(tempuid,NULL,1); + free_user_id(tempuid); + + if(cpr_get_answer_is_yes("keyedit.setpref.okay", + count_selected_uids (keyblock)? + _("Really update the preferences" + " for the selected user IDs? (y/N) "): + _("Really update the preferences? (y/N) "))) + { + if ( menu_set_preferences (keyblock, sec_keyblock) ) + { + merge_keys_and_selfsig (keyblock); + modified = 1; + redisplay = 1; + } + } + } + break; + + case cmdPREFKS: + if( menu_set_keyserver_url ( *arg_string?arg_string:NULL, + keyblock, sec_keyblock ) ) + { + merge_keys_and_selfsig( keyblock ); + modified = 1; + redisplay = 1; + } + break; + + case cmdNOTATION: + if( menu_set_notation ( *arg_string?arg_string:NULL, + keyblock, sec_keyblock ) ) + { + merge_keys_and_selfsig( keyblock ); + modified = 1; + redisplay = 1; + } + break; + + case cmdNOP: + break; + + case cmdREVSIG: + if( menu_revsig( keyblock ) ) { + redisplay = 1; + modified = 1; + } + break; + + case cmdENABLEKEY: + case cmdDISABLEKEY: + if( enable_disable_key( keyblock, cmd == cmdDISABLEKEY ) ) { + redisplay = 1; + modified = 1; + } + break; + + case cmdSHOWPHOTO: + menu_showphoto(keyblock); + break; + + case cmdCLEAN: + redisplay=modified=menu_clean(keyblock,0); + break; + + case cmdMINIMIZE: + redisplay=modified=menu_clean(keyblock,1); + break; + + case cmdQUIT: + if( have_commands ) + goto leave; + if( !modified && !sec_modified ) + goto leave; + if( !cpr_get_answer_is_yes("keyedit.save.okay", + _("Save changes? (y/N) ")) ) { + if( cpr_enabled() + || cpr_get_answer_is_yes("keyedit.cancel.okay", + _("Quit without saving? (y/N) "))) + goto leave; + break; + } + /* fall thru */ + case cmdSAVE: + if( modified || sec_modified ) { + if( modified ) { + rc = keydb_update_keyblock (kdbhd, keyblock); + if( rc ) { + log_error(_("update failed: %s\n"), g10_errstr(rc) ); + break; + } + } + if( sec_modified ) { + rc = keydb_update_keyblock (sec_kdbhd, sec_keyblock ); + if( rc ) { + log_error( _("update secret failed: %s\n"), + g10_errstr(rc) ); + break; + } + } + } + else + tty_printf(_("Key not changed so no update needed.\n")); + + if( update_trust ) + { + revalidation_mark (); + update_trust=0; + } + goto leave; + + case cmdINVCMD: + default: + tty_printf("\n"); + tty_printf(_("Invalid command (try \"help\")\n")); + break; + } + } /* end main loop */ + + leave: + release_kbnode( keyblock ); + release_kbnode( sec_keyblock ); + keydb_release (kdbhd); + xfree(answer); +} + +static void +tty_print_notations(int indent,PKT_signature *sig) +{ + int first=1; + struct notation *notation,*nd; + + if(indent<0) + { + first=0; + indent=-indent; + } + + notation=sig_to_notation(sig); + + for(nd=notation;nd;nd=nd->next) + { + if(!first) + tty_printf("%*s",indent,""); + else + first=0; + + tty_print_utf8_string(nd->name,strlen(nd->name)); + tty_printf("="); + tty_print_utf8_string(nd->value,strlen(nd->value)); + tty_printf("\n"); + } + + free_notation(notation); +} + +/**************** + * show preferences of a public keyblock. + */ +static void +show_prefs (PKT_user_id *uid, PKT_signature *selfsig, int verbose) +{ + const prefitem_t fake={0,0}; + const prefitem_t *prefs; + int i; + + if( !uid ) + return; + + if( uid->prefs ) + prefs=uid->prefs; + else if(verbose) + prefs=&fake; + else + return; + + if (verbose) { + int any, des_seen=0, sha1_seen=0, uncomp_seen=0; + + tty_printf (" "); + tty_printf (_("Cipher: ")); + for(i=any=0; prefs[i].type; i++ ) { + if( prefs[i].type == PREFTYPE_SYM ) { + const char *s = cipher_algo_to_string (prefs[i].value); + + if (any) + tty_printf (", "); + any = 1; + /* We don't want to display strings for experimental algos */ + if (s && prefs[i].value < 100 ) + tty_printf ("%s", s ); + else + tty_printf ("[%d]", prefs[i].value); + if (prefs[i].value == CIPHER_ALGO_3DES ) + des_seen = 1; + } + } + if (!des_seen) { + if (any) + tty_printf (", "); + tty_printf ("%s",cipher_algo_to_string(CIPHER_ALGO_3DES)); + } + tty_printf ("\n "); + tty_printf (_("Digest: ")); + for(i=any=0; prefs[i].type; i++ ) { + if( prefs[i].type == PREFTYPE_HASH ) { + const char *s = digest_algo_to_string (prefs[i].value); + + if (any) + tty_printf (", "); + any = 1; + /* We don't want to display strings for experimental algos */ + if (s && prefs[i].value < 100 ) + tty_printf ("%s", s ); + else + tty_printf ("[%d]", prefs[i].value); + if (prefs[i].value == DIGEST_ALGO_SHA1 ) + sha1_seen = 1; + } + } + if (!sha1_seen) { + if (any) + tty_printf (", "); + tty_printf ("%s",digest_algo_to_string(DIGEST_ALGO_SHA1)); + } + tty_printf ("\n "); + tty_printf (_("Compression: ")); + for(i=any=0; prefs[i].type; i++ ) { + if( prefs[i].type == PREFTYPE_ZIP ) { + const char *s=compress_algo_to_string(prefs[i].value); + + if (any) + tty_printf (", "); + any = 1; + /* We don't want to display strings for experimental algos */ + if (s && prefs[i].value < 100 ) + tty_printf ("%s", s ); + else + tty_printf ("[%d]", prefs[i].value); + if (prefs[i].value == COMPRESS_ALGO_NONE ) + uncomp_seen = 1; + } + } + if (!uncomp_seen) { + if (any) + tty_printf (", "); + else { + tty_printf ("%s",compress_algo_to_string(COMPRESS_ALGO_ZIP)); + tty_printf (", "); + } + tty_printf ("%s",compress_algo_to_string(COMPRESS_ALGO_NONE)); + } + if(uid->flags.mdc || !uid->flags.ks_modify) + { + tty_printf ("\n "); + tty_printf (_("Features: ")); + any=0; + if(uid->flags.mdc) + { + tty_printf ("MDC"); + any=1; + } + if(!uid->flags.ks_modify) + { + if(any) + tty_printf (", "); + tty_printf (_("Keyserver no-modify")); + } + } + tty_printf("\n"); + + if(selfsig) + { + const byte *pref_ks; + size_t pref_ks_len; + + pref_ks=parse_sig_subpkt(selfsig->hashed, + SIGSUBPKT_PREF_KS,&pref_ks_len); + if(pref_ks && pref_ks_len) + { + tty_printf (" "); + tty_printf(_("Preferred keyserver: ")); + tty_print_utf8_string(pref_ks,pref_ks_len); + tty_printf("\n"); + } + + if(selfsig->flags.notation) + { + tty_printf (" "); + tty_printf(_("Notations: ")); + tty_print_notations(5+strlen(_("Notations: ")),selfsig); + } + } + } + else { + tty_printf(" "); + for(i=0; prefs[i].type; i++ ) { + tty_printf( " %c%d", prefs[i].type == PREFTYPE_SYM ? 'S' : + prefs[i].type == PREFTYPE_HASH ? 'H' : + prefs[i].type == PREFTYPE_ZIP ? 'Z':'?', + prefs[i].value); + } + if (uid->flags.mdc) + tty_printf (" [mdc]"); + if (!uid->flags.ks_modify) + tty_printf (" [no-ks-modify]"); + tty_printf("\n"); + } +} + +/* This is the version of show_key_with_all_names used when + opt.with_colons is used. It prints all available data in a easy to + parse format and does not translate utf8 */ +static void +show_key_with_all_names_colon (KBNODE keyblock) +{ + KBNODE node; + int i, j, ulti_hack=0; + byte pk_version=0; + PKT_public_key *primary=NULL; + + /* the keys */ + for ( node = keyblock; node; node = node->next ) + { + if (node->pkt->pkttype == PKT_PUBLIC_KEY + || (node->pkt->pkttype == PKT_PUBLIC_SUBKEY) ) + { + PKT_public_key *pk = node->pkt->pkt.public_key; + u32 keyid[2]; + + if (node->pkt->pkttype == PKT_PUBLIC_KEY) + { + pk_version = pk->version; + primary=pk; + } + + keyid_from_pk (pk, keyid); + + fputs (node->pkt->pkttype == PKT_PUBLIC_KEY?"pub:":"sub:", stdout); + if (!pk->is_valid) + putchar ('i'); + else if (pk->is_revoked) + putchar ('r'); + else if (pk->has_expired) + putchar ('e'); + else if (!(opt.fast_list_mode || opt.no_expensive_trust_checks )) + { + int trust = get_validity_info (pk, NULL); + if(trust=='u') + ulti_hack=1; + putchar (trust); + } + + printf (":%u:%d:%08lX%08lX:%lu:%lu::", + nbits_from_pk (pk), + pk->pubkey_algo, + (ulong)keyid[0], (ulong)keyid[1], + (ulong)pk->timestamp, + (ulong)pk->expiredate ); + if (node->pkt->pkttype==PKT_PUBLIC_KEY + && !(opt.fast_list_mode || opt.no_expensive_trust_checks )) + putchar(get_ownertrust_info (pk)); + putchar(':'); + putchar('\n'); + + print_fingerprint (pk, NULL, 0); + print_revokers(pk); + } + } + + /* the user ids */ + i = 0; + for (node = keyblock; node; node = node->next) + { + if ( node->pkt->pkttype == PKT_USER_ID ) + { + PKT_user_id *uid = node->pkt->pkt.user_id; + + ++i; + + if(uid->attrib_data) + printf("uat:"); + else + printf("uid:"); + + if ( uid->is_revoked ) + printf("r::::::::"); + else if ( uid->is_expired ) + printf("e::::::::"); + else if ( opt.fast_list_mode || opt.no_expensive_trust_checks ) + printf("::::::::"); + else + { + int uid_validity; + + if( primary && !ulti_hack ) + uid_validity = get_validity_info( primary, uid ); + else + uid_validity = 'u'; + printf("%c::::::::",uid_validity); + } + + if(uid->attrib_data) + printf ("%u %lu",uid->numattribs,uid->attrib_len); + else + print_string (stdout, uid->name, uid->len, ':'); + + putchar (':'); + /* signature class */ + putchar (':'); + /* capabilities */ + putchar (':'); + /* preferences */ + if (pk_version>3 || uid->selfsigversion>3) + { + const prefitem_t *prefs = uid->prefs; + + for (j=0; prefs && prefs[j].type; j++) + { + if (j) + putchar (' '); + printf ("%c%d", prefs[j].type == PREFTYPE_SYM ? 'S' : + prefs[j].type == PREFTYPE_HASH ? 'H' : + prefs[j].type == PREFTYPE_ZIP ? 'Z':'?', + prefs[j].value); + } + if (uid->flags.mdc) + printf (",mdc"); + if (!uid->flags.ks_modify) + printf (",no-ks-modify"); + } + putchar (':'); + /* flags */ + printf ("%d,", i); + if (uid->is_primary) + putchar ('p'); + if (uid->is_revoked) + putchar ('r'); + if (uid->is_expired) + putchar ('e'); + if ((node->flag & NODFLG_SELUID)) + putchar ('s'); + if ((node->flag & NODFLG_MARK_A)) + putchar ('m'); + putchar (':'); + putchar('\n'); + } + } +} + +static void +show_names(KBNODE keyblock,PKT_public_key *pk,unsigned int flag,int with_prefs) +{ + KBNODE node; + int i=0; + + for( node = keyblock; node; node = node->next ) + { + if( node->pkt->pkttype == PKT_USER_ID + && !is_deleted_kbnode(node)) + { + PKT_user_id *uid = node->pkt->pkt.user_id; + ++i; + if(!flag || (flag && (node->flag & flag))) + { + if(!(flag&NODFLG_MARK_A) && pk) + tty_printf("%s ",uid_trust_string_fixed(pk,uid)); + + if( flag & NODFLG_MARK_A ) + tty_printf(" "); + else if( node->flag & NODFLG_SELUID ) + tty_printf("(%d)* ", i); + else if( uid->is_primary ) + tty_printf("(%d). ", i); + else + tty_printf("(%d) ", i); + tty_print_utf8_string( uid->name, uid->len ); + tty_printf("\n"); + if(with_prefs && pk) + { + if(pk->version>3 || uid->selfsigversion>3) + { + PKT_signature *selfsig=NULL; + KBNODE signode; + + for(signode=node->next; + signode && signode->pkt->pkttype==PKT_SIGNATURE; + signode=signode->next) + { + if(signode->pkt->pkt.signature-> + flags.chosen_selfsig) + { + selfsig=signode->pkt->pkt.signature; + break; + } + } + + show_prefs (uid, selfsig, with_prefs == 2); + } + else + tty_printf(_("There are no preferences on a" + " PGP 2.x-style user ID.\n")); + } + } + } + } +} + +/**************** + * Display the key a the user ids, if only_marked is true, do only + * so for user ids with mark A flag set and dont display the index number + */ +static void +show_key_with_all_names( KBNODE keyblock, int only_marked, int with_revoker, + int with_fpr, int with_subkeys, int with_prefs ) +{ + KBNODE node; + int i; + int do_warn = 0; + byte pk_version=0; + PKT_public_key *primary=NULL; + + if (opt.with_colons) + { + show_key_with_all_names_colon (keyblock); + return; + } + + /* the keys */ + for( node = keyblock; node; node = node->next ) { + if( node->pkt->pkttype == PKT_PUBLIC_KEY + || (with_subkeys && node->pkt->pkttype == PKT_PUBLIC_SUBKEY + && !is_deleted_kbnode(node)) ) { + PKT_public_key *pk = node->pkt->pkt.public_key; + const char *otrust="err",*trust="err"; + + if( node->pkt->pkttype == PKT_PUBLIC_KEY ) { + /* do it here, so that debug messages don't clutter the + * output */ + static int did_warn = 0; + + trust = get_validity_string (pk, NULL); + otrust = get_ownertrust_string (pk); + + /* Show a warning once */ + if (!did_warn + && (get_validity (pk, NULL) & TRUST_FLAG_PENDING_CHECK)) { + did_warn = 1; + do_warn = 1; + } + + pk_version=pk->version; + primary=pk; + } + + if(pk->is_revoked) + { + char *user=get_user_id_string_native(pk->revoked.keyid); + const char *algo=pubkey_algo_to_string(pk->revoked.algo); + tty_printf(_("This key was revoked on %s by %s key %s\n"), + revokestr_from_pk(pk),algo?algo:"?",user); + xfree(user); + } + + if(with_revoker) + { + if( !pk->revkey && pk->numrevkeys ) + BUG(); + else + for(i=0;i<pk->numrevkeys;i++) + { + u32 r_keyid[2]; + char *user; + const char *algo= + pubkey_algo_to_string(pk->revkey[i].algid); + + keyid_from_fingerprint(pk->revkey[i].fpr, + MAX_FINGERPRINT_LEN,r_keyid); + + user=get_user_id_string_native(r_keyid); + tty_printf(_("This key may be revoked by %s key %s"), + algo?algo:"?",user); + + if(pk->revkey[i].class&0x40) + { + tty_printf(" "); + tty_printf(_("(sensitive)")); + } + + tty_printf ("\n"); + xfree(user); + } + } + + keyid_from_pk(pk,NULL); + tty_printf("%s%c %4u%c/%s ", + node->pkt->pkttype == PKT_PUBLIC_KEY? "pub":"sub", + (node->flag & NODFLG_SELKEY)? '*':' ', + nbits_from_pk( pk ), + pubkey_letter( pk->pubkey_algo ), + keystr(pk->keyid)); + + tty_printf(_("created: %s"),datestr_from_pk(pk)); + tty_printf(" "); + if(pk->is_revoked) + tty_printf(_("revoked: %s"),revokestr_from_pk(pk)); + else if(pk->has_expired) + tty_printf(_("expired: %s"),expirestr_from_pk(pk)); + else + tty_printf(_("expires: %s"),expirestr_from_pk(pk)); + tty_printf(" "); + tty_printf(_("usage: %s"),usagestr_from_pk(pk)); + tty_printf("\n"); + + if( node->pkt->pkttype == PKT_PUBLIC_KEY ) + { + if(opt.trust_model!=TM_ALWAYS) + { + tty_printf("%*s", (int)keystrlen()+13,""); + /* Ownertrust is only meaningful for the PGP or + classic trust models */ + if(opt.trust_model==TM_PGP || opt.trust_model==TM_CLASSIC) + { + int width=14-strlen(otrust); + if(width<=0) + width=1; + tty_printf(_("trust: %s"), otrust); + tty_printf("%*s",width,""); + } + + tty_printf(_("validity: %s"), trust ); + tty_printf("\n"); + } + if( node->pkt->pkttype == PKT_PUBLIC_KEY + && (get_ownertrust (pk)&TRUST_FLAG_DISABLED)) + { + tty_printf("*** "); + tty_printf(_("This key has been disabled")); + tty_printf("\n"); + } + } + + if( node->pkt->pkttype == PKT_PUBLIC_KEY && with_fpr ) + { + print_fingerprint ( pk, NULL, 2 ); + tty_printf("\n"); + } + } + else if( node->pkt->pkttype == PKT_SECRET_KEY + || (with_subkeys && node->pkt->pkttype == PKT_SECRET_SUBKEY) ) + { + PKT_secret_key *sk = node->pkt->pkt.secret_key; + tty_printf("%s%c %4u%c/%s ", + node->pkt->pkttype == PKT_SECRET_KEY? "sec":"ssb", + (node->flag & NODFLG_SELKEY)? '*':' ', + nbits_from_sk( sk ), + pubkey_letter( sk->pubkey_algo ), + keystr_from_sk(sk)); + tty_printf(_("created: %s"),datestr_from_sk(sk)); + tty_printf(" "); + tty_printf(_("expires: %s"),expirestr_from_sk(sk)); + tty_printf("\n"); + if (sk->is_protected && sk->protect.s2k.mode == 1002) + { + tty_printf(" "); + tty_printf(_("card-no: ")); + if (sk->protect.ivlen == 16 + && !memcmp (sk->protect.iv, "\xD2\x76\x00\x01\x24\x01", 6)) + { /* This is an OpenPGP card. */ + for (i=8; i < 14; i++) + { + if (i == 10) + tty_printf (" "); + tty_printf ("%02X", sk->protect.iv[i]); + } + } + else + { /* Something is wrong: Print all. */ + for (i=0; i < sk->protect.ivlen; i++) + tty_printf ("%02X", sk->protect.iv[i]); + } + tty_printf ("\n"); + } + } + } + + show_names(keyblock,primary,only_marked?NODFLG_MARK_A:0,with_prefs); + + if (do_warn) + tty_printf (_("Please note that the shown key validity" + " is not necessarily correct\n" + "unless you restart the program.\n")); +} + + +/* Display basic key information. This function is suitable to show + information on the key without any dependencies on the trustdb or + any other internal GnuPG stuff. KEYBLOCK may either be a public or + a secret key.*/ +void +show_basic_key_info ( KBNODE keyblock ) +{ + KBNODE node; + int i; + + /* The primary key */ + for (node = keyblock; node; node = node->next) + { + if (node->pkt->pkttype == PKT_PUBLIC_KEY) + { + PKT_public_key *pk = node->pkt->pkt.public_key; + + /* Note, we use the same format string as in other show + functions to make the translation job easier. */ + tty_printf ("%s %4u%c/%s ", + node->pkt->pkttype == PKT_PUBLIC_KEY? "pub":"sub", + nbits_from_pk( pk ), + pubkey_letter( pk->pubkey_algo ), + keystr_from_pk(pk)); + tty_printf(_("created: %s"),datestr_from_pk(pk)); + tty_printf(" "); + tty_printf(_("expires: %s"),expirestr_from_pk(pk)); + tty_printf("\n"); + print_fingerprint ( pk, NULL, 3 ); + tty_printf("\n"); + } + else if (node->pkt->pkttype == PKT_SECRET_KEY) + { + PKT_secret_key *sk = node->pkt->pkt.secret_key; + tty_printf("%s %4u%c/%s", + node->pkt->pkttype == PKT_SECRET_KEY? "sec":"ssb", + nbits_from_sk( sk ), + pubkey_letter( sk->pubkey_algo ), + keystr_from_sk(sk)); + tty_printf(_("created: %s"),datestr_from_sk(sk)); + tty_printf(" "); + tty_printf(_("expires: %s"),expirestr_from_sk(sk)); + tty_printf("\n"); + print_fingerprint (NULL, sk, 3 ); + tty_printf("\n"); + } + } + + /* The user IDs. */ + for (i=0, node = keyblock; node; node = node->next) + { + if (node->pkt->pkttype == PKT_USER_ID) + { + PKT_user_id *uid = node->pkt->pkt.user_id; + ++i; + + tty_printf (" "); + if (uid->is_revoked) + tty_printf("[%s] ",_("revoked")); + else if ( uid->is_expired ) + tty_printf("[%s] ",_("expired")); + tty_print_utf8_string (uid->name, uid->len); + tty_printf ("\n"); + } + } +} + +static void +show_key_and_fingerprint( KBNODE keyblock ) +{ + KBNODE node; + PKT_public_key *pk = NULL; + + for( node = keyblock; node; node = node->next ) + { + if( node->pkt->pkttype == PKT_PUBLIC_KEY ) + { + pk = node->pkt->pkt.public_key; + tty_printf("pub %4u%c/%s %s ", + nbits_from_pk( pk ), + pubkey_letter( pk->pubkey_algo ), + keystr_from_pk(pk), + datestr_from_pk(pk) ); + } + else if( node->pkt->pkttype == PKT_USER_ID ) + { + PKT_user_id *uid = node->pkt->pkt.user_id; + tty_print_utf8_string( uid->name, uid->len ); + break; + } + } + tty_printf("\n"); + if( pk ) + print_fingerprint( pk, NULL, 2 ); +} + + +/* Show a warning if no uids on the key have the primary uid flag + set. */ +static void +no_primary_warning(KBNODE keyblock) +{ + KBNODE node; + int have_primary=0,uid_count=0; + + /* TODO: if we ever start behaving differently with a primary or + non-primary attribute ID, we will need to check for attributes + here as well. */ + + for(node=keyblock; node; node = node->next) + { + if(node->pkt->pkttype==PKT_USER_ID + && node->pkt->pkt.user_id->attrib_data==NULL) + { + uid_count++; + + if(node->pkt->pkt.user_id->is_primary==2) + { + have_primary=1; + break; + } + } + } + + if(uid_count>1 && !have_primary) + log_info(_("WARNING: no user ID has been marked as primary. This command" + " may\n cause a different user ID to become" + " the assumed primary.\n")); +} + +/**************** + * Ask for a new user id, do the selfsignature and put it into + * both keyblocks. + * Return true if there is a new user id + */ +static int +menu_adduid( KBNODE pub_keyblock, KBNODE sec_keyblock, + int photo, const char *photo_name) +{ + PKT_user_id *uid; + PKT_public_key *pk=NULL; + PKT_secret_key *sk=NULL; + PKT_signature *sig=NULL; + PACKET *pkt; + KBNODE node; + KBNODE pub_where=NULL, sec_where=NULL; + int rc; + + for( node = pub_keyblock; node; pub_where = node, node = node->next ) { + if( node->pkt->pkttype == PKT_PUBLIC_KEY ) + pk = node->pkt->pkt.public_key; + else if( node->pkt->pkttype == PKT_PUBLIC_SUBKEY ) + break; + } + if( !node ) /* no subkey */ + pub_where = NULL; + for( node = sec_keyblock; node; sec_where = node, node = node->next ) { + if( node->pkt->pkttype == PKT_SECRET_KEY ) + sk = copy_secret_key( NULL, node->pkt->pkt.secret_key); + else if( node->pkt->pkttype == PKT_SECRET_SUBKEY ) + break; + } + if( !node ) /* no subkey */ + sec_where = NULL; + assert(pk && sk); + + if(photo) { + int hasattrib=0; + + for( node = pub_keyblock; node; node = node->next ) + if( node->pkt->pkttype == PKT_USER_ID && + node->pkt->pkt.user_id->attrib_data!=NULL) + { + hasattrib=1; + break; + } + + /* It is legal but bad for compatibility to add a photo ID to a + v3 key as it means that PGP2 will not be able to use that key + anymore. Also, PGP may not expect a photo on a v3 key. + Don't bother to ask this if the key already has a photo - any + damage has already been done at that point. -dms */ + if(pk->version==3 && !hasattrib) + { + if(opt.expert) + { + tty_printf(_("WARNING: This is a PGP2-style key. " + "Adding a photo ID may cause some versions\n" + " of PGP to reject this key.\n")); + + if(!cpr_get_answer_is_yes("keyedit.v3_photo.okay", + _("Are you sure you still want " + "to add it? (y/N) "))) + return 0; + } + else + { + tty_printf(_("You may not add a photo ID to " + "a PGP2-style key.\n")); + return 0; + } + } + + uid = generate_photo_id(pk,photo_name); + } else + uid = generate_user_id(); + if( !uid ) + return 0; + + rc = make_keysig_packet( &sig, pk, uid, NULL, sk, 0x13, 0, 0, 0, 0, + keygen_add_std_prefs, pk ); + free_secret_key( sk ); + if( rc ) { + log_error("signing failed: %s\n", g10_errstr(rc) ); + free_user_id(uid); + return 0; + } + + /* insert/append to secret keyblock */ + pkt = xmalloc_clear( sizeof *pkt ); + pkt->pkttype = PKT_USER_ID; + pkt->pkt.user_id = scopy_user_id(uid); + node = new_kbnode(pkt); + if( sec_where ) + insert_kbnode( sec_where, node, 0 ); + else + add_kbnode( sec_keyblock, node ); + pkt = xmalloc_clear( sizeof *pkt ); + pkt->pkttype = PKT_SIGNATURE; + pkt->pkt.signature = copy_signature(NULL, sig); + if( sec_where ) + insert_kbnode( node, new_kbnode(pkt), 0 ); + else + add_kbnode( sec_keyblock, new_kbnode(pkt) ); + /* insert/append to public keyblock */ + pkt = xmalloc_clear( sizeof *pkt ); + pkt->pkttype = PKT_USER_ID; + pkt->pkt.user_id = uid; + node = new_kbnode(pkt); + if( pub_where ) + insert_kbnode( pub_where, node, 0 ); + else + add_kbnode( pub_keyblock, node ); + pkt = xmalloc_clear( sizeof *pkt ); + pkt->pkttype = PKT_SIGNATURE; + pkt->pkt.signature = copy_signature(NULL, sig); + if( pub_where ) + insert_kbnode( node, new_kbnode(pkt), 0 ); + else + add_kbnode( pub_keyblock, new_kbnode(pkt) ); + return 1; +} + + +/**************** + * Remove all selected userids from the keyrings + */ +static void +menu_deluid( KBNODE pub_keyblock, KBNODE sec_keyblock ) +{ + KBNODE node; + int selected=0; + + for( node = pub_keyblock; node; node = node->next ) { + if( node->pkt->pkttype == PKT_USER_ID ) { + selected = node->flag & NODFLG_SELUID; + if( selected ) { + /* Only cause a trust update if we delete a + non-revoked user id */ + if(!node->pkt->pkt.user_id->is_revoked) + update_trust=1; + delete_kbnode( node ); + if( sec_keyblock ) { + KBNODE snode; + int s_selected = 0; + PKT_user_id *uid = node->pkt->pkt.user_id; + for( snode = sec_keyblock; snode; snode = snode->next ) { + if( snode->pkt->pkttype == PKT_USER_ID ) { + PKT_user_id *suid = snode->pkt->pkt.user_id; + + s_selected = + (uid->len == suid->len + && !memcmp( uid->name, suid->name, uid->len)); + if( s_selected ) + delete_kbnode( snode ); + } + else if( s_selected + && snode->pkt->pkttype == PKT_SIGNATURE ) + delete_kbnode( snode ); + else if( snode->pkt->pkttype == PKT_SECRET_SUBKEY ) + s_selected = 0; + } + } + } + } + else if( selected && node->pkt->pkttype == PKT_SIGNATURE ) + delete_kbnode( node ); + else if( node->pkt->pkttype == PKT_PUBLIC_SUBKEY ) + selected = 0; + } + commit_kbnode( &pub_keyblock ); + if( sec_keyblock ) + commit_kbnode( &sec_keyblock ); +} + + +static int +menu_delsig( KBNODE pub_keyblock ) +{ + KBNODE node; + PKT_user_id *uid = NULL; + int changed=0; + + for( node = pub_keyblock; node; node = node->next ) { + if( node->pkt->pkttype == PKT_USER_ID ) { + uid = (node->flag & NODFLG_SELUID)? node->pkt->pkt.user_id : NULL; + } + else if( uid && node->pkt->pkttype == PKT_SIGNATURE ) { + int okay, valid, selfsig, inv_sig, no_key, other_err; + + tty_printf("uid "); + tty_print_utf8_string( uid->name, uid->len ); + tty_printf("\n"); + + okay = inv_sig = no_key = other_err = 0; + if(opt.with_colons) + valid = print_and_check_one_sig_colon( pub_keyblock, node, + &inv_sig, &no_key, &other_err, + &selfsig, 1 ); + else + valid = print_and_check_one_sig( pub_keyblock, node, + &inv_sig, &no_key, &other_err, + &selfsig, 1 ); + + if( valid ) { + okay = cpr_get_answer_yes_no_quit( + "keyedit.delsig.valid", + _("Delete this good signature? (y/N/q)")); + + /* Only update trust if we delete a good signature. + The other two cases do not affect trust. */ + if(okay) + update_trust=1; + } + else if( inv_sig || other_err ) + okay = cpr_get_answer_yes_no_quit( + "keyedit.delsig.invalid", + _("Delete this invalid signature? (y/N/q)")); + else if( no_key ) + okay = cpr_get_answer_yes_no_quit( + "keyedit.delsig.unknown", + _("Delete this unknown signature? (y/N/q)")); + + if( okay == -1 ) + break; + if( okay && selfsig && !cpr_get_answer_is_yes( + "keyedit.delsig.selfsig", + _("Really delete this self-signature? (y/N)") )) + okay = 0; + if( okay ) { + delete_kbnode( node ); + changed++; + } + + } + else if( node->pkt->pkttype == PKT_PUBLIC_SUBKEY ) + uid = NULL; + } + + if( changed ) { + commit_kbnode( &pub_keyblock ); + tty_printf( changed == 1? _("Deleted %d signature.\n") + : _("Deleted %d signatures.\n"), changed ); + } + else + tty_printf( _("Nothing deleted.\n") ); + + return changed; +} + +static int +menu_clean(KBNODE keyblock,int self_only) +{ + KBNODE uidnode; + int modified=0,select_all=!count_selected_uids(keyblock); + + for(uidnode=keyblock->next; + uidnode && uidnode->pkt->pkttype!=PKT_PUBLIC_SUBKEY; + uidnode=uidnode->next) + { + if(uidnode->pkt->pkttype==PKT_USER_ID + && (uidnode->flag&NODFLG_SELUID || select_all)) + { + int uids=0,sigs=0; + char *user=utf8_to_native(uidnode->pkt->pkt.user_id->name, + uidnode->pkt->pkt.user_id->len, + 0); + + clean_one_uid(keyblock,uidnode,opt.verbose,self_only,&uids,&sigs); + if(uids) + { + const char *reason; + + if(uidnode->pkt->pkt.user_id->is_revoked) + reason=_("revoked"); + else if(uidnode->pkt->pkt.user_id->is_expired) + reason=_("expired"); + else + reason=_("invalid"); + + tty_printf (_("User ID \"%s\" compacted: %s\n"), user, reason); + + modified=1; + } + else if(sigs) + { + tty_printf (sigs==1? + _("User ID \"%s\": %d signature removed\n"): + _("User ID \"%s\": %d signatures removed\n"), + user,sigs); + + modified=1; + } + else + { + tty_printf (self_only==1? + _("User ID \"%s\": already minimized\n"): + _("User ID \"%s\": already clean\n"), + user); + } + + xfree(user); + } + } + + return modified; +} + +/**************** + * Remove some of the secondary keys + */ +static void +menu_delkey( KBNODE pub_keyblock, KBNODE sec_keyblock ) +{ + KBNODE node; + int selected=0; + + for( node = pub_keyblock; node; node = node->next ) { + if( node->pkt->pkttype == PKT_PUBLIC_SUBKEY ) { + selected = node->flag & NODFLG_SELKEY; + if( selected ) { + delete_kbnode( node ); + if( sec_keyblock ) { + KBNODE snode; + int s_selected = 0; + u32 ki[2]; + + keyid_from_pk( node->pkt->pkt.public_key, ki ); + for( snode = sec_keyblock; snode; snode = snode->next ) { + if( snode->pkt->pkttype == PKT_SECRET_SUBKEY ) { + u32 ki2[2]; + + keyid_from_sk( snode->pkt->pkt.secret_key, ki2 ); + s_selected = (ki[0] == ki2[0] && ki[1] == ki2[1]); + if( s_selected ) + delete_kbnode( snode ); + } + else if( s_selected + && snode->pkt->pkttype == PKT_SIGNATURE ) + delete_kbnode( snode ); + else + s_selected = 0; + } + } + } + } + else if( selected && node->pkt->pkttype == PKT_SIGNATURE ) + delete_kbnode( node ); + else + selected = 0; + } + commit_kbnode( &pub_keyblock ); + if( sec_keyblock ) + commit_kbnode( &sec_keyblock ); + + /* No need to set update_trust here since signing keys are no + longer used to certify other keys, so there is no change in + trust when revoking/removing them */ +} + + +/**************** + * Ask for a new revoker, do the selfsignature and put it into + * both keyblocks. + * Return true if there is a new revoker + */ +static int +menu_addrevoker( KBNODE pub_keyblock, KBNODE sec_keyblock, int sensitive ) +{ + PKT_public_key *pk=NULL,*revoker_pk=NULL; + PKT_secret_key *sk=NULL; + PKT_signature *sig=NULL; + PACKET *pkt; + struct revocation_key revkey; + size_t fprlen; + int rc; + + assert(pub_keyblock->pkt->pkttype==PKT_PUBLIC_KEY); + assert(sec_keyblock->pkt->pkttype==PKT_SECRET_KEY); + + pk=pub_keyblock->pkt->pkt.public_key; + + if(pk->numrevkeys==0 && pk->version==3) + { + /* It is legal but bad for compatibility to add a revoker to a + v3 key as it means that PGP2 will not be able to use that key + anymore. Also, PGP may not expect a revoker on a v3 key. + Don't bother to ask this if the key already has a revoker - + any damage has already been done at that point. -dms */ + if(opt.expert) + { + tty_printf(_("WARNING: This is a PGP 2.x-style key. " + "Adding a designated revoker may cause\n" + " some versions of PGP to reject this key.\n")); + + if(!cpr_get_answer_is_yes("keyedit.v3_revoker.okay", + _("Are you sure you still want " + "to add it? (y/N) "))) + return 0; + } + else + { + tty_printf(_("You may not add a designated revoker to " + "a PGP 2.x-style key.\n")); + return 0; + } + } + + sk=copy_secret_key(NULL,sec_keyblock->pkt->pkt.secret_key); + + for(;;) + { + char *answer; + + if(revoker_pk) + free_public_key(revoker_pk); + + revoker_pk=xmalloc_clear(sizeof(*revoker_pk)); + + tty_printf("\n"); + + answer=cpr_get_utf8("keyedit.add_revoker", + _("Enter the user ID of the designated revoker: ")); + if(answer[0]=='\0' || answer[0]=='\004') + { + xfree(answer); + goto fail; + } + + /* Note that I'm requesting CERT here, which usually implies + primary keys only, but some casual testing shows that PGP and + GnuPG both can handle a designated revokation from a + subkey. */ + revoker_pk->req_usage=PUBKEY_USAGE_CERT; + rc=get_pubkey_byname(revoker_pk,answer,NULL,NULL,1); + if(rc) + { + log_error (_("key \"%s\" not found: %s\n"),answer,g10_errstr(rc)); + xfree(answer); + continue; + } + + xfree(answer); + + fingerprint_from_pk(revoker_pk,revkey.fpr,&fprlen); + if(fprlen!=20) + { + log_error(_("cannot appoint a PGP 2.x style key as a " + "designated revoker\n")); + continue; + } + + revkey.class=0x80; + if(sensitive) + revkey.class|=0x40; + revkey.algid=revoker_pk->pubkey_algo; + + if(cmp_public_keys(revoker_pk,pk)==0) + { + /* This actually causes no harm (after all, a key that + designates itself as a revoker is the same as a + regular key), but it's easy enough to check. */ + log_error(_("you cannot appoint a key as its own " + "designated revoker\n")); + + continue; + } + + keyid_from_pk(pk,NULL); + + /* Does this revkey already exist? */ + if(!pk->revkey && pk->numrevkeys) + BUG(); + else + { + int i; + + for(i=0;i<pk->numrevkeys;i++) + { + if(memcmp(&pk->revkey[i],&revkey, + sizeof(struct revocation_key))==0) + { + char buf[50]; + + log_error(_("this key has already been designated " + "as a revoker\n")); + + sprintf(buf,"%08lX%08lX", + (ulong)pk->keyid[0],(ulong)pk->keyid[1]); + write_status_text(STATUS_ALREADY_SIGNED,buf); + + break; + } + } + + if(i<pk->numrevkeys) + continue; + } + + print_pubkey_info(NULL,revoker_pk); + print_fingerprint(revoker_pk,NULL,2); + tty_printf("\n"); + + tty_printf(_("WARNING: appointing a key as a designated revoker " + "cannot be undone!\n")); + + tty_printf("\n"); + + if(!cpr_get_answer_is_yes("keyedit.add_revoker.okay", + _("Are you sure you want to appoint this " + "key as a designated revoker? (y/N) "))) + continue; + + free_public_key(revoker_pk); + revoker_pk=NULL; + break; + } + + /* The 1F signature must be at least v4 to carry the revocation key + subpacket. */ + rc = make_keysig_packet( &sig, pk, NULL, NULL, sk, 0x1F, 0, 4, 0, 0, + keygen_add_revkey,&revkey ); + if( rc ) + { + log_error("signing failed: %s\n", g10_errstr(rc) ); + goto fail; + } + + free_secret_key(sk); + sk=NULL; + + /* insert into secret keyblock */ + pkt = xmalloc_clear( sizeof *pkt ); + pkt->pkttype = PKT_SIGNATURE; + pkt->pkt.signature = copy_signature(NULL, sig); + insert_kbnode( sec_keyblock, new_kbnode(pkt), PKT_SIGNATURE ); + + /* insert into public keyblock */ + pkt = xmalloc_clear( sizeof *pkt ); + pkt->pkttype = PKT_SIGNATURE; + pkt->pkt.signature = sig; + insert_kbnode( pub_keyblock, new_kbnode(pkt), PKT_SIGNATURE ); + + return 1; + + fail: + if(sk) + free_secret_key(sk); + if(sig) + free_seckey_enc(sig); + if(revoker_pk) + free_public_key(revoker_pk); + + return 0; +} + + +static int +menu_expire( KBNODE pub_keyblock, KBNODE sec_keyblock ) +{ + int n1, signumber, rc; + u32 expiredate; + int mainkey=0; + PKT_secret_key *sk; /* copy of the main sk */ + PKT_public_key *main_pk, *sub_pk; + PKT_user_id *uid; + KBNODE node; + u32 keyid[2]; + + if( count_selected_keys( sec_keyblock ) ) { + tty_printf(_("Please remove selections from the secret keys.\n")); + return 0; + } + + n1 = count_selected_keys( pub_keyblock ); + if( n1 > 1 ) { + tty_printf(_("Please select at most one subkey.\n")); + return 0; + } + else if( n1 ) + tty_printf(_("Changing expiration time for a subkey.\n")); + else + { + tty_printf(_("Changing expiration time for the primary key.\n")); + mainkey=1; + no_primary_warning(pub_keyblock); + } + + expiredate = ask_expiredate(); + node = find_kbnode( sec_keyblock, PKT_SECRET_KEY ); + sk = copy_secret_key( NULL, node->pkt->pkt.secret_key); + + /* Now we can actually change the self signature(s) */ + main_pk = sub_pk = NULL; + uid = NULL; + signumber = 0; + for( node=pub_keyblock; node; node = node->next ) { + if( node->pkt->pkttype == PKT_PUBLIC_KEY ) { + main_pk = node->pkt->pkt.public_key; + keyid_from_pk( main_pk, keyid ); + main_pk->expiredate = expiredate; + } + else if( node->pkt->pkttype == PKT_PUBLIC_SUBKEY + && (node->flag & NODFLG_SELKEY ) ) { + sub_pk = node->pkt->pkt.public_key; + sub_pk->expiredate = expiredate; + } + else if( node->pkt->pkttype == PKT_USER_ID ) + uid = node->pkt->pkt.user_id; + else if( main_pk && node->pkt->pkttype == PKT_SIGNATURE + && ( mainkey || sub_pk ) ) { + PKT_signature *sig = node->pkt->pkt.signature; + if( keyid[0] == sig->keyid[0] && keyid[1] == sig->keyid[1] + && ( (mainkey && uid + && uid->created && (sig->sig_class&~3) == 0x10) + || (!mainkey && sig->sig_class == 0x18) ) + && sig->flags.chosen_selfsig ) + { + /* this is a selfsignature which is to be replaced */ + PKT_signature *newsig; + PACKET *newpkt; + KBNODE sn; + int signumber2 = 0; + + signumber++; + + if( (mainkey && main_pk->version < 4) + || (!mainkey && sub_pk->version < 4 ) ) { + log_info(_( + "You can't change the expiration date of a v3 key\n")); + free_secret_key( sk ); + return 0; + } + + /* find the corresponding secret self-signature */ + for( sn=sec_keyblock; sn; sn = sn->next ) { + if( sn->pkt->pkttype == PKT_SIGNATURE ) { + PKT_signature *b = sn->pkt->pkt.signature; + if( keyid[0] == b->keyid[0] && keyid[1] == b->keyid[1] + && sig->sig_class == b->sig_class + && ++signumber2 == signumber ) + break; + } + } + if( !sn ) + log_info(_("No corresponding signature in secret ring\n")); + + if( mainkey ) + rc = update_keysig_packet(&newsig, sig, main_pk, uid, NULL, + sk, keygen_add_key_expire, main_pk); + else + rc = update_keysig_packet(&newsig, sig, main_pk, NULL, sub_pk, + sk, keygen_add_key_expire, sub_pk ); + if( rc ) { + log_error("make_keysig_packet failed: %s\n", + g10_errstr(rc)); + free_secret_key( sk ); + return 0; + } + /* replace the packet */ + newpkt = xmalloc_clear( sizeof *newpkt ); + newpkt->pkttype = PKT_SIGNATURE; + newpkt->pkt.signature = newsig; + free_packet( node->pkt ); + xfree( node->pkt ); + node->pkt = newpkt; + if( sn ) { + newpkt = xmalloc_clear( sizeof *newpkt ); + newpkt->pkttype = PKT_SIGNATURE; + newpkt->pkt.signature = copy_signature( NULL, newsig ); + free_packet( sn->pkt ); + xfree( sn->pkt ); + sn->pkt = newpkt; + } + sub_pk = NULL; + } + } + } + + free_secret_key( sk ); + update_trust=1; + return 1; +} + +static int +menu_backsign(KBNODE pub_keyblock,KBNODE sec_keyblock) +{ + int rc,modified=0; + PKT_public_key *main_pk; + PKT_secret_key *main_sk,*sub_sk=NULL; + KBNODE node; + + assert(pub_keyblock->pkt->pkttype==PKT_PUBLIC_KEY); + assert(sec_keyblock->pkt->pkttype==PKT_SECRET_KEY); + + merge_keys_and_selfsig(pub_keyblock); + main_pk=pub_keyblock->pkt->pkt.public_key; + main_sk=copy_secret_key(NULL,sec_keyblock->pkt->pkt.secret_key); + keyid_from_pk(main_pk,NULL); + + for(node=pub_keyblock;node;node=node->next) + { + PKT_public_key *sub_pk=NULL; + KBNODE node2,sig_pk=NULL,sig_sk=NULL; + char *passphrase; + + if(sub_sk) + { + free_secret_key(sub_sk); + sub_sk=NULL; + } + + /* Find a signing subkey with no backsig */ + if(node->pkt->pkttype==PKT_PUBLIC_SUBKEY) + { + if(node->pkt->pkt.public_key->pubkey_usage&PUBKEY_USAGE_SIG) + { + if(node->pkt->pkt.public_key->backsig) + tty_printf(_("signing subkey %s is already cross-certified\n"), + keystr_from_pk(node->pkt->pkt.public_key)); + else + sub_pk=node->pkt->pkt.public_key; + } + else + tty_printf(_("subkey %s does not sign and so does" + " not need to be cross-certified\n"), + keystr_from_pk(node->pkt->pkt.public_key)); + } + + if(!sub_pk) + continue; + + /* Find the selected selfsig on this subkey */ + for(node2=node->next; + node2 && node2->pkt->pkttype==PKT_SIGNATURE; + node2=node2->next) + if(node2->pkt->pkt.signature->version>=4 + && node2->pkt->pkt.signature->flags.chosen_selfsig) + { + sig_pk=node2; + break; + } + + if(!sig_pk) + continue; + + /* Find the secret subkey that matches the public subkey */ + for(node2=sec_keyblock;node2;node2=node2->next) + if(node2->pkt->pkttype==PKT_SECRET_SUBKEY + && !cmp_public_secret_key(sub_pk,node2->pkt->pkt.secret_key)) + { + sub_sk=copy_secret_key(NULL,node2->pkt->pkt.secret_key); + break; + } + + if(!sub_sk) + { + tty_printf(_("no secret subkey for public subkey %s - ignoring\n"), + keystr_from_pk(sub_pk)); + continue; + } + + /* Now finally find the matching selfsig on the secret subkey. + We can't use chosen_selfsig here (it's not set for secret + keys), so we just pick the selfsig with the right class. + This is what menu_expire does as well. */ + for(node2=node2->next; + node2 && node2->pkt->pkttype!=PKT_SECRET_SUBKEY; + node2=node2->next) + if(node2->pkt->pkttype==PKT_SIGNATURE + && node2->pkt->pkt.signature->version>=4 + && node2->pkt->pkt.signature->keyid[0]==sig_pk->pkt->pkt.signature->keyid[0] + && node2->pkt->pkt.signature->keyid[1]==sig_pk->pkt->pkt.signature->keyid[1] + && node2->pkt->pkt.signature->sig_class==sig_pk->pkt->pkt.signature->sig_class) + { + sig_sk=node2; + break; + } + + /* Now we can get to work. We have a main key and secret part, + a signing subkey with signature and secret part possibly with + signature. */ + + passphrase=get_last_passphrase(); + set_next_passphrase(passphrase); + xfree(passphrase); + + rc=make_backsig(sig_pk->pkt->pkt.signature,main_pk,sub_pk,sub_sk); + if(rc==0) + { + PKT_signature *newsig; + PACKET *newpkt; + + passphrase=get_last_passphrase(); + set_next_passphrase(passphrase); + xfree(passphrase); + + rc=update_keysig_packet(&newsig,sig_pk->pkt->pkt.signature,main_pk, + NULL,sub_pk,main_sk,NULL,NULL); + if(rc==0) + { + /* Put the new sig into place on the pubkey */ + newpkt=xmalloc_clear(sizeof(*newpkt)); + newpkt->pkttype=PKT_SIGNATURE; + newpkt->pkt.signature=newsig; + free_packet(sig_pk->pkt); + xfree(sig_pk->pkt); + sig_pk->pkt=newpkt; + + if(sig_sk) + { + /* Put the new sig into place on the seckey */ + newpkt=xmalloc_clear(sizeof(*newpkt)); + newpkt->pkttype=PKT_SIGNATURE; + newpkt->pkt.signature=copy_signature(NULL,newsig); + free_packet(sig_sk->pkt); + xfree(sig_sk->pkt); + sig_sk->pkt=newpkt; + } + + modified=1; + } + else + { + log_error("update_keysig_packet failed: %s\n",g10_errstr(rc)); + break; + } + } + else + { + log_error("make_backsig failed: %s\n",g10_errstr(rc)); + break; + } + } + + set_next_passphrase(NULL); + + free_secret_key(main_sk); + if(sub_sk) + free_secret_key(sub_sk); + + return modified; +} + + +static int +change_primary_uid_cb ( PKT_signature *sig, void *opaque ) +{ + byte buf[1]; + + /* first clear all primary uid flags so that we are sure none are + * lingering around */ + delete_sig_subpkt (sig->hashed, SIGSUBPKT_PRIMARY_UID); + delete_sig_subpkt (sig->unhashed, SIGSUBPKT_PRIMARY_UID); + + /* if opaque is set,we want to set the primary id */ + if (opaque) { + buf[0] = 1; + build_sig_subpkt (sig, SIGSUBPKT_PRIMARY_UID, buf, 1 ); + } + + return 0; +} + + +/* + * Set the primary uid flag for the selected UID. We will also reset + * all other primary uid flags. For this to work with have to update + * all the signature timestamps. If we would do this with the current + * time, we lose quite a lot of information, so we use a a kludge to + * do this: Just increment the timestamp by one second which is + * sufficient to updated a signature during import. + */ +static int +menu_set_primary_uid ( KBNODE pub_keyblock, KBNODE sec_keyblock ) +{ + PKT_secret_key *sk; /* copy of the main sk */ + PKT_public_key *main_pk; + PKT_user_id *uid; + KBNODE node; + u32 keyid[2]; + int selected; + int attribute = 0; + int modified = 0; + + if ( count_selected_uids (pub_keyblock) != 1 ) { + tty_printf(_("Please select exactly one user ID.\n")); + return 0; + } + + node = find_kbnode( sec_keyblock, PKT_SECRET_KEY ); + sk = copy_secret_key( NULL, node->pkt->pkt.secret_key); + + /* Now we can actually change the self signature(s) */ + main_pk = NULL; + uid = NULL; + selected = 0; + + /* Is our selected uid an attribute packet? */ + for ( node=pub_keyblock; node; node = node->next ) + if (node->pkt->pkttype == PKT_USER_ID && node->flag & NODFLG_SELUID) + attribute = (node->pkt->pkt.user_id->attrib_data!=NULL); + + for ( node=pub_keyblock; node; node = node->next ) { + if ( node->pkt->pkttype == PKT_PUBLIC_SUBKEY ) + break; /* ready */ + + if ( node->pkt->pkttype == PKT_PUBLIC_KEY ) { + main_pk = node->pkt->pkt.public_key; + keyid_from_pk( main_pk, keyid ); + } + else if ( node->pkt->pkttype == PKT_USER_ID ) { + uid = node->pkt->pkt.user_id; + selected = node->flag & NODFLG_SELUID; + } + else if ( main_pk && uid && node->pkt->pkttype == PKT_SIGNATURE ) { + PKT_signature *sig = node->pkt->pkt.signature; + if ( keyid[0] == sig->keyid[0] && keyid[1] == sig->keyid[1] + && (uid && (sig->sig_class&~3) == 0x10) + && attribute == (uid->attrib_data!=NULL) + && sig->flags.chosen_selfsig ) + { + if(sig->version < 4) { + char *user=utf8_to_native(uid->name,strlen(uid->name),0); + + log_info(_("skipping v3 self-signature on user ID \"%s\"\n"), + user); + xfree(user); + } + else { + /* This is a selfsignature which is to be replaced. + We can just ignore v3 signatures because they are + not able to carry the primary ID flag. We also + ignore self-sigs on user IDs that are not of the + same type that we are making primary. That is, if + we are making a user ID primary, we alter user IDs. + If we are making an attribute packet primary, we + alter attribute packets. */ + + /* FIXME: We must make sure that we only have one + self-signature per user ID here (not counting + revocations) */ + PKT_signature *newsig; + PACKET *newpkt; + const byte *p; + int action; + + /* see whether this signature has the primary UID flag */ + p = parse_sig_subpkt (sig->hashed, + SIGSUBPKT_PRIMARY_UID, NULL ); + if ( !p ) + p = parse_sig_subpkt (sig->unhashed, + SIGSUBPKT_PRIMARY_UID, NULL ); + if ( p && *p ) /* yes */ + action = selected? 0 : -1; + else /* no */ + action = selected? 1 : 0; + + if (action) { + int rc = update_keysig_packet (&newsig, sig, + main_pk, uid, NULL, + sk, + change_primary_uid_cb, + action > 0? "x":NULL ); + if( rc ) { + log_error ("update_keysig_packet failed: %s\n", + g10_errstr(rc)); + free_secret_key( sk ); + return 0; + } + /* replace the packet */ + newpkt = xmalloc_clear( sizeof *newpkt ); + newpkt->pkttype = PKT_SIGNATURE; + newpkt->pkt.signature = newsig; + free_packet( node->pkt ); + xfree( node->pkt ); + node->pkt = newpkt; + modified = 1; + } + } + } + } + } + + free_secret_key( sk ); + return modified; +} + + +/* + * Set preferences to new values for the selected user IDs + */ +static int +menu_set_preferences (KBNODE pub_keyblock, KBNODE sec_keyblock ) +{ + PKT_secret_key *sk; /* copy of the main sk */ + PKT_public_key *main_pk; + PKT_user_id *uid; + KBNODE node; + u32 keyid[2]; + int selected, select_all; + int modified = 0; + + no_primary_warning(pub_keyblock); + + select_all = !count_selected_uids (pub_keyblock); + + node = find_kbnode( sec_keyblock, PKT_SECRET_KEY ); + sk = copy_secret_key( NULL, node->pkt->pkt.secret_key); + + /* Now we can actually change the self signature(s) */ + main_pk = NULL; + uid = NULL; + selected = 0; + for ( node=pub_keyblock; node; node = node->next ) { + if ( node->pkt->pkttype == PKT_PUBLIC_SUBKEY ) + break; /* ready */ + + if ( node->pkt->pkttype == PKT_PUBLIC_KEY ) { + main_pk = node->pkt->pkt.public_key; + keyid_from_pk( main_pk, keyid ); + } + else if ( node->pkt->pkttype == PKT_USER_ID ) { + uid = node->pkt->pkt.user_id; + selected = select_all || (node->flag & NODFLG_SELUID); + } + else if ( main_pk && uid && selected + && node->pkt->pkttype == PKT_SIGNATURE ) { + PKT_signature *sig = node->pkt->pkt.signature; + if ( keyid[0] == sig->keyid[0] && keyid[1] == sig->keyid[1] + && (uid && (sig->sig_class&~3) == 0x10) + && sig->flags.chosen_selfsig ) { + if( sig->version < 4 ) { + char *user=utf8_to_native(uid->name,strlen(uid->name),0); + + log_info(_("skipping v3 self-signature on user ID \"%s\"\n"), + user); + xfree(user); + } + else { + /* This is a selfsignature which is to be replaced + * We have to ignore v3 signatures because they are + * not able to carry the preferences */ + PKT_signature *newsig; + PACKET *newpkt; + int rc; + + rc = update_keysig_packet (&newsig, sig, + main_pk, uid, NULL, + sk, + keygen_upd_std_prefs, + NULL ); + if( rc ) { + log_error ("update_keysig_packet failed: %s\n", + g10_errstr(rc)); + free_secret_key( sk ); + return 0; + } + /* replace the packet */ + newpkt = xmalloc_clear( sizeof *newpkt ); + newpkt->pkttype = PKT_SIGNATURE; + newpkt->pkt.signature = newsig; + free_packet( node->pkt ); + xfree( node->pkt ); + node->pkt = newpkt; + modified = 1; + } + } + } + } + + free_secret_key( sk ); + return modified; +} + + +static int +menu_set_keyserver_url (const char *url, + KBNODE pub_keyblock, KBNODE sec_keyblock ) +{ + PKT_secret_key *sk; /* copy of the main sk */ + PKT_public_key *main_pk; + PKT_user_id *uid; + KBNODE node; + u32 keyid[2]; + int selected, select_all; + int modified = 0; + char *answer,*uri; + + no_primary_warning(pub_keyblock); + + if(url) + answer=xstrdup(url); + else + { + answer=cpr_get_utf8("keyedit.add_keyserver", + _("Enter your preferred keyserver URL: ")); + if(answer[0]=='\0' || answer[0]=='\004') + { + xfree(answer); + return 0; + } + } + + if(ascii_strcasecmp(answer,"none")==0) + uri=NULL; + else + { + struct keyserver_spec *keyserver=NULL; + /* Sanity check the format */ + keyserver=parse_keyserver_uri(answer,1,NULL,0); + xfree(answer); + if(!keyserver) + { + log_info(_("could not parse keyserver URL\n")); + return 0; + } + uri=xstrdup(keyserver->uri); + free_keyserver_spec(keyserver); + } + + select_all = !count_selected_uids (pub_keyblock); + + node = find_kbnode( sec_keyblock, PKT_SECRET_KEY ); + sk = copy_secret_key( NULL, node->pkt->pkt.secret_key); + + /* Now we can actually change the self signature(s) */ + main_pk = NULL; + uid = NULL; + selected = 0; + for ( node=pub_keyblock; node; node = node->next ) + { + if ( node->pkt->pkttype == PKT_PUBLIC_SUBKEY ) + break; /* ready */ + + if ( node->pkt->pkttype == PKT_PUBLIC_KEY ) + { + main_pk = node->pkt->pkt.public_key; + keyid_from_pk( main_pk, keyid ); + } + else if ( node->pkt->pkttype == PKT_USER_ID ) + { + uid = node->pkt->pkt.user_id; + selected = select_all || (node->flag & NODFLG_SELUID); + } + else if ( main_pk && uid && selected + && node->pkt->pkttype == PKT_SIGNATURE ) + { + PKT_signature *sig = node->pkt->pkt.signature; + if ( keyid[0] == sig->keyid[0] && keyid[1] == sig->keyid[1] + && (uid && (sig->sig_class&~3) == 0x10) + && sig->flags.chosen_selfsig) + { + char *user=utf8_to_native(uid->name,strlen(uid->name),0); + if( sig->version < 4 ) + log_info(_("skipping v3 self-signature on user ID \"%s\"\n"), + user); + else + { + /* This is a selfsignature which is to be replaced + * We have to ignore v3 signatures because they are + * not able to carry the subpacket. */ + PKT_signature *newsig; + PACKET *newpkt; + int rc; + const byte *p; + size_t plen; + + p=parse_sig_subpkt(sig->hashed,SIGSUBPKT_PREF_KS,&plen); + if(p && plen) + { + tty_printf("Current preferred keyserver for user" + " ID \"%s\": ",user); + tty_print_utf8_string(p,plen); + tty_printf("\n"); + if(!cpr_get_answer_is_yes("keyedit.confirm_keyserver", + uri?_("Are you sure you want to replace it? (y/N) "): + _("Are you sure you want to delete it? (y/N) "))) + continue; + } + else if(uri==NULL) + { + /* There is no current keyserver URL, so there + is no point in trying to un-set it. */ + continue; + } + + rc = update_keysig_packet (&newsig, sig, + main_pk, uid, NULL, + sk, + keygen_add_keyserver_url, uri ); + if( rc ) + { + log_error ("update_keysig_packet failed: %s\n", + g10_errstr(rc)); + free_secret_key( sk ); + xfree(uri); + return 0; + } + /* replace the packet */ + newpkt = xmalloc_clear( sizeof *newpkt ); + newpkt->pkttype = PKT_SIGNATURE; + newpkt->pkt.signature = newsig; + free_packet( node->pkt ); + xfree( node->pkt ); + node->pkt = newpkt; + modified = 1; + } + + xfree(user); + } + } + } + + xfree(uri); + free_secret_key( sk ); + return modified; +} + +static int +menu_set_notation(const char *string,KBNODE pub_keyblock,KBNODE sec_keyblock) +{ + PKT_secret_key *sk; /* copy of the main sk */ + PKT_public_key *main_pk; + PKT_user_id *uid; + KBNODE node; + u32 keyid[2]; + int selected, select_all; + int modified = 0; + char *answer; + struct notation *notation; + + no_primary_warning(pub_keyblock); + + if(string) + answer=xstrdup(string); + else + { + answer=cpr_get_utf8("keyedit.add_notation", + _("Enter the notation: ")); + if(answer[0]=='\0' || answer[0]=='\004') + { + xfree(answer); + return 0; + } + } + + if(ascii_strcasecmp(answer,"none")==0 + || ascii_strcasecmp(answer,"-")==0) + notation=NULL; /* delete them all */ + else + { + notation=string_to_notation(answer,0); + if(!notation) + { + xfree(answer); + return 0; + } + } + + xfree(answer); + + select_all = !count_selected_uids (pub_keyblock); + + node = find_kbnode( sec_keyblock, PKT_SECRET_KEY ); + sk = copy_secret_key( NULL, node->pkt->pkt.secret_key); + + /* Now we can actually change the self signature(s) */ + main_pk = NULL; + uid = NULL; + selected = 0; + for ( node=pub_keyblock; node; node = node->next ) + { + if ( node->pkt->pkttype == PKT_PUBLIC_SUBKEY ) + break; /* ready */ + + if ( node->pkt->pkttype == PKT_PUBLIC_KEY ) + { + main_pk = node->pkt->pkt.public_key; + keyid_from_pk( main_pk, keyid ); + } + else if ( node->pkt->pkttype == PKT_USER_ID ) + { + uid = node->pkt->pkt.user_id; + selected = select_all || (node->flag & NODFLG_SELUID); + } + else if ( main_pk && uid && selected + && node->pkt->pkttype == PKT_SIGNATURE ) + { + PKT_signature *sig = node->pkt->pkt.signature; + if ( keyid[0] == sig->keyid[0] && keyid[1] == sig->keyid[1] + && (uid && (sig->sig_class&~3) == 0x10) + && sig->flags.chosen_selfsig) + { + char *user=utf8_to_native(uid->name,strlen(uid->name),0); + if( sig->version < 4 ) + log_info(_("skipping v3 self-signature on user ID \"%s\"\n"), + user); + else + { + PKT_signature *newsig; + PACKET *newpkt; + int rc,skip=0,addonly=1; + + if(sig->flags.notation) + { + tty_printf("Current notations for user ID \"%s\":\n", + user); + tty_print_notations(-9,sig); + } + else + { + tty_printf("No notations on user ID \"%s\"\n",user); + if(notation==NULL) + { + /* There are no current notations, so there + is no point in trying to un-set them. */ + continue; + } + } + + if(notation) + { + struct notation *n; + int deleting=0; + + notation->next=sig_to_notation(sig); + + for(n=notation->next;n;n=n->next) + if(strcmp(n->name,notation->name)==0) + { + if(notation->value) + { + if(strcmp(n->value,notation->value)==0) + { + if(notation->flags.ignore) + { + /* Value match with a delete + flag. */ + n->flags.ignore=1; + deleting=1; + } + else + { + /* Adding the same notation + twice, so don't add it at + all. */ + skip=1; + tty_printf("Skipping notation:" + " %s=%s\n", + notation->name, + notation->value); + break; + } + } + } + else + { + /* No value, so it means delete. */ + n->flags.ignore=1; + deleting=1; + } + + if(n->flags.ignore) + { + tty_printf("Removing notation: %s=%s\n", + n->name,n->value); + addonly=0; + } + } + + if(!notation->flags.ignore && !skip) + tty_printf("Adding notation: %s=%s\n", + notation->name,notation->value); + + /* We tried to delete, but had no matches */ + if(notation->flags.ignore && !deleting) + continue; + } + else + { + tty_printf("Removing all notations\n"); + addonly=0; + } + + if(skip + || (!addonly + && !cpr_get_answer_is_yes("keyedit.confirm_notation", + _("Proceed? (y/N) ")))) + continue; + + rc = update_keysig_packet (&newsig, sig, + main_pk, uid, NULL, + sk, + keygen_add_notations, notation ); + if( rc ) + { + log_error ("update_keysig_packet failed: %s\n", + g10_errstr(rc)); + free_secret_key( sk ); + free_notation(notation); + xfree(user); + return 0; + } + + /* replace the packet */ + newpkt = xmalloc_clear( sizeof *newpkt ); + newpkt->pkttype = PKT_SIGNATURE; + newpkt->pkt.signature = newsig; + free_packet( node->pkt ); + xfree( node->pkt ); + node->pkt = newpkt; + modified = 1; + + if(notation) + { + /* Snip off the notation list from the sig */ + free_notation(notation->next); + notation->next=NULL; + } + + xfree(user); + } + } + } + } + + free_notation(notation); + free_secret_key( sk ); + return modified; +} + + +/**************** + * Select one user id or remove all selection if index is 0. + * Returns: True if the selection changed; + */ +static int +menu_select_uid( KBNODE keyblock, int idx ) +{ + KBNODE node; + int i; + + /* first check that the index is valid */ + if( idx ) { + for( i=0, node = keyblock; node; node = node->next ) { + if( node->pkt->pkttype == PKT_USER_ID ) { + if( ++i == idx ) + break; + } + } + if( !node ) { + tty_printf(_("No user ID with index %d\n"), idx ); + return 0; + } + } + else { /* reset all */ + for( i=0, node = keyblock; node; node = node->next ) { + if( node->pkt->pkttype == PKT_USER_ID ) + node->flag &= ~NODFLG_SELUID; + } + return 1; + } + /* and toggle the new index */ + for( i=0, node = keyblock; node; node = node->next ) { + if( node->pkt->pkttype == PKT_USER_ID ) { + if( ++i == idx ) { + if( (node->flag & NODFLG_SELUID) ) + node->flag &= ~NODFLG_SELUID; + else + node->flag |= NODFLG_SELUID; + } + } + } + + return 1; +} + +/* Search in the keyblock for a uid that matches namehash */ +static int +menu_select_uid_namehash( KBNODE keyblock, const char *namehash ) +{ + byte hash[NAMEHASH_LEN]; + KBNODE node; + int i; + + assert(strlen(namehash)==NAMEHASH_LEN*2); + + for(i=0;i<NAMEHASH_LEN;i++) + hash[i]=hextobyte(&namehash[i*2]); + + for(node=keyblock->next;node;node=node->next) + { + if(node->pkt->pkttype==PKT_USER_ID) + { + namehash_from_uid(node->pkt->pkt.user_id); + if(memcmp(node->pkt->pkt.user_id->namehash,hash,NAMEHASH_LEN)==0) + { + if(node->flag&NODFLG_SELUID) + node->flag &= ~NODFLG_SELUID; + else + node->flag |= NODFLG_SELUID; + + break; + } + } + } + + if(!node) + { + tty_printf(_("No user ID with hash %s\n"),namehash); + return 0; + } + + return 1; +} + +/**************** + * Select secondary keys + * Returns: True if the selection changed; + */ +static int +menu_select_key( KBNODE keyblock, int idx ) +{ + KBNODE node; + int i; + + /* first check that the index is valid */ + if( idx ) { + for( i=0, node = keyblock; node; node = node->next ) { + if( node->pkt->pkttype == PKT_PUBLIC_SUBKEY + || node->pkt->pkttype == PKT_SECRET_SUBKEY ) { + if( ++i == idx ) + break; + } + } + if( !node ) { + tty_printf(_("No subkey with index %d\n"), idx ); + return 0; + } + } + else { /* reset all */ + for( i=0, node = keyblock; node; node = node->next ) { + if( node->pkt->pkttype == PKT_PUBLIC_SUBKEY + || node->pkt->pkttype == PKT_SECRET_SUBKEY ) + node->flag &= ~NODFLG_SELKEY; + } + return 1; + } + /* and set the new index */ + for( i=0, node = keyblock; node; node = node->next ) { + if( node->pkt->pkttype == PKT_PUBLIC_SUBKEY + || node->pkt->pkttype == PKT_SECRET_SUBKEY ) { + if( ++i == idx ) { + if( (node->flag & NODFLG_SELKEY) ) + node->flag &= ~NODFLG_SELKEY; + else + node->flag |= NODFLG_SELKEY; + } + } + } + + return 1; +} + + +static int +count_uids_with_flag( KBNODE keyblock, unsigned flag ) +{ + KBNODE node; + int i=0; + + for( node = keyblock; node; node = node->next ) + if( node->pkt->pkttype == PKT_USER_ID && (node->flag & flag) ) + i++; + return i; +} + +static int +count_keys_with_flag( KBNODE keyblock, unsigned flag ) +{ + KBNODE node; + int i=0; + + for( node = keyblock; node; node = node->next ) + if( ( node->pkt->pkttype == PKT_PUBLIC_SUBKEY + || node->pkt->pkttype == PKT_SECRET_SUBKEY) + && (node->flag & flag) ) + i++; + return i; +} + +static int +count_uids( KBNODE keyblock ) +{ + KBNODE node; + int i=0; + + for( node = keyblock; node; node = node->next ) + if( node->pkt->pkttype == PKT_USER_ID ) + i++; + return i; +} + + +/**************** + * Returns true if there is at least one selected user id + */ +static int +count_selected_uids( KBNODE keyblock ) +{ + return count_uids_with_flag( keyblock, NODFLG_SELUID); +} + +static int +count_selected_keys( KBNODE keyblock ) +{ + return count_keys_with_flag( keyblock, NODFLG_SELKEY); +} + +/* returns how many real (i.e. not attribute) uids are unmarked */ +static int +real_uids_left( KBNODE keyblock ) +{ + KBNODE node; + int real=0; + + for(node=keyblock;node;node=node->next) + if(node->pkt->pkttype==PKT_USER_ID && !(node->flag&NODFLG_SELUID) && + !node->pkt->pkt.user_id->attrib_data) + real++; + + return real; +} + +/* + * Ask whether the signature should be revoked. If the user commits this, + * flag bit MARK_A is set on the signature and the user ID. + */ +static void +ask_revoke_sig( KBNODE keyblock, KBNODE node ) +{ + int doit=0; + PKT_user_id *uid; + PKT_signature *sig = node->pkt->pkt.signature; + KBNODE unode = find_prev_kbnode( keyblock, node, PKT_USER_ID ); + + if( !unode ) { + log_error("Oops: no user ID for signature\n"); + return; + } + + uid=unode->pkt->pkt.user_id; + + if(opt.with_colons) + { + if(uid->attrib_data) + printf("uat:::::::::%u %lu",uid->numattribs,uid->attrib_len); + else + { + printf("uid:::::::::"); + print_string (stdout, uid->name, uid->len, ':'); + } + + printf("\n"); + + print_and_check_one_sig_colon(keyblock,node,NULL,NULL,NULL,NULL,1); + } + else + { + char *p=utf8_to_native(unode->pkt->pkt.user_id->name, + unode->pkt->pkt.user_id->len,0); + tty_printf(_("user ID: \"%s\"\n"),p); + xfree(p); + + tty_printf(_("signed by your key %s on %s%s%s\n"), + keystr(sig->keyid),datestr_from_sig(sig), + sig->flags.exportable?"":_(" (non-exportable)"),""); + } + if(sig->flags.expired) + { + tty_printf(_("This signature expired on %s.\n"), + expirestr_from_sig(sig)); + /* Use a different question so we can have different help text */ + doit=cpr_get_answer_is_yes("ask_revoke_sig.expired", + _("Are you sure you still want to revoke it? (y/N) ")); + } + else + doit=cpr_get_answer_is_yes("ask_revoke_sig.one", + _("Create a revocation certificate for this signature? (y/N) ")); + + if(doit) { + node->flag |= NODFLG_MARK_A; + unode->flag |= NODFLG_MARK_A; + } +} + +/**************** + * Display all user ids of the current public key together with signatures + * done by one of our keys. Then walk over all this sigs and ask the user + * whether he wants to revoke this signature. + * Return: True when the keyblock has changed. + */ +static int +menu_revsig( KBNODE keyblock ) +{ + PKT_signature *sig; + PKT_public_key *primary_pk; + KBNODE node; + int changed = 0; + int rc, any, skip=1, all=!count_selected_uids(keyblock); + struct revocation_reason_info *reason = NULL; + + assert(keyblock->pkt->pkttype==PKT_PUBLIC_KEY); + + /* FIXME: detect duplicates here */ + tty_printf(_("You have signed these user IDs on key %s:\n"), + keystr_from_pk(keyblock->pkt->pkt.public_key)); + for( node = keyblock; node; node = node->next ) { + node->flag &= ~(NODFLG_SELSIG | NODFLG_MARK_A); + if( node->pkt->pkttype == PKT_USER_ID ) { + if( node->flag&NODFLG_SELUID || all ) { + PKT_user_id *uid = node->pkt->pkt.user_id; + /* Hmmm: Should we show only UIDs with a signature? */ + tty_printf(" "); + tty_print_utf8_string( uid->name, uid->len ); + tty_printf("\n"); + skip=0; + } + else + skip=1; + } + else if( !skip && node->pkt->pkttype == PKT_SIGNATURE + && ((sig = node->pkt->pkt.signature), + !seckey_available(sig->keyid) ) ) + { + if( (sig->sig_class&~3) == 0x10 ) + { + tty_printf(" "); + tty_printf(_("signed by your key %s on %s%s%s\n"), + keystr(sig->keyid), datestr_from_sig(sig), + sig->flags.exportable?"":_(" (non-exportable)"), + sig->flags.revocable?"":_(" (non-revocable)")); + if(sig->flags.revocable) + node->flag |= NODFLG_SELSIG; + } + else if( sig->sig_class == 0x30 ) + { + tty_printf(" "); + tty_printf(_("revoked by your key %s on %s\n"), + keystr(sig->keyid),datestr_from_sig(sig)); + } + } + } + + tty_printf("\n"); + + /* ask */ + for( node = keyblock; node; node = node->next ) { + if( !(node->flag & NODFLG_SELSIG) ) + continue; + ask_revoke_sig( keyblock, node ); + } + + /* present selected */ + any = 0; + for( node = keyblock; node; node = node->next ) { + if( !(node->flag & NODFLG_MARK_A) ) + continue; + if( !any ) { + any = 1; + tty_printf(_("You are about to revoke these signatures:\n")); + } + if( node->pkt->pkttype == PKT_USER_ID ) { + PKT_user_id *uid = node->pkt->pkt.user_id; + tty_printf(" "); + tty_print_utf8_string( uid->name, uid->len ); + tty_printf("\n"); + } + else if( node->pkt->pkttype == PKT_SIGNATURE ) { + sig = node->pkt->pkt.signature; + tty_printf(" "); + tty_printf(_("signed by your key %s on %s%s%s\n"), + keystr(sig->keyid), datestr_from_sig(sig),"", + sig->flags.exportable?"":_(" (non-exportable)") ); + } + } + if( !any ) + return 0; /* none selected */ + + if( !cpr_get_answer_is_yes("ask_revoke_sig.okay", + _("Really create the revocation certificates? (y/N) ")) ) + return 0; /* forget it */ + + reason = ask_revocation_reason( 0, 1, 0 ); + if( !reason ) { /* user decided to cancel */ + return 0; + } + + /* now we can sign the user ids */ + reloop: /* (must use this, because we are modifing the list) */ + primary_pk = keyblock->pkt->pkt.public_key; + for( node=keyblock; node; node = node->next ) { + KBNODE unode; + PACKET *pkt; + struct sign_attrib attrib; + PKT_secret_key *sk; + + if( !(node->flag & NODFLG_MARK_A) + || node->pkt->pkttype != PKT_SIGNATURE ) + continue; + unode = find_prev_kbnode( keyblock, node, PKT_USER_ID ); + assert( unode ); /* we already checked this */ + + memset( &attrib, 0, sizeof attrib ); + attrib.reason = reason; + attrib.non_exportable=!node->pkt->pkt.signature->flags.exportable; + + node->flag &= ~NODFLG_MARK_A; + sk = xmalloc_secure_clear( sizeof *sk ); + if( get_seckey( sk, node->pkt->pkt.signature->keyid ) ) { + log_info(_("no secret key\n")); + continue; + } + rc = make_keysig_packet( &sig, primary_pk, + unode->pkt->pkt.user_id, + NULL, + sk, + 0x30, 0, 0, 0, 0, + sign_mk_attrib, + &attrib ); + free_secret_key(sk); + if( rc ) { + log_error(_("signing failed: %s\n"), g10_errstr(rc)); + release_revocation_reason_info( reason ); + return changed; + } + changed = 1; /* we changed the keyblock */ + update_trust = 1; + /* Are we revoking our own uid? */ + if(primary_pk->keyid[0]==sig->keyid[0] && + primary_pk->keyid[1]==sig->keyid[1]) + unode->pkt->pkt.user_id->is_revoked=1; + pkt = xmalloc_clear( sizeof *pkt ); + pkt->pkttype = PKT_SIGNATURE; + pkt->pkt.signature = sig; + insert_kbnode( unode, new_kbnode(pkt), 0 ); + goto reloop; + } + + release_revocation_reason_info( reason ); + return changed; +} + +/* Revoke a user ID (i.e. revoke a user ID selfsig). Return true if + keyblock changed. */ +static int +menu_revuid( KBNODE pub_keyblock, KBNODE sec_keyblock ) +{ + PKT_public_key *pk = pub_keyblock->pkt->pkt.public_key; + PKT_secret_key *sk = copy_secret_key( NULL, + sec_keyblock->pkt->pkt.secret_key ); + KBNODE node; + int changed = 0; + int rc; + struct revocation_reason_info *reason = NULL; + + /* Note that this is correct as per the RFCs, but nevertheless + somewhat meaningless in the real world. 1991 did define the 0x30 + sig class, but PGP 2.x did not actually implement it, so it would + probably be safe to use v4 revocations everywhere. -ds */ + + for( node = pub_keyblock; node; node = node->next ) + if(pk->version>3 || (node->pkt->pkttype==PKT_USER_ID && + node->pkt->pkt.user_id->selfsigversion>3)) + { + if((reason = ask_revocation_reason( 0, 1, 4 ))) + break; + else + goto leave; + } + + reloop: /* (better this way because we are modifing the keyring) */ + for( node = pub_keyblock; node; node = node->next ) + if(node->pkt->pkttype == PKT_USER_ID && (node->flag & NODFLG_SELUID)) + { + PKT_user_id *uid=node->pkt->pkt.user_id; + + if(uid->is_revoked) + { + char *user=utf8_to_native(uid->name,uid->len,0); + log_info(_("user ID \"%s\" is already revoked\n"),user); + xfree(user); + } + else + { + PACKET *pkt; + PKT_signature *sig; + struct sign_attrib attrib; + u32 timestamp=make_timestamp(); + + if(uid->created>=timestamp) + { + /* Okay, this is a problem. The user ID selfsig was + created in the future, so we need to warn the user and + set our revocation timestamp one second after that so + everything comes out clean. */ + + log_info(_("WARNING: a user ID signature is dated %d" + " seconds in the future\n"),uid->created-timestamp); + + timestamp=uid->created+1; + } + + memset( &attrib, 0, sizeof attrib ); + attrib.reason = reason; + + node->flag &= ~NODFLG_SELUID; + + rc = make_keysig_packet( &sig, pk, uid, NULL, sk, 0x30, 0, + (reason==NULL)?3:0, timestamp, 0, + sign_mk_attrib, &attrib ); + if( rc ) + { + log_error(_("signing failed: %s\n"), g10_errstr(rc)); + goto leave; + } + else + { + pkt = xmalloc_clear( sizeof *pkt ); + pkt->pkttype = PKT_SIGNATURE; + pkt->pkt.signature = sig; + insert_kbnode( node, new_kbnode(pkt), 0 ); + + /* If the trustdb has an entry for this key+uid then the + trustdb needs an update. */ + if(!update_trust + && (get_validity(pk,uid)&TRUST_MASK)>=TRUST_UNDEFINED) + update_trust=1; + + changed = 1; + node->pkt->pkt.user_id->is_revoked=1; + + goto reloop; + } + } + } + + if(changed) + commit_kbnode( &pub_keyblock ); + + leave: + free_secret_key(sk); + release_revocation_reason_info( reason ); + return changed; +} + +/**************** + * Revoke the whole key. + */ +static int +menu_revkey( KBNODE pub_keyblock, KBNODE sec_keyblock ) +{ + PKT_public_key *pk=pub_keyblock->pkt->pkt.public_key; + PKT_secret_key *sk; + int rc,changed = 0; + struct revocation_reason_info *reason; + PACKET *pkt; + PKT_signature *sig; + + if(pk->is_revoked) + { + tty_printf(_("Key %s is already revoked.\n"),keystr_from_pk(pk)); + return 0; + } + + reason = ask_revocation_reason( 1, 0, 0 ); + /* user decided to cancel */ + if( !reason ) + return 0; + + sk = copy_secret_key( NULL, sec_keyblock->pkt->pkt.secret_key ); + rc = make_keysig_packet( &sig, pk, NULL, NULL, sk, + 0x20, 0, opt.force_v4_certs?4:0, 0, 0, + revocation_reason_build_cb, reason ); + free_secret_key(sk); + if( rc ) + { + log_error(_("signing failed: %s\n"), g10_errstr(rc)); + goto scram; + } + + changed = 1; /* we changed the keyblock */ + + pkt = xmalloc_clear( sizeof *pkt ); + pkt->pkttype = PKT_SIGNATURE; + pkt->pkt.signature = sig; + insert_kbnode( pub_keyblock, new_kbnode(pkt), 0 ); + commit_kbnode( &pub_keyblock ); + + update_trust=1; + + scram: + release_revocation_reason_info( reason ); + return changed; +} + +static int +menu_revsubkey( KBNODE pub_keyblock, KBNODE sec_keyblock ) +{ + PKT_public_key *mainpk; + KBNODE node; + int changed = 0; + int rc; + struct revocation_reason_info *reason = NULL; + + reason = ask_revocation_reason( 1, 0, 0 ); + if( !reason ) { /* user decided to cancel */ + return 0; + } + + reloop: /* (better this way because we are modifing the keyring) */ + mainpk = pub_keyblock->pkt->pkt.public_key; + for( node = pub_keyblock; node; node = node->next ) { + if( node->pkt->pkttype == PKT_PUBLIC_SUBKEY + && (node->flag & NODFLG_SELKEY) ) { + PACKET *pkt; + PKT_signature *sig; + PKT_secret_key *sk; + PKT_public_key *subpk = node->pkt->pkt.public_key; + struct sign_attrib attrib; + + if(subpk->is_revoked) + { + tty_printf(_("Subkey %s is already revoked.\n"), + keystr_from_pk(subpk)); + continue; + } + + memset( &attrib, 0, sizeof attrib ); + attrib.reason = reason; + + node->flag &= ~NODFLG_SELKEY; + sk = copy_secret_key( NULL, sec_keyblock->pkt->pkt.secret_key ); + rc = make_keysig_packet( &sig, mainpk, NULL, subpk, sk, + 0x28, 0, 0, 0, 0, + sign_mk_attrib, &attrib ); + free_secret_key(sk); + if( rc ) { + log_error(_("signing failed: %s\n"), g10_errstr(rc)); + release_revocation_reason_info( reason ); + return changed; + } + changed = 1; /* we changed the keyblock */ + + pkt = xmalloc_clear( sizeof *pkt ); + pkt->pkttype = PKT_SIGNATURE; + pkt->pkt.signature = sig; + insert_kbnode( node, new_kbnode(pkt), 0 ); + goto reloop; + } + } + commit_kbnode( &pub_keyblock ); + /*commit_kbnode( &sec_keyblock );*/ + + /* No need to set update_trust here since signing keys no longer + are used to certify other keys, so there is no change in trust + when revoking/removing them */ + + release_revocation_reason_info( reason ); + return changed; +} + +/* Note that update_ownertrust is going to mark the trustdb dirty when + enabling or disabling a key. This is arguably sub-optimal as + disabled keys are still counted in the web of trust, but perhaps + not worth adding extra complexity to change. -ds */ +static int +enable_disable_key( KBNODE keyblock, int disable ) +{ + PKT_public_key *pk = find_kbnode( keyblock, PKT_PUBLIC_KEY ) + ->pkt->pkt.public_key; + unsigned int trust, newtrust; + + trust = newtrust = get_ownertrust (pk); + newtrust &= ~TRUST_FLAG_DISABLED; + if( disable ) + newtrust |= TRUST_FLAG_DISABLED; + if( trust == newtrust ) + return 0; /* already in that state */ + update_ownertrust(pk, newtrust ); + return 0; +} + + +static void +menu_showphoto( KBNODE keyblock ) +{ + KBNODE node; + int select_all = !count_selected_uids(keyblock); + int count=0; + PKT_public_key *pk=NULL; + + /* Look for the public key first. We have to be really, really, + explicit as to which photo this is, and what key it is a UID on + since people may want to sign it. */ + + for( node = keyblock; node; node = node->next ) + { + if( node->pkt->pkttype == PKT_PUBLIC_KEY ) + pk = node->pkt->pkt.public_key; + else if( node->pkt->pkttype == PKT_USER_ID ) + { + PKT_user_id *uid = node->pkt->pkt.user_id; + count++; + + if((select_all || (node->flag & NODFLG_SELUID)) && + uid->attribs!=NULL) + { + int i; + + for(i=0;i<uid->numattribs;i++) + { + byte type; + u32 size; + + if(uid->attribs[i].type==ATTRIB_IMAGE && + parse_image_header(&uid->attribs[i],&type,&size)) + { + tty_printf(_("Displaying %s photo ID of size %ld for " + "key %s (uid %d)\n"), + image_type_to_string(type,1), + (ulong)size,keystr_from_pk(pk),count); + show_photos(&uid->attribs[i],1,pk,NULL); + } + } + } + } + } +} diff --git a/g10/keygen.c b/g10/keygen.c new file mode 100644 index 0000000..620274a --- /dev/null +++ b/g10/keygen.c @@ -0,0 +1,3707 @@ +/* keygen.c - generate a key pair + * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, + * 2006 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <errno.h> +#include <assert.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include "util.h" +#include "main.h" +#include "packet.h" +#include "cipher.h" +#include "ttyio.h" +#include "options.h" +#include "keydb.h" +#include "trustdb.h" +#include "status.h" +#include "i18n.h" +#include "cardglue.h" +#include "keyserver-internal.h" + +#define MAX_PREFS 30 + +enum para_name { + pKEYTYPE, + pKEYLENGTH, + pKEYUSAGE, + pSUBKEYTYPE, + pSUBKEYLENGTH, + pSUBKEYUSAGE, + pAUTHKEYTYPE, + pNAMEREAL, + pNAMEEMAIL, + pNAMECOMMENT, + pPREFERENCES, + pREVOKER, + pUSERID, + pEXPIREDATE, + pKEYEXPIRE, /* in n seconds */ + pSUBKEYEXPIRE, /* in n seconds */ + pPASSPHRASE, + pPASSPHRASE_DEK, + pPASSPHRASE_S2K, + pSERIALNO, + pBACKUPENCDIR, + pHANDLE, + pKEYSERVER +}; + +struct para_data_s { + struct para_data_s *next; + int lnr; + enum para_name key; + union { + DEK *dek; + STRING2KEY *s2k; + u32 expire; + unsigned int usage; + struct revocation_key revkey; + char value[1]; + } u; +}; + +struct output_control_s { + int lnr; + int dryrun; + int use_files; + struct { + char *fname; + char *newfname; + IOBUF stream; + armor_filter_context_t afx; + } pub; + struct { + char *fname; + char *newfname; + IOBUF stream; + armor_filter_context_t afx; + } sec; +}; + + +struct opaque_data_usage_and_pk { + unsigned int usage; + PKT_public_key *pk; +}; + + +static int prefs_initialized = 0; +static byte sym_prefs[MAX_PREFS]; +static int nsym_prefs; +static byte hash_prefs[MAX_PREFS]; +static int nhash_prefs; +static byte zip_prefs[MAX_PREFS]; +static int nzip_prefs; +static int mdc_available,ks_modify; + +static void do_generate_keypair( struct para_data_s *para, + struct output_control_s *outctrl, int card ); +static int write_keyblock( IOBUF out, KBNODE node ); +static int gen_card_key (int algo, int keyno, int is_primary, + KBNODE pub_root, KBNODE sec_root, + PKT_secret_key **ret_sk, + u32 expireval, struct para_data_s *para); +static int gen_card_key_with_backup (int algo, int keyno, int is_primary, + KBNODE pub_root, KBNODE sec_root, + u32 expireval, struct para_data_s *para, + const char *backup_dir); + + +static void +print_status_key_created (int letter, PKT_public_key *pk, const char *handle) +{ + byte array[MAX_FINGERPRINT_LEN], *s; + char *buf, *p; + size_t i, n; + + if (!handle) + handle = ""; + + buf = xmalloc (MAX_FINGERPRINT_LEN*2+31 + strlen (handle) + 1); + + p = buf; + if (letter || pk) + { + *p++ = letter; + *p++ = ' '; + fingerprint_from_pk (pk, array, &n); + s = array; + for (i=0; i < n ; i++, s++, p += 2) + sprintf (p, "%02X", *s); + } + if (*handle) + { + *p++ = ' '; + for (i=0; handle[i] && i < 100; i++) + *p++ = isspace ((unsigned int)handle[i])? '_':handle[i]; + } + *p = 0; + write_status_text ((letter || pk)?STATUS_KEY_CREATED:STATUS_KEY_NOT_CREATED, + buf); + xfree (buf); +} + +static void +print_status_key_not_created (const char *handle) +{ + print_status_key_created (0, NULL, handle); +} + + + +static void +write_uid( KBNODE root, const char *s ) +{ + PACKET *pkt = xmalloc_clear(sizeof *pkt ); + size_t n = strlen(s); + + pkt->pkttype = PKT_USER_ID; + pkt->pkt.user_id = xmalloc_clear( sizeof *pkt->pkt.user_id + n - 1 ); + pkt->pkt.user_id->len = n; + pkt->pkt.user_id->ref = 1; + strcpy(pkt->pkt.user_id->name, s); + add_kbnode( root, new_kbnode( pkt ) ); +} + +static void +do_add_key_flags (PKT_signature *sig, unsigned int use) +{ + byte buf[1]; + + buf[0] = 0; + + /* The spec says that all primary keys MUST be able to certify. */ + if(sig->sig_class!=0x18) + buf[0] |= 0x01; + + if (use & PUBKEY_USAGE_SIG) + buf[0] |= 0x02; + if (use & PUBKEY_USAGE_ENC) + buf[0] |= 0x04 | 0x08; + if (use & PUBKEY_USAGE_AUTH) + buf[0] |= 0x20; + + if (!buf[0]) + return; + + build_sig_subpkt (sig, SIGSUBPKT_KEY_FLAGS, buf, 1); +} + + +int +keygen_add_key_expire( PKT_signature *sig, void *opaque ) +{ + PKT_public_key *pk = opaque; + byte buf[8]; + u32 u; + + if( pk->expiredate ) { + if(pk->expiredate > pk->timestamp) + u= pk->expiredate - pk->timestamp; + else + u= 1; + + buf[0] = (u >> 24) & 0xff; + buf[1] = (u >> 16) & 0xff; + buf[2] = (u >> 8) & 0xff; + buf[3] = u & 0xff; + build_sig_subpkt( sig, SIGSUBPKT_KEY_EXPIRE, buf, 4 ); + } + else + { + /* Make sure we don't leave a key expiration subpacket lying + around */ + delete_sig_subpkt (sig->hashed, SIGSUBPKT_KEY_EXPIRE); + } + + return 0; +} + +static int +keygen_add_key_flags_and_expire (PKT_signature *sig, void *opaque) +{ + struct opaque_data_usage_and_pk *oduap = opaque; + + do_add_key_flags (sig, oduap->usage); + return keygen_add_key_expire (sig, oduap->pk); +} + +static int +set_one_pref (int val, int type, const char *item, byte *buf, int *nbuf) +{ + int i; + + for (i=0; i < *nbuf; i++ ) + if (buf[i] == val) + { + log_info (_("preference `%s' duplicated\n"), item); + return -1; + } + + if (*nbuf >= MAX_PREFS) + { + if(type==1) + log_info(_("too many cipher preferences\n")); + else if(type==2) + log_info(_("too many digest preferences\n")); + else if(type==3) + log_info(_("too many compression preferences\n")); + else + BUG(); + + return -1; + } + + buf[(*nbuf)++] = val; + return 0; +} + +/* + * Parse the supplied string and use it to set the standard + * preferences. The string may be in a form like the one printed by + * "pref" (something like: "S10 S3 H3 H2 Z2 Z1") or the actual + * cipher/hash/compress names. Use NULL to set the default + * preferences. Returns: 0 = okay + */ +int +keygen_set_std_prefs (const char *string,int personal) +{ + byte sym[MAX_PREFS], hash[MAX_PREFS], zip[MAX_PREFS]; + int nsym=0, nhash=0, nzip=0, val, rc=0; + int mdc=1, modify=0; /* mdc defaults on, modify defaults off. */ + char dummy_string[45+1]; /* Enough for 15 items. */ + + if (!string || !ascii_strcasecmp (string, "default")) + { + if (opt.def_preference_list) + string=opt.def_preference_list; + else + { + dummy_string[0]='\0'; + + /* The rationale why we use the order AES256,192,128 is + for compatibility reasons with PGP. If gpg would + define AES128 first, we would get the somewhat + confusing situation: + + gpg -r pgpkey -r gpgkey ---gives--> AES256 + gpg -r gpgkey -r pgpkey ---gives--> AES + + Note that by using --personal-cipher-preferences it is + possible to prefer AES128. + */ + + /* Make sure we do not add more than 15 items here, as we + could overflow the size of dummy_string. We currently + have at most 12. */ + if(!check_cipher_algo(CIPHER_ALGO_AES256)) + strcat(dummy_string,"S9 "); + if(!check_cipher_algo(CIPHER_ALGO_AES192)) + strcat(dummy_string,"S8 "); + if(!check_cipher_algo(CIPHER_ALGO_AES)) + strcat(dummy_string,"S7 "); + if(!check_cipher_algo(CIPHER_ALGO_CAST5)) + strcat(dummy_string,"S3 "); + strcat(dummy_string,"S2 "); /* 3DES */ + /* If we have it, IDEA goes *after* 3DES so it won't be + used unless we're encrypting along with a V3 key. + Ideally, we would only put the S1 preference in if the + key was RSA and <=2048 bits, as that is what won't + break PGP2, but that is difficult with the current + code, and not really worth checking as a non-RSA <=2048 + bit key wouldn't be usable by PGP2 anyway. -dms */ + if(!check_cipher_algo(CIPHER_ALGO_IDEA)) + strcat(dummy_string,"S1 "); + + /* SHA-1 */ + strcat(dummy_string,"H2 "); + + if(!check_digest_algo(DIGEST_ALGO_SHA256)) + strcat(dummy_string,"H8 "); + + /* RIPEMD160 */ + strcat(dummy_string,"H3 "); + + /* ZLIB */ + strcat(dummy_string,"Z2 "); + + if(!check_compress_algo(COMPRESS_ALGO_BZIP2)) + strcat(dummy_string,"Z3 "); + + /* ZIP */ + strcat(dummy_string,"Z1"); + + string=dummy_string; + } + } + else if (!ascii_strcasecmp (string, "none")) + string = ""; + + if(strlen(string)) + { + char *tok,*prefstring; + + prefstring=xstrdup(string); /* need a writable string! */ + + while((tok=strsep(&prefstring," ,"))) + { + if((val=string_to_cipher_algo(tok))) + { + if(set_one_pref(val,1,tok,sym,&nsym)) + rc=-1; + } + else if((val=string_to_digest_algo(tok))) + { + if(set_one_pref(val,2,tok,hash,&nhash)) + rc=-1; + } + else if((val=string_to_compress_algo(tok))>-1) + { + if(set_one_pref(val,3,tok,zip,&nzip)) + rc=-1; + } + else if (ascii_strcasecmp(tok,"mdc")==0) + mdc=1; + else if (ascii_strcasecmp(tok,"no-mdc")==0) + mdc=0; + else if (ascii_strcasecmp(tok,"ks-modify")==0) + modify=1; + else if (ascii_strcasecmp(tok,"no-ks-modify")==0) + modify=0; + else + { + log_info (_("invalid item `%s' in preference string\n"),tok); + + /* Complain if IDEA is not available. */ + if(ascii_strcasecmp(tok,"s1")==0 + || ascii_strcasecmp(tok,"idea")==0) + idea_cipher_warn(1); + + rc=-1; + } + } + + xfree(prefstring); + } + + if(!rc) + { + if(personal) + { + if(personal==PREFTYPE_SYM) + { + xfree(opt.personal_cipher_prefs); + + if(nsym==0) + opt.personal_cipher_prefs=NULL; + else + { + int i; + + opt.personal_cipher_prefs= + xmalloc(sizeof(prefitem_t *)*(nsym+1)); + + for (i=0; i<nsym; i++) + { + opt.personal_cipher_prefs[i].type = PREFTYPE_SYM; + opt.personal_cipher_prefs[i].value = sym[i]; + } + + opt.personal_cipher_prefs[i].type = PREFTYPE_NONE; + opt.personal_cipher_prefs[i].value = 0; + } + } + else if(personal==PREFTYPE_HASH) + { + xfree(opt.personal_digest_prefs); + + if(nhash==0) + opt.personal_digest_prefs=NULL; + else + { + int i; + + opt.personal_digest_prefs= + xmalloc(sizeof(prefitem_t *)*(nhash+1)); + + for (i=0; i<nhash; i++) + { + opt.personal_digest_prefs[i].type = PREFTYPE_HASH; + opt.personal_digest_prefs[i].value = hash[i]; + } + + opt.personal_digest_prefs[i].type = PREFTYPE_NONE; + opt.personal_digest_prefs[i].value = 0; + } + } + else if(personal==PREFTYPE_ZIP) + { + xfree(opt.personal_compress_prefs); + + if(nzip==0) + opt.personal_compress_prefs=NULL; + else + { + int i; + + opt.personal_compress_prefs= + xmalloc(sizeof(prefitem_t *)*(nzip+1)); + + for (i=0; i<nzip; i++) + { + opt.personal_compress_prefs[i].type = PREFTYPE_ZIP; + opt.personal_compress_prefs[i].value = zip[i]; + } + + opt.personal_compress_prefs[i].type = PREFTYPE_NONE; + opt.personal_compress_prefs[i].value = 0; + } + } + } + else + { + memcpy (sym_prefs, sym, (nsym_prefs=nsym)); + memcpy (hash_prefs, hash, (nhash_prefs=nhash)); + memcpy (zip_prefs, zip, (nzip_prefs=nzip)); + mdc_available = mdc; + ks_modify = modify; + prefs_initialized = 1; + } + } + + return rc; +} + +/* Return a fake user ID containing the preferences. Caller must + free. */ +PKT_user_id *keygen_get_std_prefs(void) +{ + int i,j=0; + PKT_user_id *uid=xmalloc_clear(sizeof(PKT_user_id)); + + if(!prefs_initialized) + keygen_set_std_prefs(NULL,0); + + uid->ref=1; + + uid->prefs=xmalloc((sizeof(prefitem_t *)* + (nsym_prefs+nhash_prefs+nzip_prefs+1))); + + for(i=0;i<nsym_prefs;i++,j++) + { + uid->prefs[j].type=PREFTYPE_SYM; + uid->prefs[j].value=sym_prefs[i]; + } + + for(i=0;i<nhash_prefs;i++,j++) + { + uid->prefs[j].type=PREFTYPE_HASH; + uid->prefs[j].value=hash_prefs[i]; + } + + for(i=0;i<nzip_prefs;i++,j++) + { + uid->prefs[j].type=PREFTYPE_ZIP; + uid->prefs[j].value=zip_prefs[i]; + } + + uid->prefs[j].type=PREFTYPE_NONE; + uid->prefs[j].value=0; + + uid->flags.mdc=mdc_available; + uid->flags.ks_modify=ks_modify; + + return uid; +} + +static void +add_feature_mdc (PKT_signature *sig,int enabled) +{ + const byte *s; + size_t n; + int i; + char *buf; + + s = parse_sig_subpkt (sig->hashed, SIGSUBPKT_FEATURES, &n ); + /* Already set or cleared */ + if (s && n && + ((enabled && (s[0] & 0x01)) || (!enabled && !(s[0] & 0x01)))) + return; + + if (!s || !n) { /* create a new one */ + n = 1; + buf = xmalloc_clear (n); + } + else { + buf = xmalloc (n); + memcpy (buf, s, n); + } + + if(enabled) + buf[0] |= 0x01; /* MDC feature */ + else + buf[0] &= ~0x01; + + /* Are there any bits set? */ + for(i=0;i<n;i++) + if(buf[i]!=0) + break; + + if(i==n) + delete_sig_subpkt (sig->hashed, SIGSUBPKT_FEATURES); + else + build_sig_subpkt (sig, SIGSUBPKT_FEATURES, buf, n); + + xfree (buf); +} + +static void +add_keyserver_modify (PKT_signature *sig,int enabled) +{ + const byte *s; + size_t n; + int i; + char *buf; + + /* The keyserver modify flag is a negative flag (i.e. no-modify) */ + enabled=!enabled; + + s = parse_sig_subpkt (sig->hashed, SIGSUBPKT_KS_FLAGS, &n ); + /* Already set or cleared */ + if (s && n && + ((enabled && (s[0] & 0x80)) || (!enabled && !(s[0] & 0x80)))) + return; + + if (!s || !n) { /* create a new one */ + n = 1; + buf = xmalloc_clear (n); + } + else { + buf = xmalloc (n); + memcpy (buf, s, n); + } + + if(enabled) + buf[0] |= 0x80; /* no-modify flag */ + else + buf[0] &= ~0x80; + + /* Are there any bits set? */ + for(i=0;i<n;i++) + if(buf[i]!=0) + break; + + if(i==n) + delete_sig_subpkt (sig->hashed, SIGSUBPKT_KS_FLAGS); + else + build_sig_subpkt (sig, SIGSUBPKT_KS_FLAGS, buf, n); + + xfree (buf); +} + +int +keygen_upd_std_prefs( PKT_signature *sig, void *opaque ) +{ + if (!prefs_initialized) + keygen_set_std_prefs (NULL, 0); + + if (nsym_prefs) + build_sig_subpkt (sig, SIGSUBPKT_PREF_SYM, sym_prefs, nsym_prefs); + else + { + delete_sig_subpkt (sig->hashed, SIGSUBPKT_PREF_SYM); + delete_sig_subpkt (sig->unhashed, SIGSUBPKT_PREF_SYM); + } + + if (nhash_prefs) + build_sig_subpkt (sig, SIGSUBPKT_PREF_HASH, hash_prefs, nhash_prefs); + else + { + delete_sig_subpkt (sig->hashed, SIGSUBPKT_PREF_HASH); + delete_sig_subpkt (sig->unhashed, SIGSUBPKT_PREF_HASH); + } + + if (nzip_prefs) + build_sig_subpkt (sig, SIGSUBPKT_PREF_COMPR, zip_prefs, nzip_prefs); + else + { + delete_sig_subpkt (sig->hashed, SIGSUBPKT_PREF_COMPR); + delete_sig_subpkt (sig->unhashed, SIGSUBPKT_PREF_COMPR); + } + + /* Make sure that the MDC feature flag is set if needed */ + add_feature_mdc (sig,mdc_available); + add_keyserver_modify (sig,ks_modify); + keygen_add_keyserver_url(sig,NULL); + + return 0; +} + + +/**************** + * Add preference to the self signature packet. + * This is only called for packets with version > 3. + + */ +int +keygen_add_std_prefs( PKT_signature *sig, void *opaque ) +{ + PKT_public_key *pk = opaque; + + do_add_key_flags (sig, pk->pubkey_usage); + keygen_add_key_expire( sig, opaque ); + keygen_upd_std_prefs (sig, opaque); + keygen_add_keyserver_url(sig,NULL); + + return 0; +} + +int +keygen_add_keyserver_url(PKT_signature *sig, void *opaque) +{ + const char *url=opaque; + + if(!url) + url=opt.def_keyserver_url; + + if(url) + build_sig_subpkt(sig,SIGSUBPKT_PREF_KS,url,strlen(url)); + else + delete_sig_subpkt (sig->hashed,SIGSUBPKT_PREF_KS); + + return 0; +} + +int +keygen_add_notations(PKT_signature *sig,void *opaque) +{ + struct notation *notation; + + /* We always start clean */ + delete_sig_subpkt(sig->hashed,SIGSUBPKT_NOTATION); + delete_sig_subpkt(sig->unhashed,SIGSUBPKT_NOTATION); + sig->flags.notation=0; + + for(notation=opaque;notation;notation=notation->next) + if(!notation->flags.ignore) + { + unsigned char *buf; + unsigned int n1,n2; + + n1=strlen(notation->name); + if(notation->altvalue) + n2=strlen(notation->altvalue); + else if(notation->bdat) + n2=notation->blen; + else + n2=strlen(notation->value); + + buf = xmalloc( 8 + n1 + n2 ); + + /* human readable or not */ + buf[0] = notation->bdat?0:0x80; + buf[1] = buf[2] = buf[3] = 0; + buf[4] = n1 >> 8; + buf[5] = n1; + buf[6] = n2 >> 8; + buf[7] = n2; + memcpy(buf+8, notation->name, n1 ); + if(notation->altvalue) + memcpy(buf+8+n1, notation->altvalue, n2 ); + else if(notation->bdat) + memcpy(buf+8+n1, notation->bdat, n2 ); + else + memcpy(buf+8+n1, notation->value, n2 ); + build_sig_subpkt( sig, SIGSUBPKT_NOTATION | + (notation->flags.critical?SIGSUBPKT_FLAG_CRITICAL:0), + buf, 8+n1+n2 ); + xfree(buf); + } + + return 0; +} + +int +keygen_add_revkey(PKT_signature *sig, void *opaque) +{ + struct revocation_key *revkey=opaque; + byte buf[2+MAX_FINGERPRINT_LEN]; + + buf[0]=revkey->class; + buf[1]=revkey->algid; + memcpy(&buf[2],revkey->fpr,MAX_FINGERPRINT_LEN); + + build_sig_subpkt(sig,SIGSUBPKT_REV_KEY,buf,2+MAX_FINGERPRINT_LEN); + + /* All sigs with revocation keys set are nonrevocable */ + sig->flags.revocable=0; + buf[0] = 0; + build_sig_subpkt( sig, SIGSUBPKT_REVOCABLE, buf, 1 ); + + parse_revkeys(sig); + + return 0; +} + +int +make_backsig(PKT_signature *sig,PKT_public_key *pk, + PKT_public_key *sub_pk,PKT_secret_key *sub_sk) +{ + PKT_signature *backsig; + int rc; + + cache_public_key(sub_pk); + + rc=make_keysig_packet(&backsig,pk,NULL,sub_pk,sub_sk,0x19,0,0,0,0,NULL,NULL); + if(rc) + log_error("make_keysig_packet failed for backsig: %s\n",g10_errstr(rc)); + else + { + /* get it into a binary packed form. */ + IOBUF backsig_out=iobuf_temp(); + PACKET backsig_pkt; + + init_packet(&backsig_pkt); + backsig_pkt.pkttype=PKT_SIGNATURE; + backsig_pkt.pkt.signature=backsig; + rc=build_packet(backsig_out,&backsig_pkt); + free_packet(&backsig_pkt); + if(rc) + log_error("build_packet failed for backsig: %s\n",g10_errstr(rc)); + else + { + size_t pktlen=0; + byte *buf=iobuf_get_temp_buffer(backsig_out); + + /* Remove the packet header */ + if(buf[0]&0x40) + { + if(buf[1]<192) + { + pktlen=buf[1]; + buf+=2; + } + else if(buf[1]<224) + { + pktlen=(buf[1]-192)*256; + pktlen+=buf[2]+192; + buf+=3; + } + else if(buf[1]==255) + { + pktlen =buf[2] << 24; + pktlen|=buf[3] << 16; + pktlen|=buf[4] << 8; + pktlen|=buf[5]; + buf+=6; + } + else + BUG(); + } + else + { + int mark=1; + + switch(buf[0]&3) + { + case 3: + BUG(); + break; + + case 2: + pktlen =buf[mark++] << 24; + pktlen|=buf[mark++] << 16; + + case 1: + pktlen|=buf[mark++] << 8; + + case 0: + pktlen|=buf[mark++]; + } + + buf+=mark; + } + + /* now make the binary blob into a subpacket */ + build_sig_subpkt(sig,SIGSUBPKT_SIGNATURE,buf,pktlen); + + iobuf_close(backsig_out); + } + } + + return rc; +} + + +static int +write_direct_sig( KBNODE root, KBNODE pub_root, PKT_secret_key *sk, + struct revocation_key *revkey ) +{ + PACKET *pkt; + PKT_signature *sig; + int rc=0; + KBNODE node; + PKT_public_key *pk; + + if( opt.verbose ) + log_info(_("writing direct signature\n")); + + /* get the pk packet from the pub_tree */ + node = find_kbnode( pub_root, PKT_PUBLIC_KEY ); + if( !node ) + BUG(); + pk = node->pkt->pkt.public_key; + + /* we have to cache the key, so that the verification of the signature + * creation is able to retrieve the public key */ + cache_public_key (pk); + + /* and make the signature */ + rc = make_keysig_packet(&sig,pk,NULL,NULL,sk,0x1F,0,0,0,0, + keygen_add_revkey,revkey); + if( rc ) { + log_error("make_keysig_packet failed: %s\n", g10_errstr(rc) ); + return rc; + } + + pkt = xmalloc_clear( sizeof *pkt ); + pkt->pkttype = PKT_SIGNATURE; + pkt->pkt.signature = sig; + add_kbnode( root, new_kbnode( pkt ) ); + return rc; +} + +static int +write_selfsigs( KBNODE sec_root, KBNODE pub_root, PKT_secret_key *sk, + unsigned int use ) +{ + PACKET *pkt; + PKT_signature *sig; + PKT_user_id *uid; + int rc=0; + KBNODE node; + PKT_public_key *pk; + + if( opt.verbose ) + log_info(_("writing self signature\n")); + + /* get the uid packet from the list */ + node = find_kbnode( pub_root, PKT_USER_ID ); + if( !node ) + BUG(); /* no user id packet in tree */ + uid = node->pkt->pkt.user_id; + /* get the pk packet from the pub_tree */ + node = find_kbnode( pub_root, PKT_PUBLIC_KEY ); + if( !node ) + BUG(); + pk = node->pkt->pkt.public_key; + pk->pubkey_usage = use; + /* we have to cache the key, so that the verification of the signature + * creation is able to retrieve the public key */ + cache_public_key (pk); + + /* and make the signature */ + rc = make_keysig_packet( &sig, pk, uid, NULL, sk, 0x13, 0, 0, 0, 0, + keygen_add_std_prefs, pk ); + if( rc ) { + log_error("make_keysig_packet failed: %s\n", g10_errstr(rc) ); + return rc; + } + + pkt = xmalloc_clear( sizeof *pkt ); + pkt->pkttype = PKT_SIGNATURE; + pkt->pkt.signature = sig; + add_kbnode( sec_root, new_kbnode( pkt ) ); + + pkt = xmalloc_clear( sizeof *pkt ); + pkt->pkttype = PKT_SIGNATURE; + pkt->pkt.signature = copy_signature(NULL,sig); + add_kbnode( pub_root, new_kbnode( pkt ) ); + return rc; +} + +static int +write_keybinding( KBNODE root, KBNODE pub_root, + PKT_secret_key *pri_sk, PKT_secret_key *sub_sk, + unsigned int use ) +{ + PACKET *pkt; + PKT_signature *sig; + int rc=0; + KBNODE node; + PKT_public_key *pri_pk, *sub_pk; + struct opaque_data_usage_and_pk oduap; + + if( opt.verbose ) + log_info(_("writing key binding signature\n")); + + /* get the pk packet from the pub_tree */ + node = find_kbnode( pub_root, PKT_PUBLIC_KEY ); + if( !node ) + BUG(); + pri_pk = node->pkt->pkt.public_key; + /* we have to cache the key, so that the verification of the signature + * creation is able to retrieve the public key */ + cache_public_key (pri_pk); + + /* find the last subkey */ + sub_pk = NULL; + for(node=pub_root; node; node = node->next ) { + if( node->pkt->pkttype == PKT_PUBLIC_SUBKEY ) + sub_pk = node->pkt->pkt.public_key; + } + if( !sub_pk ) + BUG(); + + /* and make the signature */ + oduap.usage = use; + oduap.pk = sub_pk; + rc=make_keysig_packet(&sig, pri_pk, NULL, sub_pk, pri_sk, 0x18, 0, 0, 0, 0, + keygen_add_key_flags_and_expire, &oduap ); + if( rc ) { + log_error("make_keysig_packet failed: %s\n", g10_errstr(rc) ); + return rc; + } + + /* make a backsig */ + if(use&PUBKEY_USAGE_SIG) + { + rc=make_backsig(sig,pri_pk,sub_pk,sub_sk); + if(rc) + return rc; + } + + pkt = xmalloc_clear( sizeof *pkt ); + pkt->pkttype = PKT_SIGNATURE; + pkt->pkt.signature = sig; + add_kbnode( root, new_kbnode( pkt ) ); + return rc; +} + + +static int +gen_elg(int algo, unsigned nbits, KBNODE pub_root, KBNODE sec_root, DEK *dek, + STRING2KEY *s2k, PKT_secret_key **ret_sk, u32 expireval, int is_subkey) +{ + int rc; + PACKET *pkt; + PKT_secret_key *sk; + PKT_public_key *pk; + MPI skey[4]; + MPI *factors; + + assert( is_ELGAMAL(algo) ); + + if( nbits < 512 ) { + nbits = 1024; + log_info(_("keysize invalid; using %u bits\n"), nbits ); + } + + if( (nbits % 32) ) { + nbits = ((nbits + 31) / 32) * 32; + log_info(_("keysize rounded up to %u bits\n"), nbits ); + } + + rc = pubkey_generate( algo, nbits, skey, &factors ); + if( rc ) { + log_error("pubkey_generate failed: %s\n", g10_errstr(rc) ); + return rc; + } + + sk = xmalloc_clear( sizeof *sk ); + pk = xmalloc_clear( sizeof *pk ); + sk->timestamp = pk->timestamp = make_timestamp(); + sk->version = pk->version = 4; + if( expireval ) { + sk->expiredate = pk->expiredate = sk->timestamp + expireval; + } + sk->pubkey_algo = pk->pubkey_algo = algo; + pk->pkey[0] = mpi_copy( skey[0] ); + pk->pkey[1] = mpi_copy( skey[1] ); + pk->pkey[2] = mpi_copy( skey[2] ); + sk->skey[0] = skey[0]; + sk->skey[1] = skey[1]; + sk->skey[2] = skey[2]; + sk->skey[3] = skey[3]; + sk->is_protected = 0; + sk->protect.algo = 0; + + sk->csum = checksum_mpi( sk->skey[3] ); + if( ret_sk ) /* return an unprotected version of the sk */ + *ret_sk = copy_secret_key( NULL, sk ); + + if( dek ) { + sk->protect.algo = dek->algo; + sk->protect.s2k = *s2k; + rc = protect_secret_key( sk, dek ); + if( rc ) { + log_error("protect_secret_key failed: %s\n", g10_errstr(rc) ); + free_public_key(pk); + free_secret_key(sk); + return rc; + } + } + + pkt = xmalloc_clear(sizeof *pkt); + pkt->pkttype = is_subkey ? PKT_PUBLIC_SUBKEY : PKT_PUBLIC_KEY; + pkt->pkt.public_key = pk; + add_kbnode(pub_root, new_kbnode( pkt )); + + /* don't know whether it makes sense to have the factors, so for now + * we store them in the secret keyring (but they are not secret) */ + pkt = xmalloc_clear(sizeof *pkt); + pkt->pkttype = is_subkey ? PKT_SECRET_SUBKEY : PKT_SECRET_KEY; + pkt->pkt.secret_key = sk; + add_kbnode(sec_root, new_kbnode( pkt )); + + return 0; +} + + +/**************** + * Generate a DSA key + */ +static int +gen_dsa(unsigned int nbits, KBNODE pub_root, KBNODE sec_root, DEK *dek, + STRING2KEY *s2k, PKT_secret_key **ret_sk, u32 expireval, int is_subkey) +{ + int rc; + PACKET *pkt; + PKT_secret_key *sk; + PKT_public_key *pk; + MPI skey[5]; + MPI *factors; + unsigned int qbits; + + if( nbits < 512 || (!opt.flags.dsa2 && nbits > 1024)) + { + nbits = 1024; + log_info(_("keysize invalid; using %u bits\n"), nbits ); + } + else if(nbits>3072) + { + nbits = 3072; + log_info(_("keysize invalid; using %u bits\n"), nbits ); + } + + if(nbits % 64) + { + nbits = ((nbits + 63) / 64) * 64; + log_info(_("keysize rounded up to %u bits\n"), nbits ); + } + + /* + Figure out a q size based on the key size. FIPS 180-3 says: + + L = 1024, N = 160 + L = 2048, N = 224 + L = 2048, N = 256 + L = 3072, N = 256 + + 2048/256 is an odd pair since there is also a 2048/224 and + 3072/256. Matching sizes is not a very exact science. + + We'll do 256 qbits for nbits over 2048, 224 for nbits over 1024 + but less than 2048, and 160 for 1024 (DSA1). + */ + + if(nbits>2048) + qbits=256; + else if(nbits>1024) + qbits=224; + else + qbits=160; + + if(qbits!=160) + log_info("WARNING: some OpenPGP programs can't" + " handle a DSA key with this digest size\n"); + + rc = dsa2_generate( PUBKEY_ALGO_DSA, nbits, qbits, skey, &factors ); + if( rc ) + { + log_error("dsa2_generate failed: %s\n", g10_errstr(rc) ); + return rc; + } + + sk = xmalloc_clear( sizeof *sk ); + pk = xmalloc_clear( sizeof *pk ); + sk->timestamp = pk->timestamp = make_timestamp(); + sk->version = pk->version = 4; + if( expireval ) + sk->expiredate = pk->expiredate = sk->timestamp + expireval; + + sk->pubkey_algo = pk->pubkey_algo = PUBKEY_ALGO_DSA; + pk->pkey[0] = mpi_copy( skey[0] ); + pk->pkey[1] = mpi_copy( skey[1] ); + pk->pkey[2] = mpi_copy( skey[2] ); + pk->pkey[3] = mpi_copy( skey[3] ); + sk->skey[0] = skey[0]; + sk->skey[1] = skey[1]; + sk->skey[2] = skey[2]; + sk->skey[3] = skey[3]; + sk->skey[4] = skey[4]; + sk->is_protected = 0; + sk->protect.algo = 0; + + sk->csum = checksum_mpi ( sk->skey[4] ); + if( ret_sk ) /* return an unprotected version of the sk */ + *ret_sk = copy_secret_key( NULL, sk ); + + if( dek ) { + sk->protect.algo = dek->algo; + sk->protect.s2k = *s2k; + rc = protect_secret_key( sk, dek ); + if( rc ) { + log_error("protect_secret_key failed: %s\n", g10_errstr(rc) ); + free_public_key(pk); + free_secret_key(sk); + return rc; + } + } + + pkt = xmalloc_clear(sizeof *pkt); + pkt->pkttype = is_subkey ? PKT_PUBLIC_SUBKEY : PKT_PUBLIC_KEY; + pkt->pkt.public_key = pk; + add_kbnode(pub_root, new_kbnode( pkt )); + + /* don't know whether it makes sense to have the factors, so for now + * we store them in the secret keyring (but they are not secret) + * p = 2 * q * f1 * f2 * ... * fn + * We store only f1 to f_n-1; fn can be calculated because p and q + * are known. + */ + pkt = xmalloc_clear(sizeof *pkt); + pkt->pkttype = is_subkey ? PKT_SECRET_SUBKEY : PKT_SECRET_KEY; + pkt->pkt.secret_key = sk; + add_kbnode(sec_root, new_kbnode( pkt )); + + return 0; +} + + +/* + * Generate an RSA key. + */ +static int +gen_rsa(int algo, unsigned nbits, KBNODE pub_root, KBNODE sec_root, DEK *dek, + STRING2KEY *s2k, PKT_secret_key **ret_sk, u32 expireval, int is_subkey) +{ + int rc; + PACKET *pkt; + PKT_secret_key *sk; + PKT_public_key *pk; + MPI skey[6]; + MPI *factors; + + assert( is_RSA(algo) ); + + if( nbits < 1024 ) { + nbits = 1024; + log_info(_("keysize invalid; using %u bits\n"), nbits ); + } + + if( (nbits % 32) ) { + nbits = ((nbits + 31) / 32) * 32; + log_info(_("keysize rounded up to %u bits\n"), nbits ); + } + + rc = pubkey_generate( algo, nbits, skey, &factors ); + if( rc ) { + log_error("pubkey_generate failed: %s\n", g10_errstr(rc) ); + return rc; + } + + sk = xmalloc_clear( sizeof *sk ); + pk = xmalloc_clear( sizeof *pk ); + sk->timestamp = pk->timestamp = make_timestamp(); + sk->version = pk->version = 4; + if( expireval ) { + sk->expiredate = pk->expiredate = sk->timestamp + expireval; + } + sk->pubkey_algo = pk->pubkey_algo = algo; + pk->pkey[0] = mpi_copy( skey[0] ); + pk->pkey[1] = mpi_copy( skey[1] ); + sk->skey[0] = skey[0]; + sk->skey[1] = skey[1]; + sk->skey[2] = skey[2]; + sk->skey[3] = skey[3]; + sk->skey[4] = skey[4]; + sk->skey[5] = skey[5]; + sk->is_protected = 0; + sk->protect.algo = 0; + + sk->csum = checksum_mpi (sk->skey[2] ); + sk->csum += checksum_mpi (sk->skey[3] ); + sk->csum += checksum_mpi (sk->skey[4] ); + sk->csum += checksum_mpi (sk->skey[5] ); + if( ret_sk ) /* return an unprotected version of the sk */ + *ret_sk = copy_secret_key( NULL, sk ); + + if( dek ) { + sk->protect.algo = dek->algo; + sk->protect.s2k = *s2k; + rc = protect_secret_key( sk, dek ); + if( rc ) { + log_error("protect_secret_key failed: %s\n", g10_errstr(rc) ); + free_public_key(pk); + free_secret_key(sk); + return rc; + } + } + + pkt = xmalloc_clear(sizeof *pkt); + pkt->pkttype = is_subkey ? PKT_PUBLIC_SUBKEY : PKT_PUBLIC_KEY; + pkt->pkt.public_key = pk; + add_kbnode(pub_root, new_kbnode( pkt )); + + pkt = xmalloc_clear(sizeof *pkt); + pkt->pkttype = is_subkey ? PKT_SECRET_SUBKEY : PKT_SECRET_KEY; + pkt->pkt.secret_key = sk; + add_kbnode(sec_root, new_kbnode( pkt )); + + return 0; +} + + +/**************** + * check valid days: + * return 0 on error or the multiplier + */ +static int +check_valid_days( const char *s ) +{ + if( !digitp(s) ) + return 0; + for( s++; *s; s++) + if( !digitp(s) ) + break; + if( !*s ) + return 1; + if( s[1] ) + return 0; /* e.g. "2323wc" */ + if( *s == 'd' || *s == 'D' ) + return 1; + if( *s == 'w' || *s == 'W' ) + return 7; + if( *s == 'm' || *s == 'M' ) + return 30; + if( *s == 'y' || *s == 'Y' ) + return 365; + return 0; +} + + +static void +print_key_flags(int flags) +{ + if(flags&PUBKEY_USAGE_SIG) + tty_printf("%s ",_("Sign")); + + if(flags&PUBKEY_USAGE_CERT) + tty_printf("%s ",_("Certify")); + + if(flags&PUBKEY_USAGE_ENC) + tty_printf("%s ",_("Encrypt")); + + if(flags&PUBKEY_USAGE_AUTH) + tty_printf("%s ",_("Authenticate")); +} + + +/* Returns the key flags */ +static unsigned int +ask_key_flags(int algo,int subkey) +{ + const char *togglers=_("SsEeAaQq"); + char *answer=NULL; + unsigned int current=0; + unsigned int possible=openpgp_pk_algo_usage(algo); + + if(strlen(togglers)!=8) + BUG(); + + /* Only primary keys may certify. */ + if(subkey) + possible&=~PUBKEY_USAGE_CERT; + + /* Preload the current set with the possible set, minus + authentication, since nobody really uses auth yet. */ + current=possible&~PUBKEY_USAGE_AUTH; + + for(;;) + { + tty_printf("\n"); + tty_printf(_("Possible actions for a %s key: "), + pubkey_algo_to_string(algo)); + print_key_flags(possible); + tty_printf("\n"); + tty_printf(_("Current allowed actions: ")); + print_key_flags(current); + tty_printf("\n\n"); + + if(possible&PUBKEY_USAGE_SIG) + tty_printf(_(" (%c) Toggle the sign capability\n"), + togglers[0]); + if(possible&PUBKEY_USAGE_ENC) + tty_printf(_(" (%c) Toggle the encrypt capability\n"), + togglers[2]); + if(possible&PUBKEY_USAGE_AUTH) + tty_printf(_(" (%c) Toggle the authenticate capability\n"), + togglers[4]); + + tty_printf(_(" (%c) Finished\n"),togglers[6]); + tty_printf("\n"); + + xfree(answer); + answer = cpr_get("keygen.flags",_("Your selection? ")); + cpr_kill_prompt(); + + if(strlen(answer)>1) + tty_printf(_("Invalid selection.\n")); + else if(*answer=='\0' || *answer==togglers[6] || *answer==togglers[7]) + break; + else if((*answer==togglers[0] || *answer==togglers[1]) + && possible&PUBKEY_USAGE_SIG) + { + if(current&PUBKEY_USAGE_SIG) + current&=~PUBKEY_USAGE_SIG; + else + current|=PUBKEY_USAGE_SIG; + } + else if((*answer==togglers[2] || *answer==togglers[3]) + && possible&PUBKEY_USAGE_ENC) + { + if(current&PUBKEY_USAGE_ENC) + current&=~PUBKEY_USAGE_ENC; + else + current|=PUBKEY_USAGE_ENC; + } + else if((*answer==togglers[4] || *answer==togglers[5]) + && possible&PUBKEY_USAGE_AUTH) + { + if(current&PUBKEY_USAGE_AUTH) + current&=~PUBKEY_USAGE_AUTH; + else + current|=PUBKEY_USAGE_AUTH; + } + else + tty_printf(_("Invalid selection.\n")); + } + + xfree(answer); + + return current; +} + + +/**************** + * Returns: 0 to create both a DSA and a Elgamal key. + * and only if key flags are to be written the desired usage. + */ +static int +ask_algo (int addmode, unsigned int *r_usage) +{ + char *answer; + int algo; + + *r_usage = 0; + tty_printf(_("Please select what kind of key you want:\n")); + if( !addmode ) + tty_printf(_(" (%d) DSA and Elgamal (default)\n"), 1 ); + tty_printf( _(" (%d) DSA (sign only)\n"), 2 ); + if (opt.expert) + tty_printf( _(" (%d) DSA (set your own capabilities)\n"), 3 ); + if( addmode ) + tty_printf(_(" (%d) Elgamal (encrypt only)\n"), 4 ); + tty_printf( _(" (%d) RSA (sign only)\n"), 5 ); + if (addmode) + tty_printf(_(" (%d) RSA (encrypt only)\n"), 6 ); + if (opt.expert) + tty_printf( _(" (%d) RSA (set your own capabilities)\n"), 7 ); + + for(;;) { + answer = cpr_get("keygen.algo",_("Your selection? ")); + cpr_kill_prompt(); + algo = *answer? atoi(answer): 1; + xfree(answer); + if( algo == 1 && !addmode ) { + algo = 0; /* create both keys */ + break; + } + else if( algo == 7 && opt.expert ) { + algo = PUBKEY_ALGO_RSA; + *r_usage=ask_key_flags(algo,addmode); + break; + } + else if( algo == 6 && addmode ) { + algo = PUBKEY_ALGO_RSA; + *r_usage = PUBKEY_USAGE_ENC; + break; + } + else if( algo == 5 ) { + algo = PUBKEY_ALGO_RSA; + *r_usage = PUBKEY_USAGE_SIG; + break; + } + else if( algo == 4 && addmode ) { + algo = PUBKEY_ALGO_ELGAMAL_E; + *r_usage = PUBKEY_USAGE_ENC; + break; + } + else if( algo == 3 && opt.expert ) { + algo = PUBKEY_ALGO_DSA; + *r_usage=ask_key_flags(algo,addmode); + break; + } + else if( algo == 2 ) { + algo = PUBKEY_ALGO_DSA; + *r_usage = PUBKEY_USAGE_SIG; + break; + } + else + tty_printf(_("Invalid selection.\n")); + } + + return algo; +} + + +static unsigned +ask_keysize( int algo ) +{ + unsigned nbits,min,def=2048,max=4096; + + if(opt.expert) + min=512; + else + min=1024; + + switch(algo) + { + case PUBKEY_ALGO_DSA: + if(opt.flags.dsa2) + { + def=1024; + max=3072; + } + else + { + tty_printf(_("DSA keypair will have %u bits.\n"),1024); + return 1024; + } + break; + + case PUBKEY_ALGO_RSA: + min=1024; + break; + } + + tty_printf(_("%s keys may be between %u and %u bits long.\n"), + pubkey_algo_to_string(algo),min,max); + + for(;;) + { + char *prompt,*answer; + +#define PROMPTSTRING _("What keysize do you want? (%u) ") + + prompt=xmalloc(strlen(PROMPTSTRING)+20); + sprintf(prompt,PROMPTSTRING,def); + +#undef PROMPTSTRING + + answer = cpr_get("keygen.size",prompt); + cpr_kill_prompt(); + nbits = *answer? atoi(answer): def; + xfree(prompt); + xfree(answer); + + if(nbits<min || nbits>max) + tty_printf(_("%s keysizes must be in the range %u-%u\n"), + pubkey_algo_to_string(algo),min,max); + else + break; + } + + tty_printf(_("Requested keysize is %u bits\n"), nbits ); + + if( algo == PUBKEY_ALGO_DSA && (nbits % 64) ) + { + nbits = ((nbits + 63) / 64) * 64; + tty_printf(_("rounded up to %u bits\n"), nbits ); + } + else if( (nbits % 32) ) + { + nbits = ((nbits + 31) / 32) * 32; + tty_printf(_("rounded up to %u bits\n"), nbits ); + } + + return nbits; +} + + +/**************** + * Parse an expire string and return its value in seconds. + * Returns (u32)-1 on error. + * This isn't perfect since scan_isodatestr returns unix time, and + * OpenPGP actually allows a 32-bit time *plus* a 32-bit offset. + * Because of this, we only permit setting expirations up to 2106, but + * OpenPGP could theoretically allow up to 2242. I think we'll all + * just cope for the next few years until we get a 64-bit time_t or + * similar. + */ +u32 +parse_expire_string( const char *string ) +{ + int mult; + u32 seconds,abs_date=0,curtime = make_timestamp(); + + if( !*string ) + seconds = 0; + else if ( !strncmp (string, "seconds=", 8) ) + seconds = atoi (string+8); + else if( (abs_date = scan_isodatestr(string)) && abs_date > curtime ) + seconds = abs_date - curtime; + else if( (mult=check_valid_days(string)) ) + seconds = atoi(string) * 86400L * mult; + else + seconds=(u32)-1; + + return seconds; +} + +/* object == 0 for a key, and 1 for a sig */ +u32 +ask_expire_interval(int object,const char *def_expire) +{ + u32 interval; + char *answer; + + switch(object) + { + case 0: + if(def_expire) + BUG(); + tty_printf(_("Please specify how long the key should be valid.\n" + " 0 = key does not expire\n" + " <n> = key expires in n days\n" + " <n>w = key expires in n weeks\n" + " <n>m = key expires in n months\n" + " <n>y = key expires in n years\n")); + break; + + case 1: + if(!def_expire) + BUG(); + tty_printf(_("Please specify how long the signature should be valid.\n" + " 0 = signature does not expire\n" + " <n> = signature expires in n days\n" + " <n>w = signature expires in n weeks\n" + " <n>m = signature expires in n months\n" + " <n>y = signature expires in n years\n")); + break; + + default: + BUG(); + } + + /* Note: The elgamal subkey for DSA has no expiration date because + * it must be signed with the DSA key and this one has the expiration + * date */ + + answer = NULL; + for(;;) + { + u32 curtime=make_timestamp(); + + xfree(answer); + if(object==0) + answer = cpr_get("keygen.valid",_("Key is valid for? (0) ")); + else + { + char *prompt; + +#define PROMPTSTRING _("Signature is valid for? (%s) ") + /* This will actually end up larger than necessary because + of the 2 bytes for '%s' */ + prompt=xmalloc(strlen(PROMPTSTRING)+strlen(def_expire)+1); + sprintf(prompt,PROMPTSTRING,def_expire); +#undef PROMPTSTRING + + answer = cpr_get("siggen.valid",prompt); + xfree(prompt); + + if(*answer=='\0') + answer=xstrdup(def_expire); + } + cpr_kill_prompt(); + trim_spaces(answer); + interval = parse_expire_string( answer ); + if( interval == (u32)-1 ) + { + tty_printf(_("invalid value\n")); + continue; + } + + if( !interval ) + { + tty_printf((object==0) + ? _("Key does not expire at all\n") + : _("Signature does not expire at all\n")); + } + else + { + tty_printf(object==0 + ? _("Key expires at %s\n") + : _("Signature expires at %s\n"), + asctimestamp((ulong)(curtime + interval) ) ); + /* FIXME: This check yields warning on alhas: Write a + configure check and to this check here only for 32 bit + machines */ + if( (time_t)((ulong)(curtime+interval)) < 0 ) + tty_printf(_("Your system can't display dates beyond 2038.\n" + "However, it will be correctly handled up to 2106.\n")); + } + + if( cpr_enabled() || cpr_get_answer_is_yes("keygen.valid.okay", + _("Is this correct? (y/N) ")) ) + break; + } + + xfree(answer); + return interval; +} + +u32 +ask_expiredate() +{ + u32 x = ask_expire_interval(0,NULL); + return x? make_timestamp() + x : 0; +} + + +static char * +ask_user_id( int mode ) +{ + char *answer; + char *aname, *acomment, *amail, *uid; + + if( !mode ) + tty_printf( _("\n" +"You need a user ID to identify your key; " + "the software constructs the user ID\n" +"from the Real Name, Comment and Email Address in this form:\n" +" \"Heinrich Heine (Der Dichter) <heinrichh@duesseldorf.de>\"\n\n") ); + uid = aname = acomment = amail = NULL; + for(;;) { + char *p; + int fail=0; + + if( !aname ) { + for(;;) { + xfree(aname); + aname = cpr_get("keygen.name",_("Real name: ")); + trim_spaces(aname); + cpr_kill_prompt(); + + if( opt.allow_freeform_uid ) + break; + + if( strpbrk( aname, "<>" ) ) + tty_printf(_("Invalid character in name\n")); + else if( digitp(aname) ) + tty_printf(_("Name may not start with a digit\n")); + else if( strlen(aname) < 5 ) + tty_printf(_("Name must be at least 5 characters long\n")); + else + break; + } + } + if( !amail ) { + for(;;) { + xfree(amail); + amail = cpr_get("keygen.email",_("Email address: ")); + trim_spaces(amail); + cpr_kill_prompt(); + if( !*amail || opt.allow_freeform_uid ) + break; /* no email address is okay */ + else if ( !is_valid_mailbox (amail) ) + tty_printf(_("Not a valid email address\n")); + else + break; + } + } + if( !acomment ) { + for(;;) { + xfree(acomment); + acomment = cpr_get("keygen.comment",_("Comment: ")); + trim_spaces(acomment); + cpr_kill_prompt(); + if( !*acomment ) + break; /* no comment is okay */ + else if( strpbrk( acomment, "()" ) ) + tty_printf(_("Invalid character in comment\n")); + else + break; + } + } + + + xfree(uid); + uid = p = xmalloc(strlen(aname)+strlen(amail)+strlen(acomment)+12+10); + p = stpcpy(p, aname ); + if( *acomment ) + p = stpcpy(stpcpy(stpcpy(p," ("), acomment),")"); + if( *amail ) + p = stpcpy(stpcpy(stpcpy(p," <"), amail),">"); + + /* append a warning if we do not have dev/random + * or it is switched into quick testmode */ + if( quick_random_gen(-1) ) + strcpy(p, " (INSECURE!)" ); + + /* print a note in case that UTF8 mapping has to be done */ + for(p=uid; *p; p++ ) { + if( *p & 0x80 ) { + tty_printf(_("You are using the `%s' character set.\n"), + get_native_charset() ); + break; + } + } + + tty_printf(_("You selected this USER-ID:\n \"%s\"\n\n"), uid); + /* fixme: add a warning if this user-id already exists */ + if( !*amail && !opt.allow_freeform_uid + && (strchr( aname, '@' ) || strchr( acomment, '@'))) { + fail = 1; + tty_printf(_("Please don't put the email address " + "into the real name or the comment\n") ); + } + + for(;;) { + /* TRANSLATORS: These are the allowed answers in + lower and uppercase. Below you will find the matching + string which should be translated accordingly and the + letter changed to match the one in the answer string. + + n = Change name + c = Change comment + e = Change email + o = Okay (ready, continue) + q = Quit + */ + const char *ansstr = _("NnCcEeOoQq"); + + if( strlen(ansstr) != 10 ) + BUG(); + if( cpr_enabled() ) { + answer = xstrdup(ansstr+6); + answer[1] = 0; + } + else { + answer = cpr_get("keygen.userid.cmd", fail? + _("Change (N)ame, (C)omment, (E)mail or (Q)uit? ") : + _("Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? ")); + cpr_kill_prompt(); + } + if( strlen(answer) > 1 ) + ; + else if( *answer == ansstr[0] || *answer == ansstr[1] ) { + xfree(aname); aname = NULL; + break; + } + else if( *answer == ansstr[2] || *answer == ansstr[3] ) { + xfree(acomment); acomment = NULL; + break; + } + else if( *answer == ansstr[4] || *answer == ansstr[5] ) { + xfree(amail); amail = NULL; + break; + } + else if( *answer == ansstr[6] || *answer == ansstr[7] ) { + if( fail ) { + tty_printf(_("Please correct the error first\n")); + } + else { + xfree(aname); aname = NULL; + xfree(acomment); acomment = NULL; + xfree(amail); amail = NULL; + break; + } + } + else if( *answer == ansstr[8] || *answer == ansstr[9] ) { + xfree(aname); aname = NULL; + xfree(acomment); acomment = NULL; + xfree(amail); amail = NULL; + xfree(uid); uid = NULL; + break; + } + xfree(answer); + } + xfree(answer); + if( !amail && !acomment && !amail ) + break; + xfree(uid); uid = NULL; + } + if( uid ) { + char *p = native_to_utf8( uid ); + xfree( uid ); + uid = p; + } + return uid; +} + + +/* FIXME: We need a way to cancel this prompt. */ +static DEK * +do_ask_passphrase( STRING2KEY **ret_s2k ) +{ + DEK *dek = NULL; + STRING2KEY *s2k; + const char *errtext = NULL; + + tty_printf(_("You need a Passphrase to protect your secret key.\n\n") ); + + s2k = xmalloc_secure( sizeof *s2k ); + for(;;) { + s2k->mode = opt.s2k_mode; + s2k->hash_algo = S2K_DIGEST_ALGO; + dek = passphrase_to_dek( NULL, 0, opt.s2k_cipher_algo, s2k,2, + errtext, NULL); + if( !dek ) { + errtext = N_("passphrase not correctly repeated; try again"); + tty_printf(_("%s.\n"), _(errtext)); + } + else if( !dek->keylen ) { + xfree(dek); dek = NULL; + xfree(s2k); s2k = NULL; + tty_printf(_( + "You don't want a passphrase - this is probably a *bad* idea!\n" + "I will do it anyway. You can change your passphrase at any time,\n" + "using this program with the option \"--edit-key\".\n\n")); + break; + } + else + break; /* okay */ + } + *ret_s2k = s2k; + return dek; +} + + +static int +do_create( int algo, unsigned int nbits, KBNODE pub_root, KBNODE sec_root, + DEK *dek, STRING2KEY *s2k, PKT_secret_key **sk, u32 expiredate, + int is_subkey ) +{ + int rc=0; + + if( !opt.batch ) + tty_printf(_( +"We need to generate a lot of random bytes. It is a good idea to perform\n" +"some other action (type on the keyboard, move the mouse, utilize the\n" +"disks) during the prime generation; this gives the random number\n" +"generator a better chance to gain enough entropy.\n") ); + + if( algo == PUBKEY_ALGO_ELGAMAL_E ) + rc = gen_elg(algo, nbits, pub_root, sec_root, dek, s2k, sk, expiredate, + is_subkey); + else if( algo == PUBKEY_ALGO_DSA ) + rc = gen_dsa(nbits, pub_root, sec_root, dek, s2k, sk, expiredate, + is_subkey); + else if( algo == PUBKEY_ALGO_RSA ) + rc = gen_rsa(algo, nbits, pub_root, sec_root, dek, s2k, sk, expiredate, + is_subkey); + else + BUG(); + + return rc; +} + + +/**************** + * Generate a new user id packet, or return NULL if canceled + */ +PKT_user_id * +generate_user_id() +{ + PKT_user_id *uid; + char *p; + size_t n; + + p = ask_user_id( 1 ); + if( !p ) + return NULL; + n = strlen(p); + uid = xmalloc_clear( sizeof *uid + n - 1 ); + uid->len = n; + strcpy(uid->name, p); + uid->ref = 1; + return uid; +} + + +static void +release_parameter_list( struct para_data_s *r ) +{ + struct para_data_s *r2; + + for( ; r ; r = r2 ) { + r2 = r->next; + if( r->key == pPASSPHRASE_DEK ) + xfree( r->u.dek ); + else if( r->key == pPASSPHRASE_S2K ) + xfree( r->u.s2k ); + + xfree(r); + } +} + +static struct para_data_s * +get_parameter( struct para_data_s *para, enum para_name key ) +{ + struct para_data_s *r; + + for( r = para; r && r->key != key; r = r->next ) + ; + return r; +} + +static const char * +get_parameter_value( struct para_data_s *para, enum para_name key ) +{ + struct para_data_s *r = get_parameter( para, key ); + return (r && *r->u.value)? r->u.value : NULL; +} + +static int +get_parameter_algo( struct para_data_s *para, enum para_name key ) +{ + int i; + struct para_data_s *r = get_parameter( para, key ); + if( !r ) + return -1; + if( digitp( r->u.value ) ) + i = atoi( r->u.value ); + else + i = string_to_pubkey_algo( r->u.value ); + if (i == PUBKEY_ALGO_RSA_E || i == PUBKEY_ALGO_RSA_S) + i = 0; /* we don't want to allow generation of these algorithms */ + return i; +} + +/* + * parse the usage parameter and set the keyflags. Return true on error. + */ +static int +parse_parameter_usage (const char *fname, + struct para_data_s *para, enum para_name key) +{ + struct para_data_s *r = get_parameter( para, key ); + char *p, *pn; + unsigned int use; + + if( !r ) + return 0; /* none (this is an optional parameter)*/ + + use = 0; + pn = r->u.value; + while ( (p = strsep (&pn, " \t,")) ) { + if ( !*p) + ; + else if ( !ascii_strcasecmp (p, "sign") ) + use |= PUBKEY_USAGE_SIG; + else if ( !ascii_strcasecmp (p, "encrypt") ) + use |= PUBKEY_USAGE_ENC; + else if ( !ascii_strcasecmp (p, "auth") ) + use |= PUBKEY_USAGE_AUTH; + else { + log_error("%s:%d: invalid usage list\n", fname, r->lnr ); + return -1; /* error */ + } + } + r->u.usage = use; + return 1; +} + +static int +parse_revocation_key (const char *fname, + struct para_data_s *para, enum para_name key) +{ + struct para_data_s *r = get_parameter( para, key ); + struct revocation_key revkey; + char *pn; + int i; + + if( !r ) + return 0; /* none (this is an optional parameter) */ + + pn = r->u.value; + + revkey.class=0x80; + revkey.algid=atoi(pn); + if(!revkey.algid) + goto fail; + + /* Skip to the fpr */ + while(*pn && *pn!=':') + pn++; + + if(*pn!=':') + goto fail; + + pn++; + + for(i=0;i<MAX_FINGERPRINT_LEN && *pn;i++,pn+=2) + { + int c=hextobyte(pn); + if(c==-1) + goto fail; + + revkey.fpr[i]=c; + } + + /* skip to the tag */ + while(*pn && *pn!='s' && *pn!='S') + pn++; + + if(ascii_strcasecmp(pn,"sensitive")==0) + revkey.class|=0x40; + + memcpy(&r->u.revkey,&revkey,sizeof(struct revocation_key)); + + return 0; + + fail: + log_error("%s:%d: invalid revocation key\n", fname, r->lnr ); + return -1; /* error */ +} + + +static u32 +get_parameter_u32( struct para_data_s *para, enum para_name key ) +{ + struct para_data_s *r = get_parameter( para, key ); + + if( !r ) + return 0; + if( r->key == pKEYEXPIRE || r->key == pSUBKEYEXPIRE ) + return r->u.expire; + if( r->key == pKEYUSAGE || r->key == pSUBKEYUSAGE ) + return r->u.usage; + + return (unsigned int)strtoul( r->u.value, NULL, 10 ); +} + +static unsigned int +get_parameter_uint( struct para_data_s *para, enum para_name key ) +{ + return get_parameter_u32( para, key ); +} + +static DEK * +get_parameter_dek( struct para_data_s *para, enum para_name key ) +{ + struct para_data_s *r = get_parameter( para, key ); + return r? r->u.dek : NULL; +} + +static STRING2KEY * +get_parameter_s2k( struct para_data_s *para, enum para_name key ) +{ + struct para_data_s *r = get_parameter( para, key ); + return r? r->u.s2k : NULL; +} + +static struct revocation_key * +get_parameter_revkey( struct para_data_s *para, enum para_name key ) +{ + struct para_data_s *r = get_parameter( para, key ); + return r? &r->u.revkey : NULL; +} + +static int +proc_parameter_file( struct para_data_s *para, const char *fname, + struct output_control_s *outctrl, int card ) +{ + struct para_data_s *r; + const char *s1, *s2, *s3; + size_t n; + char *p; + int have_user_id=0,err,algo; + + /* Check that we have all required parameters. */ + r = get_parameter( para, pKEYTYPE ); + if(r) + { + algo=get_parameter_algo(para,pKEYTYPE); + if(check_pubkey_algo2(algo,PUBKEY_USAGE_SIG)) + { + log_error("%s:%d: invalid algorithm\n", fname, r->lnr ); + return -1; + } + } + else + { + log_error("%s: no Key-Type specified\n",fname); + return -1; + } + + err=parse_parameter_usage (fname, para, pKEYUSAGE); + if(err==0) + { + /* Default to algo capabilities if key-usage is not provided */ + r=xmalloc_clear(sizeof(*r)); + r->key=pKEYUSAGE; + r->u.usage=openpgp_pk_algo_usage(algo); + r->next=para; + para=r; + } + else if(err==-1) + return -1; + + r = get_parameter( para, pSUBKEYTYPE ); + if(r) + { + algo=get_parameter_algo( para, pSUBKEYTYPE); + if(check_pubkey_algo(algo)) + { + log_error("%s:%d: invalid algorithm\n", fname, r->lnr ); + return -1; + } + + err=parse_parameter_usage (fname, para, pSUBKEYUSAGE); + if(err==0) + { + /* Default to algo capabilities if subkey-usage is not + provided */ + r=xmalloc_clear(sizeof(*r)); + r->key=pSUBKEYUSAGE; + r->u.usage=openpgp_pk_algo_usage(algo); + r->next=para; + para=r; + } + else if(err==-1) + return -1; + } + + if( get_parameter_value( para, pUSERID ) ) + have_user_id=1; + else + { + /* create the formatted user ID */ + s1 = get_parameter_value( para, pNAMEREAL ); + s2 = get_parameter_value( para, pNAMECOMMENT ); + s3 = get_parameter_value( para, pNAMEEMAIL ); + if( s1 || s2 || s3 ) + { + n = (s1?strlen(s1):0) + (s2?strlen(s2):0) + (s3?strlen(s3):0); + r = xmalloc_clear( sizeof *r + n + 20 ); + r->key = pUSERID; + p = r->u.value; + if( s1 ) + p = stpcpy(p, s1 ); + if( s2 ) + p = stpcpy(stpcpy(stpcpy(p," ("), s2 ),")"); + if( s3 ) + p = stpcpy(stpcpy(stpcpy(p," <"), s3 ),">"); + r->next = para; + para = r; + have_user_id=1; + } + } + + if(!have_user_id) + { + log_error("%s: no User-ID specified\n",fname); + return -1; + } + + /* Set preferences, if any. */ + keygen_set_std_prefs(get_parameter_value( para, pPREFERENCES ), 0); + + /* Set keyserver, if any. */ + s1=get_parameter_value( para, pKEYSERVER ); + if(s1) + { + struct keyserver_spec *spec; + + spec=parse_keyserver_uri(s1,1,NULL,0); + if(spec) + { + free_keyserver_spec(spec); + opt.def_keyserver_url=s1; + } + else + { + log_error("%s:%d: invalid keyserver url\n", fname, r->lnr ); + return -1; + } + } + + /* Set revoker, if any. */ + if (parse_revocation_key (fname, para, pREVOKER)) + return -1; + + /* make DEK and S2K from the Passphrase */ + r = get_parameter( para, pPASSPHRASE ); + if( r && *r->u.value ) { + /* we have a plain text passphrase - create a DEK from it. + * It is a little bit ridiculous to keep it ih secure memory + * but becuase we do this alwasy, why not here */ + STRING2KEY *s2k; + DEK *dek; + + s2k = xmalloc_secure( sizeof *s2k ); + s2k->mode = opt.s2k_mode; + s2k->hash_algo = S2K_DIGEST_ALGO; + set_next_passphrase( r->u.value ); + dek = passphrase_to_dek( NULL, 0, opt.s2k_cipher_algo, s2k, 2, + NULL, NULL); + set_next_passphrase( NULL ); + assert( dek ); + memset( r->u.value, 0, strlen(r->u.value) ); + + r = xmalloc_clear( sizeof *r ); + r->key = pPASSPHRASE_S2K; + r->u.s2k = s2k; + r->next = para; + para = r; + r = xmalloc_clear( sizeof *r ); + r->key = pPASSPHRASE_DEK; + r->u.dek = dek; + r->next = para; + para = r; + } + + /* make KEYEXPIRE from Expire-Date */ + r = get_parameter( para, pEXPIREDATE ); + if( r && *r->u.value ) + { + u32 seconds; + + seconds = parse_expire_string( r->u.value ); + if( seconds == (u32)-1 ) + { + log_error("%s:%d: invalid expire date\n", fname, r->lnr ); + return -1; + } + r->u.expire = seconds; + r->key = pKEYEXPIRE; /* change hat entry */ + /* also set it for the subkey */ + r = xmalloc_clear( sizeof *r + 20 ); + r->key = pSUBKEYEXPIRE; + r->u.expire = seconds; + r->next = para; + para = r; + } + + if( !!outctrl->pub.newfname ^ !!outctrl->sec.newfname ) { + log_error("%s:%d: only one ring name is set\n", fname, outctrl->lnr ); + return -1; + } + + do_generate_keypair( para, outctrl, card ); + return 0; +} + + +/**************** + * Kludge to allow non interactive key generation controlled + * by a parameter file. + * Note, that string parameters are expected to be in UTF-8 + */ +static void +read_parameter_file( const char *fname ) +{ + static struct { const char *name; + enum para_name key; + } keywords[] = { + { "Key-Type", pKEYTYPE}, + { "Key-Length", pKEYLENGTH }, + { "Key-Usage", pKEYUSAGE }, + { "Subkey-Type", pSUBKEYTYPE }, + { "Subkey-Length", pSUBKEYLENGTH }, + { "Subkey-Usage", pSUBKEYUSAGE }, + { "Name-Real", pNAMEREAL }, + { "Name-Email", pNAMEEMAIL }, + { "Name-Comment", pNAMECOMMENT }, + { "Expire-Date", pEXPIREDATE }, + { "Passphrase", pPASSPHRASE }, + { "Preferences", pPREFERENCES }, + { "Revoker", pREVOKER }, + { "Handle", pHANDLE }, + { "Keyserver", pKEYSERVER }, + { NULL, 0 } + }; + IOBUF fp; + byte *line; + unsigned int maxlen, nline; + char *p; + int lnr; + const char *err = NULL; + struct para_data_s *para, *r; + int i; + struct output_control_s outctrl; + + memset( &outctrl, 0, sizeof( outctrl ) ); + + if( !fname || !*fname) + fname = "-"; + + fp = iobuf_open (fname); + if (fp && is_secured_file (iobuf_get_fd (fp))) + { + iobuf_close (fp); + fp = NULL; + errno = EPERM; + } + if (!fp) { + log_error (_("can't open `%s': %s\n"), fname, strerror(errno) ); + return; + } + iobuf_ioctl (fp, 3, 1, NULL); /* No file caching. */ + + lnr = 0; + err = NULL; + para = NULL; + maxlen = 1024; + line = NULL; + while ( iobuf_read_line (fp, &line, &nline, &maxlen) ) { + char *keyword, *value; + + lnr++; + if( !maxlen ) { + err = "line too long"; + break; + } + for( p = line; isspace(*(byte*)p); p++ ) + ; + if( !*p || *p == '#' ) + continue; + keyword = p; + if( *keyword == '%' ) { + for( ; !isspace(*(byte*)p); p++ ) + ; + if( *p ) + *p++ = 0; + for( ; isspace(*(byte*)p); p++ ) + ; + value = p; + trim_trailing_ws( value, strlen(value) ); + if( !ascii_strcasecmp( keyword, "%echo" ) ) + log_info("%s\n", value ); + else if( !ascii_strcasecmp( keyword, "%dry-run" ) ) + outctrl.dryrun = 1; + else if( !ascii_strcasecmp( keyword, "%commit" ) ) { + outctrl.lnr = lnr; + if (proc_parameter_file( para, fname, &outctrl, 0 )) + print_status_key_not_created + (get_parameter_value (para, pHANDLE)); + release_parameter_list( para ); + para = NULL; + } + else if( !ascii_strcasecmp( keyword, "%pubring" ) ) { + if( outctrl.pub.fname && !strcmp( outctrl.pub.fname, value ) ) + ; /* still the same file - ignore it */ + else { + xfree( outctrl.pub.newfname ); + outctrl.pub.newfname = xstrdup( value ); + outctrl.use_files = 1; + } + } + else if( !ascii_strcasecmp( keyword, "%secring" ) ) { + if( outctrl.sec.fname && !strcmp( outctrl.sec.fname, value ) ) + ; /* still the same file - ignore it */ + else { + xfree( outctrl.sec.newfname ); + outctrl.sec.newfname = xstrdup( value ); + outctrl.use_files = 1; + } + } + else + log_info("skipping control `%s' (%s)\n", keyword, value ); + + + continue; + } + + + if( !(p = strchr( p, ':' )) || p == keyword ) { + err = "missing colon"; + break; + } + if( *p ) + *p++ = 0; + for( ; isspace(*(byte*)p); p++ ) + ; + if( !*p ) { + err = "missing argument"; + break; + } + value = p; + trim_trailing_ws( value, strlen(value) ); + + for(i=0; keywords[i].name; i++ ) { + if( !ascii_strcasecmp( keywords[i].name, keyword ) ) + break; + } + if( !keywords[i].name ) { + err = "unknown keyword"; + break; + } + if( keywords[i].key != pKEYTYPE && !para ) { + err = "parameter block does not start with \"Key-Type\""; + break; + } + + if( keywords[i].key == pKEYTYPE && para ) { + outctrl.lnr = lnr; + if (proc_parameter_file( para, fname, &outctrl, 0 )) + print_status_key_not_created + (get_parameter_value (para, pHANDLE)); + release_parameter_list( para ); + para = NULL; + } + else { + for( r = para; r; r = r->next ) { + if( r->key == keywords[i].key ) + break; + } + if( r ) { + err = "duplicate keyword"; + break; + } + } + r = xmalloc_clear( sizeof *r + strlen( value ) ); + r->lnr = lnr; + r->key = keywords[i].key; + strcpy( r->u.value, value ); + r->next = para; + para = r; + } + if( err ) + log_error("%s:%d: %s\n", fname, lnr, err ); + else if( iobuf_error (fp) ) { + log_error("%s:%d: read error\n", fname, lnr); + } + else if( para ) { + outctrl.lnr = lnr; + if (proc_parameter_file( para, fname, &outctrl, 0 )) + print_status_key_not_created (get_parameter_value (para, pHANDLE)); + } + + if( outctrl.use_files ) { /* close open streams */ + iobuf_close( outctrl.pub.stream ); + iobuf_close( outctrl.sec.stream ); + + /* Must invalidate that ugly cache to actually close it. */ + if (outctrl.pub.fname) + iobuf_ioctl (NULL, 2, 0, (char*)outctrl.pub.fname); + if (outctrl.sec.fname) + iobuf_ioctl (NULL, 2, 0, (char*)outctrl.sec.fname); + + xfree( outctrl.pub.fname ); + xfree( outctrl.pub.newfname ); + xfree( outctrl.sec.fname ); + xfree( outctrl.sec.newfname ); + } + + release_parameter_list( para ); + iobuf_close (fp); +} + + +/* + * Generate a keypair (fname is only used in batch mode) If + * CARD_SERIALNO is not NULL the fucntion will create the keys on an + * OpenPGP Card. If BACKUP_ENCRYPTION_DIR has been set and + * CARD_SERIALNO is NOT NULL, the encryption key for the card gets + * generate in software, imported to the card and a backup file + * written to directory given by this argument . + */ +void +generate_keypair (const char *fname, const char *card_serialno, + const char *backup_encryption_dir) +{ + unsigned int nbits; + char *uid = NULL; + DEK *dek; + STRING2KEY *s2k; + int algo; + unsigned int use; + int both = 0; + u32 expire; + struct para_data_s *para = NULL; + struct para_data_s *r; + struct output_control_s outctrl; + + memset( &outctrl, 0, sizeof( outctrl ) ); + + if (opt.batch && card_serialno) + { + /* We don't yet support unattended key generation. */ + log_error (_("can't do this in batch mode\n")); + return; + } + + if (opt.batch) + { + read_parameter_file( fname ); + return; + } + + if (card_serialno) + { +#ifdef ENABLE_CARD_SUPPORT + r = xcalloc (1, sizeof *r + strlen (card_serialno) ); + r->key = pSERIALNO; + strcpy( r->u.value, card_serialno); + r->next = para; + para = r; + + algo = PUBKEY_ALGO_RSA; + + r = xcalloc (1, sizeof *r + 20 ); + r->key = pKEYTYPE; + sprintf( r->u.value, "%d", algo ); + r->next = para; + para = r; + r = xcalloc (1, sizeof *r + 20 ); + r->key = pKEYUSAGE; + strcpy (r->u.value, "sign"); + r->next = para; + para = r; + + r = xcalloc (1, sizeof *r + 20 ); + r->key = pSUBKEYTYPE; + sprintf( r->u.value, "%d", algo ); + r->next = para; + para = r; + r = xcalloc (1, sizeof *r + 20 ); + r->key = pSUBKEYUSAGE; + strcpy (r->u.value, "encrypt"); + r->next = para; + para = r; + + r = xcalloc (1, sizeof *r + 20 ); + r->key = pAUTHKEYTYPE; + sprintf( r->u.value, "%d", algo ); + r->next = para; + para = r; + + if (backup_encryption_dir) + { + r = xcalloc (1, sizeof *r + strlen (backup_encryption_dir) ); + r->key = pBACKUPENCDIR; + strcpy (r->u.value, backup_encryption_dir); + r->next = para; + para = r; + } +#endif /*ENABLE_CARD_SUPPORT*/ + } + else + { + algo = ask_algo( 0, &use ); + if( !algo ) + { /* default: DSA with ElG subkey of the specified size */ + both = 1; + r = xmalloc_clear( sizeof *r + 20 ); + r->key = pKEYTYPE; + sprintf( r->u.value, "%d", PUBKEY_ALGO_DSA ); + r->next = para; + para = r; + nbits = ask_keysize( PUBKEY_ALGO_DSA ); + r = xmalloc_clear( sizeof *r + 20 ); + r->key = pKEYLENGTH; + sprintf( r->u.value, "%u", nbits); + r->next = para; + para = r; + r = xmalloc_clear( sizeof *r + 20 ); + r->key = pKEYUSAGE; + strcpy( r->u.value, "sign" ); + r->next = para; + para = r; + + algo = PUBKEY_ALGO_ELGAMAL_E; + r = xmalloc_clear( sizeof *r + 20 ); + r->key = pSUBKEYTYPE; + sprintf( r->u.value, "%d", algo ); + r->next = para; + para = r; + r = xmalloc_clear( sizeof *r + 20 ); + r->key = pSUBKEYUSAGE; + strcpy( r->u.value, "encrypt" ); + r->next = para; + para = r; + } + else + { + r = xmalloc_clear( sizeof *r + 20 ); + r->key = pKEYTYPE; + sprintf( r->u.value, "%d", algo ); + r->next = para; + para = r; + + if (use) + { + r = xmalloc_clear( sizeof *r + 25 ); + r->key = pKEYUSAGE; + sprintf( r->u.value, "%s%s%s", + (use & PUBKEY_USAGE_SIG)? "sign ":"", + (use & PUBKEY_USAGE_ENC)? "encrypt ":"", + (use & PUBKEY_USAGE_AUTH)? "auth":"" ); + r->next = para; + para = r; + } + + } + + nbits = ask_keysize( algo ); + r = xmalloc_clear( sizeof *r + 20 ); + r->key = both? pSUBKEYLENGTH : pKEYLENGTH; + sprintf( r->u.value, "%u", nbits); + r->next = para; + para = r; + } + + expire = ask_expire_interval(0,NULL); + r = xmalloc_clear( sizeof *r + 20 ); + r->key = pKEYEXPIRE; + r->u.expire = expire; + r->next = para; + para = r; + r = xmalloc_clear( sizeof *r + 20 ); + r->key = pSUBKEYEXPIRE; + r->u.expire = expire; + r->next = para; + para = r; + + uid = ask_user_id(0); + if( !uid ) + { + log_error(_("Key generation canceled.\n")); + release_parameter_list( para ); + return; + } + r = xmalloc_clear( sizeof *r + strlen(uid) ); + r->key = pUSERID; + strcpy( r->u.value, uid ); + r->next = para; + para = r; + + dek = card_serialno? NULL : do_ask_passphrase( &s2k ); + if( dek ) + { + r = xmalloc_clear( sizeof *r ); + r->key = pPASSPHRASE_DEK; + r->u.dek = dek; + r->next = para; + para = r; + r = xmalloc_clear( sizeof *r ); + r->key = pPASSPHRASE_S2K; + r->u.s2k = s2k; + r->next = para; + para = r; + } + + proc_parameter_file( para, "[internal]", &outctrl, !!card_serialno); + release_parameter_list( para ); +} + + +#ifdef ENABLE_CARD_SUPPORT +/* Generate a raw key and return it as a secret key packet. The + function will ask for the passphrase and return a protected as well + as an unprotected copy of a new secret key packet. 0 is returned + on success and the caller must then free the returned values. */ +static int +generate_raw_key (int algo, unsigned int nbits, u32 created_at, + PKT_secret_key **r_sk_unprotected, + PKT_secret_key **r_sk_protected) +{ + int rc; + DEK *dek = NULL; + STRING2KEY *s2k = NULL; + PKT_secret_key *sk = NULL; + int i; + size_t nskey, npkey; + + npkey = pubkey_get_npkey (algo); + nskey = pubkey_get_nskey (algo); + assert (nskey <= PUBKEY_MAX_NSKEY && npkey < nskey); + + if (nbits < 512) + { + nbits = 512; + log_info (_("keysize invalid; using %u bits\n"), nbits ); + } + + if ((nbits % 32)) + { + nbits = ((nbits + 31) / 32) * 32; + log_info(_("keysize rounded up to %u bits\n"), nbits ); + } + + dek = do_ask_passphrase (&s2k); + + sk = xmalloc_clear (sizeof *sk); + sk->timestamp = created_at; + sk->version = 4; + sk->pubkey_algo = algo; + + rc = pubkey_generate (algo, nbits, sk->skey, NULL); + if (rc) + { + log_error("pubkey_generate failed: %s\n", g10_errstr(rc) ); + goto leave; + } + + for (i=npkey; i < nskey; i++) + sk->csum += checksum_mpi (sk->skey[i]); + + if (r_sk_unprotected) + *r_sk_unprotected = copy_secret_key (NULL, sk); + + if (dek) + { + sk->protect.algo = dek->algo; + sk->protect.s2k = *s2k; + rc = protect_secret_key (sk, dek); + if (rc) + { + log_error ("protect_secret_key failed: %s\n", g10_errstr(rc)); + goto leave; + } + } + if (r_sk_protected) + { + *r_sk_protected = sk; + sk = NULL; + } + + leave: + if (sk) + free_secret_key (sk); + xfree (dek); + xfree (s2k); + return rc; +} +#endif /* ENABLE_CARD_SUPPORT */ + +/* Create and delete a dummy packet to start off a list of kbnodes. */ +static void +start_tree(KBNODE *tree) +{ + PACKET *pkt; + + pkt=xmalloc_clear(sizeof(*pkt)); + pkt->pkttype=PKT_NONE; + *tree=new_kbnode(pkt); + delete_kbnode(*tree); +} + +static void +do_generate_keypair( struct para_data_s *para, + struct output_control_s *outctrl, int card ) +{ + KBNODE pub_root = NULL; + KBNODE sec_root = NULL; + PKT_secret_key *pri_sk = NULL, *sub_sk = NULL; + const char *s; + struct revocation_key *revkey; + int rc; + int did_sub = 0; + + if( outctrl->dryrun ) + { + log_info("dry-run mode - key generation skipped\n"); + return; + } + + if( outctrl->use_files ) { + if( outctrl->pub.newfname ) { + iobuf_close(outctrl->pub.stream); + outctrl->pub.stream = NULL; + if (outctrl->pub.fname) + iobuf_ioctl (NULL, 2, 0, (char*)outctrl->pub.fname); + xfree( outctrl->pub.fname ); + outctrl->pub.fname = outctrl->pub.newfname; + outctrl->pub.newfname = NULL; + + if (is_secured_filename (outctrl->pub.fname) ) { + outctrl->pub.stream = NULL; + errno = EPERM; + } + else + outctrl->pub.stream = iobuf_create( outctrl->pub.fname ); + if( !outctrl->pub.stream ) { + log_error(_("can't create `%s': %s\n"), outctrl->pub.newfname, + strerror(errno) ); + return; + } + if( opt.armor ) { + outctrl->pub.afx.what = 1; + iobuf_push_filter( outctrl->pub.stream, armor_filter, + &outctrl->pub.afx ); + } + } + if( outctrl->sec.newfname ) { + mode_t oldmask; + + iobuf_close(outctrl->sec.stream); + outctrl->sec.stream = NULL; + if (outctrl->sec.fname) + iobuf_ioctl (NULL, 2, 0, (char*)outctrl->sec.fname); + xfree( outctrl->sec.fname ); + outctrl->sec.fname = outctrl->sec.newfname; + outctrl->sec.newfname = NULL; + + oldmask = umask (077); + if (is_secured_filename (outctrl->sec.fname) ) { + outctrl->sec.stream = NULL; + errno = EPERM; + } + else + outctrl->sec.stream = iobuf_create( outctrl->sec.fname ); + umask (oldmask); + if( !outctrl->sec.stream ) { + log_error(_("can't create `%s': %s\n"), outctrl->sec.newfname, + strerror(errno) ); + return; + } + if( opt.armor ) { + outctrl->sec.afx.what = 5; + iobuf_push_filter( outctrl->sec.stream, armor_filter, + &outctrl->sec.afx ); + } + } + assert( outctrl->pub.stream ); + assert( outctrl->sec.stream ); + if( opt.verbose ) { + log_info(_("writing public key to `%s'\n"), outctrl->pub.fname ); + if (card) + log_info (_("writing secret key stub to `%s'\n"), + outctrl->sec.fname); + else + log_info(_("writing secret key to `%s'\n"), outctrl->sec.fname ); + } + } + + + /* we create the packets as a tree of kbnodes. Because the + * structure we create is known in advance we simply generate a + * linked list. The first packet is a dummy packet which we flag + * as deleted. The very first packet must always be a KEY packet. + */ + + start_tree(&pub_root); + start_tree(&sec_root); + + if (!card) + { + rc = do_create( get_parameter_algo( para, pKEYTYPE ), + get_parameter_uint( para, pKEYLENGTH ), + pub_root, sec_root, + get_parameter_dek( para, pPASSPHRASE_DEK ), + get_parameter_s2k( para, pPASSPHRASE_S2K ), + &pri_sk, + get_parameter_u32( para, pKEYEXPIRE ), 0 ); + } + else + { + rc = gen_card_key (PUBKEY_ALGO_RSA, 1, 1, pub_root, sec_root, NULL, + get_parameter_u32 (para, pKEYEXPIRE), para); + if (!rc) + { + pri_sk = sec_root->next->pkt->pkt.secret_key; + assert (pri_sk); + } + } + + if(!rc && (revkey=get_parameter_revkey(para,pREVOKER))) + { + rc=write_direct_sig(pub_root,pub_root,pri_sk,revkey); + if(!rc) + write_direct_sig(sec_root,pub_root,pri_sk,revkey); + } + + if( !rc && (s=get_parameter_value(para, pUSERID)) ) + { + write_uid(pub_root, s ); + if( !rc ) + write_uid(sec_root, s ); + + if( !rc ) + rc = write_selfsigs(sec_root, pub_root, pri_sk, + get_parameter_uint (para, pKEYUSAGE)); + } + + /* Write the auth key to the card before the encryption key. This + is a partial workaround for a PGP bug (as of this writing, all + versions including 8.1), that causes it to try and encrypt to + the most recent subkey regardless of whether that subkey is + actually an encryption type. In this case, the auth key is an + RSA key so it succeeds. */ + + if (!rc && card && get_parameter (para, pAUTHKEYTYPE)) + { + rc = gen_card_key (PUBKEY_ALGO_RSA, 3, 0, pub_root, sec_root, NULL, + get_parameter_u32 (para, pKEYEXPIRE), para); + + if (!rc) + rc = write_keybinding (pub_root, pub_root, pri_sk, sub_sk, PUBKEY_USAGE_AUTH); + if (!rc) + rc = write_keybinding (sec_root, pub_root, pri_sk, sub_sk, PUBKEY_USAGE_AUTH); + } + + if( !rc && get_parameter( para, pSUBKEYTYPE ) ) + { + if (!card) + { + rc = do_create( get_parameter_algo( para, pSUBKEYTYPE ), + get_parameter_uint( para, pSUBKEYLENGTH ), + pub_root, sec_root, + get_parameter_dek( para, pPASSPHRASE_DEK ), + get_parameter_s2k( para, pPASSPHRASE_S2K ), + &sub_sk, + get_parameter_u32( para, pSUBKEYEXPIRE ), 1 ); + } + else + { + if ((s = get_parameter_value (para, pBACKUPENCDIR))) + { + /* A backup of the encryption key has been requested. + Generate the key i software and import it then to + the card. Write a backup file. */ + rc = gen_card_key_with_backup (PUBKEY_ALGO_RSA, 2, 0, + pub_root, sec_root, + get_parameter_u32 (para, + pKEYEXPIRE), + para, s); + } + else + rc = gen_card_key (PUBKEY_ALGO_RSA, 2, 0, pub_root, sec_root, + NULL, + get_parameter_u32 (para, pKEYEXPIRE), para); + } + + if( !rc ) + rc = write_keybinding(pub_root, pub_root, pri_sk, sub_sk, + get_parameter_uint (para, pSUBKEYUSAGE)); + if( !rc ) + rc = write_keybinding(sec_root, pub_root, pri_sk, sub_sk, + get_parameter_uint (para, pSUBKEYUSAGE)); + did_sub = 1; + } + + if( !rc && outctrl->use_files ) { /* direct write to specified files */ + rc = write_keyblock( outctrl->pub.stream, pub_root ); + if( rc ) + log_error("can't write public key: %s\n", g10_errstr(rc) ); + if( !rc ) { + rc = write_keyblock( outctrl->sec.stream, sec_root ); + if( rc ) + log_error("can't write secret key: %s\n", g10_errstr(rc) ); + } + + } + else if( !rc ) { /* write to the standard keyrings */ + KEYDB_HANDLE pub_hd = keydb_new (0); + KEYDB_HANDLE sec_hd = keydb_new (1); + + /* FIXME: we may have to create the keyring first */ + rc = keydb_locate_writable (pub_hd, NULL); + if (rc) + log_error (_("no writable public keyring found: %s\n"), + g10_errstr (rc)); + + if (!rc) { + rc = keydb_locate_writable (sec_hd, NULL); + if (rc) + log_error (_("no writable secret keyring found: %s\n"), + g10_errstr (rc)); + } + + if (!rc && opt.verbose) { + log_info(_("writing public key to `%s'\n"), + keydb_get_resource_name (pub_hd)); + if (card) + log_info (_("writing secret key stub to `%s'\n"), + keydb_get_resource_name (sec_hd)); + else + log_info(_("writing secret key to `%s'\n"), + keydb_get_resource_name (sec_hd)); + } + + if (!rc) { + rc = keydb_insert_keyblock (pub_hd, pub_root); + if (rc) + log_error (_("error writing public keyring `%s': %s\n"), + keydb_get_resource_name (pub_hd), g10_errstr(rc)); + } + + if (!rc) { + rc = keydb_insert_keyblock (sec_hd, sec_root); + if (rc) + log_error (_("error writing secret keyring `%s': %s\n"), + keydb_get_resource_name (pub_hd), g10_errstr(rc)); + } + + keydb_release (pub_hd); + keydb_release (sec_hd); + + if (!rc) { + int no_enc_rsa = + get_parameter_algo(para, pKEYTYPE) == PUBKEY_ALGO_RSA + && get_parameter_uint( para, pKEYUSAGE ) + && !(get_parameter_uint( para,pKEYUSAGE) & PUBKEY_USAGE_ENC); + PKT_public_key *pk = find_kbnode (pub_root, + PKT_PUBLIC_KEY)->pkt->pkt.public_key; + + keyid_from_pk(pk,pk->main_keyid); + register_trusted_keyid(pk->main_keyid); + + update_ownertrust (pk, + ((get_ownertrust (pk) & ~TRUST_MASK) + | TRUST_ULTIMATE )); + + if (!opt.batch) { + tty_printf(_("public and secret key created and signed.\n") ); + tty_printf("\n"); + list_keyblock(pub_root,0,1,NULL); + } + + + if( !opt.batch + && ( get_parameter_algo( para, pKEYTYPE ) == PUBKEY_ALGO_DSA + || no_enc_rsa ) + && !get_parameter( para, pSUBKEYTYPE ) ) + { + tty_printf(_("Note that this key cannot be used for " + "encryption. You may want to use\n" + "the command \"--edit-key\" to generate a " + "subkey for this purpose.\n") ); + } + } + } + + if( rc ) { + if( opt.batch ) + log_error("key generation failed: %s\n", g10_errstr(rc) ); + else + tty_printf(_("Key generation failed: %s\n"), g10_errstr(rc) ); + print_status_key_not_created ( get_parameter_value (para, pHANDLE) ); + } + else { + PKT_public_key *pk = find_kbnode (pub_root, + PKT_PUBLIC_KEY)->pkt->pkt.public_key; + print_status_key_created (did_sub? 'B':'P', pk, + get_parameter_value (para, pHANDLE)); + } + release_kbnode( pub_root ); + release_kbnode( sec_root ); + + if( pri_sk && !card) /* the unprotected secret key unless we have a */ + free_secret_key(pri_sk); /* shallow copy in card mode. */ + if( sub_sk ) + free_secret_key(sub_sk); +} + + +/**************** + * add a new subkey to an existing key. + * Returns true if a new key has been generated and put into the keyblocks. + */ +int +generate_subkeypair( KBNODE pub_keyblock, KBNODE sec_keyblock ) +{ + int okay=0, rc=0; + KBNODE node; + PKT_secret_key *pri_sk = NULL, *sub_sk = NULL; + int algo; + unsigned int use; + u32 expire; + unsigned nbits; + char *passphrase = NULL; + DEK *dek = NULL; + STRING2KEY *s2k = NULL; + u32 cur_time; + int ask_pass = 0; + + /* break out the primary secret key */ + node = find_kbnode( sec_keyblock, PKT_SECRET_KEY ); + if( !node ) { + log_error("Oops; secret key not found anymore!\n"); + goto leave; + } + + /* make a copy of the sk to keep the protected one in the keyblock */ + pri_sk = copy_secret_key( NULL, node->pkt->pkt.secret_key ); + + cur_time = make_timestamp(); + if( pri_sk->timestamp > cur_time ) { + ulong d = pri_sk->timestamp - cur_time; + log_info( d==1 ? _("key has been created %lu second " + "in future (time warp or clock problem)\n") + : _("key has been created %lu seconds " + "in future (time warp or clock problem)\n"), d ); + if( !opt.ignore_time_conflict ) { + rc = G10ERR_TIME_CONFLICT; + goto leave; + } + } + + if (pri_sk->version < 4) { + log_info (_("NOTE: creating subkeys for v3 keys " + "is not OpenPGP compliant\n")); + goto leave; + } + + if (pri_sk->is_protected && pri_sk->protect.s2k.mode == 1001) { + tty_printf(_("Secret parts of primary key are not available.\n")); + rc = G10ERR_NO_SECKEY; + goto leave; + } + + + /* Unprotect to get the passphrase. */ + switch( is_secret_key_protected( pri_sk ) ) { + case -1: + rc = G10ERR_PUBKEY_ALGO; + break; + case 0: + tty_printf(_("This key is not protected.\n")); + break; + case -2: + tty_printf(_("Secret parts of primary key are stored on-card.\n")); + ask_pass = 1; + break; + default: + tty_printf(_("Key is protected.\n")); + rc = check_secret_key( pri_sk, 0 ); + if( !rc ) + passphrase = get_last_passphrase(); + break; + } + if( rc ) + goto leave; + + algo = ask_algo( 1, &use ); + assert(algo); + nbits = ask_keysize( algo ); + expire = ask_expire_interval(0,NULL); + if( !cpr_enabled() && !cpr_get_answer_is_yes("keygen.sub.okay", + _("Really create? (y/N) "))) + goto leave; + + if (ask_pass) + dek = do_ask_passphrase (&s2k); + else if (passphrase) { + s2k = xmalloc_secure( sizeof *s2k ); + s2k->mode = opt.s2k_mode; + s2k->hash_algo = S2K_DIGEST_ALGO; + set_next_passphrase( passphrase ); + dek = passphrase_to_dek( NULL, 0, opt.s2k_cipher_algo, s2k, 2, + NULL, NULL ); + } + + rc = do_create( algo, nbits, pub_keyblock, sec_keyblock, + dek, s2k, &sub_sk, expire, 1 ); + if( !rc ) + rc = write_keybinding(pub_keyblock, pub_keyblock, pri_sk, sub_sk, use); + if( !rc ) + rc = write_keybinding(sec_keyblock, pub_keyblock, pri_sk, sub_sk, use); + if( !rc ) { + okay = 1; + write_status_text (STATUS_KEY_CREATED, "S"); + } + + leave: + if( rc ) + log_error(_("Key generation failed: %s\n"), g10_errstr(rc) ); + xfree( passphrase ); + xfree( dek ); + xfree( s2k ); + /* release the copy of the (now unprotected) secret keys */ + if( pri_sk ) + free_secret_key(pri_sk); + if( sub_sk ) + free_secret_key(sub_sk); + set_next_passphrase( NULL ); + return okay; +} + + +#ifdef ENABLE_CARD_SUPPORT +/* Generate a subkey on a card. */ +int +generate_card_subkeypair (KBNODE pub_keyblock, KBNODE sec_keyblock, + int keyno, const char *serialno) +{ + int okay=0, rc=0; + KBNODE node; + PKT_secret_key *pri_sk = NULL, *sub_sk; + int algo; + unsigned int use; + u32 expire; + char *passphrase = NULL; + u32 cur_time; + struct para_data_s *para = NULL; + + assert (keyno >= 1 && keyno <= 3); + + para = xcalloc (1, sizeof *para + strlen (serialno) ); + para->key = pSERIALNO; + strcpy (para->u.value, serialno); + + /* Break out the primary secret key */ + node = find_kbnode( sec_keyblock, PKT_SECRET_KEY ); + if(!node) + { + log_error("Oops; secret key not found anymore!\n"); + goto leave; + } + + /* Make a copy of the sk to keep the protected one in the keyblock */ + pri_sk = copy_secret_key (NULL, node->pkt->pkt.secret_key); + + cur_time = make_timestamp(); + if (pri_sk->timestamp > cur_time) + { + ulong d = pri_sk->timestamp - cur_time; + log_info (d==1 ? _("key has been created %lu second " + "in future (time warp or clock problem)\n") + : _("key has been created %lu seconds " + "in future (time warp or clock problem)\n"), d ); + if (!opt.ignore_time_conflict) + { + rc = G10ERR_TIME_CONFLICT; + goto leave; + } + } + + if (pri_sk->version < 4) + { + log_info (_("NOTE: creating subkeys for v3 keys " + "is not OpenPGP compliant\n")); + goto leave; + } + + /* Unprotect to get the passphrase. */ + switch( is_secret_key_protected (pri_sk) ) + { + case -1: + rc = G10ERR_PUBKEY_ALGO; + break; + case 0: + tty_printf("This key is not protected.\n"); + break; + default: + tty_printf("Key is protected.\n"); + rc = check_secret_key( pri_sk, 0 ); + if (!rc) + passphrase = get_last_passphrase(); + break; + } + if (rc) + goto leave; + + algo = PUBKEY_ALGO_RSA; + expire = ask_expire_interval (0,NULL); + if (keyno == 1) + use = PUBKEY_USAGE_SIG; + else if (keyno == 2) + use = PUBKEY_USAGE_ENC; + else + use = PUBKEY_USAGE_AUTH; + if (!cpr_enabled() && !cpr_get_answer_is_yes("keygen.cardsub.okay", + _("Really create? (y/N) "))) + goto leave; + + if (passphrase) + set_next_passphrase (passphrase); + rc = gen_card_key (algo, keyno, 0, pub_keyblock, sec_keyblock, + &sub_sk, expire, para); + if (!rc) + rc = write_keybinding (pub_keyblock, pub_keyblock, pri_sk, sub_sk, use); + if (!rc) + rc = write_keybinding (sec_keyblock, pub_keyblock, pri_sk, sub_sk, use); + if (!rc) + { + okay = 1; + write_status_text (STATUS_KEY_CREATED, "S"); + } + + leave: + if (rc) + log_error (_("Key generation failed: %s\n"), g10_errstr(rc) ); + xfree (passphrase); + /* Release the copy of the (now unprotected) secret keys. */ + if (pri_sk) + free_secret_key (pri_sk); + set_next_passphrase( NULL ); + release_parameter_list (para); + return okay; +} +#endif /* !ENABLE_CARD_SUPPORT */ + + +/**************** + * Write a keyblock to an output stream + */ +static int +write_keyblock( IOBUF out, KBNODE node ) +{ + for( ; node ; node = node->next ) + { + if(!is_deleted_kbnode(node)) + { + int rc = build_packet( out, node->pkt ); + if( rc ) + { + log_error("build_packet(%d) failed: %s\n", + node->pkt->pkttype, g10_errstr(rc) ); + return G10ERR_WRITE_FILE; + } + } + } + + return 0; +} + + +static int +gen_card_key (int algo, int keyno, int is_primary, + KBNODE pub_root, KBNODE sec_root, PKT_secret_key **ret_sk, + u32 expireval, struct para_data_s *para) +{ +#ifdef ENABLE_CARD_SUPPORT + int rc; + const char *s; + struct agent_card_genkey_s info; + PACKET *pkt; + PKT_secret_key *sk; + PKT_public_key *pk; + + assert (algo == PUBKEY_ALGO_RSA); + + /* Fixme: We don't have the serialnumber available, thus passing NULL. */ + rc = agent_scd_genkey (&info, keyno, 1, NULL); +/* if (gpg_err_code (rc) == GPG_ERR_EEXIST) */ +/* { */ +/* tty_printf ("\n"); */ +/* log_error ("WARNING: key does already exists!\n"); */ +/* tty_printf ("\n"); */ +/* if ( cpr_get_answer_is_yes( "keygen.card.replace_key", */ +/* _("Replace existing key? "))) */ +/* rc = agent_scd_genkey (&info, keyno, 1); */ +/* } */ + + if (rc) + { + log_error ("key generation failed: %s\n", gpg_strerror (rc)); + return rc; + } + if ( !info.n || !info.e ) + { + log_error ("communication error with SCD\n"); + mpi_free (info.n); + mpi_free (info.e); + return gpg_error (GPG_ERR_GENERAL); + } + + + pk = xcalloc (1, sizeof *pk ); + sk = xcalloc (1, sizeof *sk ); + sk->timestamp = pk->timestamp = info.created_at; + sk->version = pk->version = 4; + if (expireval) + sk->expiredate = pk->expiredate = pk->timestamp + expireval; + sk->pubkey_algo = pk->pubkey_algo = algo; + pk->pkey[0] = info.n; + pk->pkey[1] = info.e; + sk->skey[0] = mpi_copy (pk->pkey[0]); + sk->skey[1] = mpi_copy (pk->pkey[1]); + sk->skey[2] = mpi_set_opaque (NULL, xstrdup ("dummydata"), 10); + sk->is_protected = 1; + sk->protect.s2k.mode = 1002; + s = get_parameter_value (para, pSERIALNO); + if (s) + { + for (sk->protect.ivlen=0; sk->protect.ivlen < 16 && *s && s[1]; + sk->protect.ivlen++, s += 2) + sk->protect.iv[sk->protect.ivlen] = xtoi_2 (s); + } + + if( ret_sk ) + *ret_sk = sk; + + pkt = xcalloc (1,sizeof *pkt); + pkt->pkttype = is_primary ? PKT_PUBLIC_KEY : PKT_PUBLIC_SUBKEY; + pkt->pkt.public_key = pk; + add_kbnode(pub_root, new_kbnode( pkt )); + + pkt = xcalloc (1,sizeof *pkt); + pkt->pkttype = is_primary ? PKT_SECRET_KEY : PKT_SECRET_SUBKEY; + pkt->pkt.secret_key = sk; + add_kbnode(sec_root, new_kbnode( pkt )); + + return 0; +#else + return -1; +#endif /*!ENABLE_CARD_SUPPORT*/ +} + + + +static int +gen_card_key_with_backup (int algo, int keyno, int is_primary, + KBNODE pub_root, KBNODE sec_root, + u32 expireval, struct para_data_s *para, + const char *backup_dir) +{ +#ifdef ENABLE_CARD_SUPPORT + int rc; + const char *s; + PACKET *pkt; + PKT_secret_key *sk, *sk_unprotected, *sk_protected; + PKT_public_key *pk; + size_t n; + int i; + + sk_unprotected = NULL; + sk_protected = NULL; + rc = generate_raw_key (algo, 1024, make_timestamp (), + &sk_unprotected, &sk_protected); + if (rc) + return rc; + + /* First, store the key to the card. */ + rc = save_unprotected_key_to_card (sk_unprotected, keyno); + if (rc) + { + log_error (_("storing key onto card failed: %s\n"), g10_errstr (rc)); + free_secret_key (sk_unprotected); + free_secret_key (sk_protected); + return rc; + } + + /* Get rid of the secret key parameters and store the serial numer. */ + sk = sk_unprotected; + n = pubkey_get_nskey (sk->pubkey_algo); + for (i=pubkey_get_npkey (sk->pubkey_algo); i < n; i++) + { + mpi_free (sk->skey[i]); + sk->skey[i] = NULL; + } + i = pubkey_get_npkey (sk->pubkey_algo); + sk->skey[i] = mpi_set_opaque (NULL, xstrdup ("dummydata"), 10); + sk->is_protected = 1; + sk->protect.s2k.mode = 1002; + s = get_parameter_value (para, pSERIALNO); + assert (s); + for (sk->protect.ivlen=0; sk->protect.ivlen < 16 && *s && s[1]; + sk->protect.ivlen++, s += 2) + sk->protect.iv[sk->protect.ivlen] = xtoi_2 (s); + + /* Now write the *protected* secret key to the file. */ + { + char name_buffer[50]; + char *fname; + IOBUF fp; + mode_t oldmask; + + keyid_from_sk (sk, NULL); + sprintf (name_buffer,"sk_%08lX%08lX.gpg", + (ulong)sk->keyid[0], (ulong)sk->keyid[1]); + + fname = make_filename (backup_dir, name_buffer, NULL); + oldmask = umask (077); + if (is_secured_filename (fname)) + { + fp = NULL; + errno = EPERM; + } + else + fp = iobuf_create (fname); + umask (oldmask); + if (!fp) + { + log_error (_("can't create backup file `%s': %s\n"), + fname, strerror(errno) ); + xfree (fname); + free_secret_key (sk_unprotected); + free_secret_key (sk_protected); + return G10ERR_OPEN_FILE; + } + + pkt = xcalloc (1, sizeof *pkt); + pkt->pkttype = PKT_SECRET_KEY; + pkt->pkt.secret_key = sk_protected; + sk_protected = NULL; + + rc = build_packet (fp, pkt); + if (rc) + { + log_error("build packet failed: %s\n", g10_errstr(rc) ); + iobuf_cancel (fp); + } + else + { + byte array[MAX_FINGERPRINT_LEN]; + char *fprbuf, *p; + + iobuf_close (fp); + iobuf_ioctl (NULL, 2, 0, (char*)fname); + log_info (_("NOTE: backup of card key saved to `%s'\n"), fname); + + fingerprint_from_sk (sk, array, &n); + p = fprbuf = xmalloc (MAX_FINGERPRINT_LEN*2 + 1 + 1); + for (i=0; i < n ; i++, p += 2) + sprintf (p, "%02X", array[i]); + *p++ = ' '; + *p = 0; + + write_status_text_and_buffer (STATUS_BACKUP_KEY_CREATED, + fprbuf, + fname, strlen (fname), + 0); + xfree (fprbuf); + } + free_packet (pkt); + xfree (pkt); + xfree (fname); + if (rc) + { + free_secret_key (sk_unprotected); + return rc; + } + } + + /* Create the public key from the secret key. */ + pk = xcalloc (1, sizeof *pk ); + pk->timestamp = sk->timestamp; + pk->version = sk->version; + if (expireval) + pk->expiredate = sk->expiredate = sk->timestamp + expireval; + pk->pubkey_algo = sk->pubkey_algo; + n = pubkey_get_npkey (sk->pubkey_algo); + for (i=0; i < n; i++) + pk->pkey[i] = mpi_copy (sk->skey[i]); + + /* Build packets and add them to the node lists. */ + pkt = xcalloc (1,sizeof *pkt); + pkt->pkttype = is_primary ? PKT_PUBLIC_KEY : PKT_PUBLIC_SUBKEY; + pkt->pkt.public_key = pk; + add_kbnode(pub_root, new_kbnode( pkt )); + + pkt = xcalloc (1,sizeof *pkt); + pkt->pkttype = is_primary ? PKT_SECRET_KEY : PKT_SECRET_SUBKEY; + pkt->pkt.secret_key = sk; + add_kbnode(sec_root, new_kbnode( pkt )); + + return 0; +#else + return -1; +#endif /*!ENABLE_CARD_SUPPORT*/ +} + + +#ifdef ENABLE_CARD_SUPPORT +int +save_unprotected_key_to_card (PKT_secret_key *sk, int keyno) +{ + int rc; + unsigned char *rsa_n = NULL; + unsigned char *rsa_e = NULL; + unsigned char *rsa_p = NULL; + unsigned char *rsa_q = NULL; + unsigned int rsa_n_len, rsa_e_len, rsa_p_len, rsa_q_len; + unsigned char *sexp = NULL; + unsigned char *p; + char numbuf[55], numbuf2[50]; + + assert (is_RSA (sk->pubkey_algo)); + assert (!sk->is_protected); + + /* Copy the parameters into straight buffers. */ + rsa_n = mpi_get_secure_buffer (sk->skey[0], &rsa_n_len, NULL); + rsa_e = mpi_get_secure_buffer (sk->skey[1], &rsa_e_len, NULL); + rsa_p = mpi_get_secure_buffer (sk->skey[3], &rsa_p_len, NULL); + rsa_q = mpi_get_secure_buffer (sk->skey[4], &rsa_q_len, NULL); + if (!rsa_n || !rsa_e || !rsa_p || !rsa_q) + { + rc = G10ERR_INV_ARG; + goto leave; + } + + /* Put the key into an S-expression. */ + sexp = p = xmalloc_secure (30 + + rsa_n_len + rsa_e_len + rsa_p_len + rsa_q_len + + 4*sizeof (numbuf) + 25 + sizeof(numbuf) + 20); + + p = stpcpy (p,"(11:private-key(3:rsa(1:n"); + sprintf (numbuf, "%u:", rsa_n_len); + p = stpcpy (p, numbuf); + memcpy (p, rsa_n, rsa_n_len); + p += rsa_n_len; + + sprintf (numbuf, ")(1:e%u:", rsa_e_len); + p = stpcpy (p, numbuf); + memcpy (p, rsa_e, rsa_e_len); + p += rsa_e_len; + + sprintf (numbuf, ")(1:p%u:", rsa_p_len); + p = stpcpy (p, numbuf); + memcpy (p, rsa_p, rsa_p_len); + p += rsa_p_len; + + sprintf (numbuf, ")(1:q%u:", rsa_q_len); + p = stpcpy (p, numbuf); + memcpy (p, rsa_q, rsa_q_len); + p += rsa_q_len; + + p = stpcpy (p,"))(10:created-at"); + sprintf (numbuf2, "%lu", (unsigned long)sk->timestamp); + sprintf (numbuf, "%lu:", (unsigned long)strlen (numbuf2)); + p = stpcpy (stpcpy (stpcpy (p, numbuf), numbuf2), "))"); + + /* Fixme: Unfortunately we don't have the serialnumber available - + thus we can't pass it down to the agent. */ + rc = agent_scd_writekey (keyno, NULL, sexp, p - sexp); + + leave: + xfree (sexp); + xfree (rsa_n); + xfree (rsa_e); + xfree (rsa_p); + xfree (rsa_q); + return rc; +} +#endif /*ENABLE_CARD_SUPPORT*/ diff --git a/g10/keyid.c b/g10/keyid.c new file mode 100644 index 0000000..86a204f --- /dev/null +++ b/g10/keyid.c @@ -0,0 +1,753 @@ +/* keyid.c - key ID and fingerprint handling + * Copyright (C) 1998, 1999, 2000, 2001, 2003, + * 2004 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <time.h> +#include <assert.h> +#include "util.h" +#include "main.h" +#include "packet.h" +#include "options.h" +#include "mpi.h" +#include "keydb.h" +#include "i18n.h" + +int +pubkey_letter( int algo ) +{ + switch( algo ) { + case PUBKEY_ALGO_RSA: return 'R' ; + case PUBKEY_ALGO_RSA_E: return 'r' ; + case PUBKEY_ALGO_RSA_S: return 's' ; + case PUBKEY_ALGO_ELGAMAL_E: return 'g'; + case PUBKEY_ALGO_ELGAMAL: return 'G' ; + case PUBKEY_ALGO_DSA: return 'D' ; + default: return '?'; + } +} + +/* This function is useful for v4 fingerprints and v3 or v4 key + signing. */ +void +hash_public_key( MD_HANDLE md, PKT_public_key *pk ) +{ + unsigned n=6; + unsigned nb[PUBKEY_MAX_NPKEY]; + unsigned nn[PUBKEY_MAX_NPKEY]; + byte *pp[PUBKEY_MAX_NPKEY]; + int i; + int npkey = pubkey_get_npkey( pk->pubkey_algo ); + + /* Two extra bytes for the expiration date in v3 */ + if(pk->version<4) + n+=2; + + if(npkey==0 && pk->pkey[0] && mpi_is_opaque(pk->pkey[0])) + { + pp[0]=mpi_get_opaque(pk->pkey[0],&nn[0]); + n+=nn[0]; + } + else + for(i=0; i < npkey; i++ ) + { + nb[i] = mpi_get_nbits(pk->pkey[i]); + pp[i] = mpi_get_buffer( pk->pkey[i], nn+i, NULL ); + n += 2 + nn[i]; + } + + md_putc( md, 0x99 ); /* ctb */ + /* What does it mean if n is greater than than 0xFFFF ? */ + md_putc( md, n >> 8 ); /* 2 byte length header */ + md_putc( md, n ); + md_putc( md, pk->version ); + + md_putc( md, pk->timestamp >> 24 ); + md_putc( md, pk->timestamp >> 16 ); + md_putc( md, pk->timestamp >> 8 ); + md_putc( md, pk->timestamp ); + + if(pk->version<4) + { + u16 days=0; + if(pk->expiredate) + days=(u16)((pk->expiredate - pk->timestamp) / 86400L); + + md_putc( md, days >> 8 ); + md_putc( md, days ); + } + + md_putc( md, pk->pubkey_algo ); + + if(npkey==0 && pk->pkey[0] && mpi_is_opaque(pk->pkey[0])) + md_write(md,pp[0],nn[0]); + else + for(i=0; i < npkey; i++ ) + { + md_putc( md, nb[i]>>8); + md_putc( md, nb[i] ); + md_write( md, pp[i], nn[i] ); + xfree(pp[i]); + } +} + +static MD_HANDLE +do_fingerprint_md( PKT_public_key *pk ) +{ + MD_HANDLE md; + + md = md_open( DIGEST_ALGO_SHA1, 0); + hash_public_key(md,pk); + md_final( md ); + + return md; +} + +static MD_HANDLE +do_fingerprint_md_sk( PKT_secret_key *sk ) +{ + PKT_public_key pk; + int npkey = pubkey_get_npkey( sk->pubkey_algo ); /* npkey is correct! */ + int i; + + if(npkey==0) + return NULL; + + pk.pubkey_algo = sk->pubkey_algo; + pk.version = sk->version; + pk.timestamp = sk->timestamp; + pk.expiredate = sk->expiredate; + pk.pubkey_algo = sk->pubkey_algo; + for( i=0; i < npkey; i++ ) + pk.pkey[i] = sk->skey[i]; + return do_fingerprint_md( &pk ); +} + +size_t +keystrlen(void) +{ + switch(opt.keyid_format) + { + case KF_SHORT: + return 8; + + case KF_LONG: + return 16; + + case KF_0xSHORT: + return 10; + + case KF_0xLONG: + return 18; + + default: + BUG(); + } +} + +const char * +keystr(u32 *keyid) +{ + static char keyid_str[19]; + + switch(opt.keyid_format) + { + case KF_SHORT: + sprintf(keyid_str,"%08lX",(ulong)keyid[1]); + break; + + case KF_LONG: + if(keyid[0]) + sprintf(keyid_str,"%08lX%08lX",(ulong)keyid[0],(ulong)keyid[1]); + else + sprintf(keyid_str,"%08lX",(ulong)keyid[1]); + break; + + case KF_0xSHORT: + sprintf(keyid_str,"0x%08lX",(ulong)keyid[1]); + break; + + case KF_0xLONG: + if(keyid[0]) + sprintf(keyid_str,"0x%08lX%08lX",(ulong)keyid[0],(ulong)keyid[1]); + else + sprintf(keyid_str,"0x%08lX",(ulong)keyid[1]); + break; + + default: + BUG(); + } + + return keyid_str; +} + +const char * +keystr_from_pk(PKT_public_key *pk) +{ + keyid_from_pk(pk,NULL); + + return keystr(pk->keyid); +} + +const char * +keystr_from_sk(PKT_secret_key *sk) +{ + keyid_from_sk(sk,NULL); + + return keystr(sk->keyid); +} + +const char * +keystr_from_desc(KEYDB_SEARCH_DESC *desc) +{ + switch(desc->mode) + { + case KEYDB_SEARCH_MODE_LONG_KID: + case KEYDB_SEARCH_MODE_SHORT_KID: + return keystr(desc->u.kid); + + case KEYDB_SEARCH_MODE_FPR20: + { + u32 keyid[2]; + + keyid[0] = (unsigned char)desc->u.fpr[12] << 24 + | (unsigned char)desc->u.fpr[13] << 16 + | (unsigned char)desc->u.fpr[14] << 8 + | (unsigned char)desc->u.fpr[15] ; + keyid[1] = (unsigned char)desc->u.fpr[16] << 24 + | (unsigned char)desc->u.fpr[17] << 16 + | (unsigned char)desc->u.fpr[18] << 8 + | (unsigned char)desc->u.fpr[19] ; + + return keystr(keyid); + } + + case KEYDB_SEARCH_MODE_FPR16: + return "?v3 fpr?"; + + default: + BUG(); + } +} + +/**************** + * Get the keyid from the secret key and put it into keyid + * if this is not NULL. Return the 32 low bits of the keyid. + */ +u32 +keyid_from_sk( PKT_secret_key *sk, u32 *keyid ) +{ + u32 lowbits; + u32 dummy_keyid[2]; + + if( !keyid ) + keyid = dummy_keyid; + + if( sk->keyid[0] || sk->keyid[1] ) + { + keyid[0] = sk->keyid[0]; + keyid[1] = sk->keyid[1]; + lowbits = keyid[1]; + } + else if( sk->version < 4 ) + { + if( is_RSA(sk->pubkey_algo) ) + { + lowbits = pubkey_get_npkey(sk->pubkey_algo) ? + mpi_get_keyid( sk->skey[0], keyid ) : 0; /* take n */ + sk->keyid[0]=keyid[0]; + sk->keyid[1]=keyid[1]; + } + else + sk->keyid[0]=sk->keyid[1]=keyid[0]=keyid[1]=lowbits=0xFFFFFFFF; + } + else + { + const byte *dp; + MD_HANDLE md; + md = do_fingerprint_md_sk(sk); + if(md) + { + dp = md_read( md, 0 ); + keyid[0] = dp[12] << 24 | dp[13] << 16 | dp[14] << 8 | dp[15] ; + keyid[1] = dp[16] << 24 | dp[17] << 16 | dp[18] << 8 | dp[19] ; + lowbits = keyid[1]; + md_close(md); + sk->keyid[0] = keyid[0]; + sk->keyid[1] = keyid[1]; + } + else + sk->keyid[0]=sk->keyid[1]=keyid[0]=keyid[1]=lowbits=0xFFFFFFFF; + } + + return lowbits; +} + + +/**************** + * Get the keyid from the public key and put it into keyid + * if this is not NULL. Return the 32 low bits of the keyid. + */ +u32 +keyid_from_pk( PKT_public_key *pk, u32 *keyid ) +{ + u32 lowbits; + u32 dummy_keyid[2]; + + if( !keyid ) + keyid = dummy_keyid; + + if( pk->keyid[0] || pk->keyid[1] ) + { + keyid[0] = pk->keyid[0]; + keyid[1] = pk->keyid[1]; + lowbits = keyid[1]; + } + else if( pk->version < 4 ) + { + if( is_RSA(pk->pubkey_algo) ) + { + lowbits = pubkey_get_npkey(pk->pubkey_algo) ? + mpi_get_keyid( pk->pkey[0], keyid ) : 0 ; /* from n */ + pk->keyid[0] = keyid[0]; + pk->keyid[1] = keyid[1]; + } + else + pk->keyid[0]=pk->keyid[1]=keyid[0]=keyid[1]=lowbits=0xFFFFFFFF; + } + else + { + const byte *dp; + MD_HANDLE md; + md = do_fingerprint_md(pk); + if(md) + { + dp = md_read( md, 0 ); + keyid[0] = dp[12] << 24 | dp[13] << 16 | dp[14] << 8 | dp[15] ; + keyid[1] = dp[16] << 24 | dp[17] << 16 | dp[18] << 8 | dp[19] ; + lowbits = keyid[1]; + md_close(md); + pk->keyid[0] = keyid[0]; + pk->keyid[1] = keyid[1]; + } + else + pk->keyid[0]=pk->keyid[1]=keyid[0]=keyid[1]=lowbits=0xFFFFFFFF; + } + + return lowbits; +} + + +/**************** + * Get the keyid from the fingerprint. This function is simple for most + * keys, but has to do a keylookup for old stayle keys. + */ +u32 +keyid_from_fingerprint( const byte *fprint, size_t fprint_len, u32 *keyid ) +{ + u32 dummy_keyid[2]; + + if( !keyid ) + keyid = dummy_keyid; + + if( fprint_len != 20 ) { + /* This is special as we have to lookup the key first */ + PKT_public_key pk; + int rc; + + memset( &pk, 0, sizeof pk ); + rc = get_pubkey_byfprint( &pk, fprint, fprint_len ); + if( rc ) { + log_error("Oops: keyid_from_fingerprint: no pubkey\n"); + keyid[0] = 0; + keyid[1] = 0; + } + else + keyid_from_pk( &pk, keyid ); + } + else { + const byte *dp = fprint; + keyid[0] = dp[12] << 24 | dp[13] << 16 | dp[14] << 8 | dp[15] ; + keyid[1] = dp[16] << 24 | dp[17] << 16 | dp[18] << 8 | dp[19] ; + } + + return keyid[1]; +} + + +u32 +keyid_from_sig( PKT_signature *sig, u32 *keyid ) +{ + if( keyid ) { + keyid[0] = sig->keyid[0]; + keyid[1] = sig->keyid[1]; + } + return sig->keyid[1]; +} + +byte * +namehash_from_uid(PKT_user_id *uid) +{ + if(uid->namehash==NULL) + { + uid->namehash=xmalloc(20); + + if(uid->attrib_data) + rmd160_hash_buffer(uid->namehash,uid->attrib_data,uid->attrib_len); + else + rmd160_hash_buffer(uid->namehash,uid->name,uid->len); + } + + return uid->namehash; +} + +/**************** + * return the number of bits used in the pk + */ +unsigned +nbits_from_pk( PKT_public_key *pk ) +{ + return pubkey_nbits( pk->pubkey_algo, pk->pkey ); +} + +/**************** + * return the number of bits used in the sk + */ +unsigned +nbits_from_sk( PKT_secret_key *sk ) +{ + return pubkey_nbits( sk->pubkey_algo, sk->skey ); +} + +static const char * +mk_datestr (char *buffer, time_t atime) +{ + struct tm *tp; + + if ( atime < 0 ) /* 32 bit time_t and after 2038-01-19 */ + strcpy (buffer, "????" "-??" "-??"); /* mark this as invalid */ + else { + tp = gmtime (&atime); + sprintf (buffer,"%04d-%02d-%02d", + 1900+tp->tm_year, tp->tm_mon+1, tp->tm_mday ); + } + return buffer; +} + +/**************** + * return a string with the creation date of the pk + * Note: this is alloced in a static buffer. + * Format is: yyyy-mm-dd + */ +const char * +datestr_from_pk( PKT_public_key *pk ) +{ + static char buffer[11+5]; + time_t atime = pk->timestamp; + + return mk_datestr (buffer, atime); +} + +const char * +datestr_from_sk( PKT_secret_key *sk ) +{ + static char buffer[11+5]; + time_t atime = sk->timestamp; + + return mk_datestr (buffer, atime); +} + +const char * +datestr_from_sig( PKT_signature *sig ) +{ + static char buffer[11+5]; + time_t atime = sig->timestamp; + + return mk_datestr (buffer, atime); +} + +const char * +expirestr_from_pk( PKT_public_key *pk ) +{ + static char buffer[11+5]; + time_t atime; + + if( !pk->expiredate ) + return _("never "); + atime = pk->expiredate; + return mk_datestr (buffer, atime); +} + +const char * +expirestr_from_sk( PKT_secret_key *sk ) +{ + static char buffer[11+5]; + time_t atime; + + if( !sk->expiredate ) + return _("never "); + atime = sk->expiredate; + return mk_datestr (buffer, atime); +} + +const char * +expirestr_from_sig( PKT_signature *sig ) +{ + static char buffer[11+5]; + time_t atime; + + if(!sig->expiredate) + return _("never "); + atime=sig->expiredate; + return mk_datestr (buffer, atime); +} + +const char * +revokestr_from_pk( PKT_public_key *pk ) +{ + static char buffer[11+5]; + time_t atime; + + if(!pk->revoked.date) + return _("never "); + atime=pk->revoked.date; + return mk_datestr (buffer, atime); +} + + +const char * +usagestr_from_pk( PKT_public_key *pk ) +{ + static char buffer[10]; + int i = 0; + unsigned int use = pk->pubkey_usage; + + if ( use & PUBKEY_USAGE_SIG ) + buffer[i++] = 'S'; + + if ( use & PUBKEY_USAGE_CERT ) + buffer[i++] = 'C'; + + if ( use & PUBKEY_USAGE_ENC ) + buffer[i++] = 'E'; + + if ( (use & PUBKEY_USAGE_AUTH) ) + buffer[i++] = 'A'; + + while (i < 4) + buffer[i++] = ' '; + + buffer[i] = 0; + return buffer; +} + + +const char * +colon_strtime (u32 t) +{ + if (!t) + return ""; + if (opt.fixed_list_mode) { + static char buf[15]; + sprintf (buf, "%lu", (ulong)t); + return buf; + } + return strtimestamp(t); +} + +const char * +colon_datestr_from_pk (PKT_public_key *pk) +{ + if (opt.fixed_list_mode) { + static char buf[15]; + sprintf (buf, "%lu", (ulong)pk->timestamp); + return buf; + } + return datestr_from_pk (pk); +} + +const char * +colon_datestr_from_sk (PKT_secret_key *sk) +{ + if (opt.fixed_list_mode) { + static char buf[15]; + sprintf (buf, "%lu", (ulong)sk->timestamp); + return buf; + } + return datestr_from_sk (sk); +} + +const char * +colon_datestr_from_sig (PKT_signature *sig) +{ + if (opt.fixed_list_mode) { + static char buf[15]; + sprintf (buf, "%lu", (ulong)sig->timestamp); + return buf; + } + return datestr_from_sig (sig); +} + +const char * +colon_expirestr_from_sig (PKT_signature *sig) +{ + if(!sig->expiredate) + return ""; + if (opt.fixed_list_mode) { + static char buf[15]; + sprintf (buf, "%lu", (ulong)sig->expiredate); + return buf; + } + return expirestr_from_sig (sig); +} + + +/**************** . + * Return a byte array with the fingerprint for the given PK/SK + * The length of the array is returned in ret_len. Caller must free + * the array or provide an array of length MAX_FINGERPRINT_LEN. + */ + +byte * +fingerprint_from_pk( PKT_public_key *pk, byte *array, size_t *ret_len ) +{ + byte *p, *buf; + const byte *dp; + size_t len; + unsigned int n; + + if( pk->version < 4 ) + { + if( is_RSA(pk->pubkey_algo) ) + { + /* RSA in version 3 packets is special */ + MD_HANDLE md; + + md = md_open( DIGEST_ALGO_MD5, 0); + if( pubkey_get_npkey( pk->pubkey_algo ) > 1 ) { + p = buf = mpi_get_buffer( pk->pkey[0], &n, NULL ); + md_write( md, p, n ); + xfree(buf); + p = buf = mpi_get_buffer( pk->pkey[1], &n, NULL ); + md_write( md, p, n ); + xfree(buf); + } + md_final(md); + if( !array ) + array = xmalloc( 16 ); + len = 16; + memcpy(array, md_read(md, DIGEST_ALGO_MD5), 16 ); + md_close(md); + } + else + { + if(!array) + array=xmalloc(16); + len=16; + memset(array,0,16); + } + } + else { + MD_HANDLE md; + md = do_fingerprint_md(pk); + dp = md_read( md, 0 ); + len = md_digest_length( md_get_algo( md ) ); + assert( len <= MAX_FINGERPRINT_LEN ); + if( !array ) + array = xmalloc( len ); + memcpy(array, dp, len ); + pk->keyid[0] = dp[12] << 24 | dp[13] << 16 | dp[14] << 8 | dp[15] ; + pk->keyid[1] = dp[16] << 24 | dp[17] << 16 | dp[18] << 8 | dp[19] ; + md_close(md); + } + + *ret_len = len; + return array; +} + +byte * +fingerprint_from_sk( PKT_secret_key *sk, byte *array, size_t *ret_len ) +{ + byte *p, *buf; + const char *dp; + size_t len; + unsigned n; + + if( sk->version < 4 ) + { + if( is_RSA(sk->pubkey_algo) ) + { + /* RSA in version 3 packets is special */ + MD_HANDLE md; + + md = md_open( DIGEST_ALGO_MD5, 0); + if( pubkey_get_npkey( sk->pubkey_algo ) > 1 ) { + p = buf = mpi_get_buffer( sk->skey[0], &n, NULL ); + md_write( md, p, n ); + xfree(buf); + p = buf = mpi_get_buffer( sk->skey[1], &n, NULL ); + md_write( md, p, n ); + xfree(buf); + } + md_final(md); + if( !array ) + array = xmalloc( 16 ); + len = 16; + memcpy(array, md_read(md, DIGEST_ALGO_MD5), 16 ); + md_close(md); + } + else + { + if(!array) + array=xmalloc(16); + len=16; + memset(array,0,16); + } + } + else { + MD_HANDLE md; + md = do_fingerprint_md_sk(sk); + if(md) + { + dp = md_read( md, 0 ); + len = md_digest_length( md_get_algo( md ) ); + assert( len <= MAX_FINGERPRINT_LEN ); + if( !array ) + array = xmalloc( len ); + memcpy(array, dp, len ); + md_close(md); + } + else + { + len=MAX_FINGERPRINT_LEN; + if(!array) + array=xmalloc(len); + memset(array,0,len); + } + } + + *ret_len = len; + return array; +} diff --git a/g10/keylist.c b/g10/keylist.c new file mode 100644 index 0000000..19f3d3f --- /dev/null +++ b/g10/keylist.c @@ -0,0 +1,1638 @@ +/* keylist.c + * Copyright (C) 1998, 1999, 2000, 2001, 2002, 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 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <assert.h> + +#include "options.h" +#include "packet.h" +#include "errors.h" +#include "keydb.h" +#include "memory.h" +#include "photoid.h" +#include "util.h" +#include "ttyio.h" +#include "trustdb.h" +#include "main.h" +#include "i18n.h" +#include "status.h" + +static void list_all(int); +static void list_one( STRLIST names, int secret); +static void print_card_serialno (PKT_secret_key *sk); + +struct sig_stats +{ + int inv_sigs; + int no_key; + int oth_err; +}; + +static FILE *attrib_fp=NULL; + +/**************** + * List the keys + * If list is NULL, all available keys are listed + */ +void +public_key_list( STRLIST list ) +{ + if(opt.with_colons) + { + byte trust_model,marginals,completes,cert_depth; + ulong created,nextcheck; + + read_trust_options(&trust_model,&created,&nextcheck, + &marginals,&completes,&cert_depth); + + printf("tru:"); + + if(nextcheck && nextcheck <= make_timestamp()) + printf("o"); + if(trust_model!=opt.trust_model) + printf("t"); + if(opt.trust_model==TM_PGP || opt.trust_model==TM_CLASSIC) + { + if(marginals!=opt.marginals_needed) + printf("m"); + if(completes!=opt.completes_needed) + printf("c"); + if(cert_depth!=opt.max_cert_depth) + printf("d"); + } + + printf(":%d:%lu:%lu",trust_model,created,nextcheck); + + /* Only show marginals, completes, and cert_depth in the classic + or PGP trust models since they are not meaningful + otherwise. */ + + if(trust_model==TM_PGP || trust_model==TM_CLASSIC) + printf(":%d:%d:%d",marginals,completes,cert_depth); + + printf("\n"); + } + + /* We need to do the stale check right here because it might need to + update the keyring while we already have the keyring open. This + is very bad for W32 because of a sharing violation. For real OSes + it might lead to false results if we are later listing a keyring + which is associated with the inode of a deleted file. */ + check_trustdb_stale (); + + if( !list ) + list_all(0); + else + list_one( list, 0 ); +} + +void +secret_key_list( STRLIST list ) +{ + check_trustdb_stale (); + + if( !list ) + list_all(1); + else /* List by user id */ + list_one( list, 1 ); +} + +void +print_seckey_info (PKT_secret_key *sk) +{ + u32 keyid[2]; + char *p; + + keyid_from_sk (sk, keyid); + p=get_user_id_native(keyid); + + tty_printf ("\nsec %4u%c/%s %s %s\n", + nbits_from_sk (sk), + pubkey_letter (sk->pubkey_algo), + keystr(keyid), datestr_from_sk (sk), p); + + xfree (p); +} + +/* Print information about the public key. With FP passed as NULL, + the tty output interface is used, otherwise output is directted to + the given stream. */ +void +print_pubkey_info (FILE *fp, PKT_public_key *pk) +{ + u32 keyid[2]; + char *p; + + keyid_from_pk (pk, keyid); + + /* If the pk was chosen by a particular user ID, that is the one to + print. */ + if(pk->user_id) + p=utf8_to_native(pk->user_id->name,pk->user_id->len,0); + else + p=get_user_id_native(keyid); + + if (fp) + fprintf (fp, "pub %4u%c/%s %s %s\n", + nbits_from_pk (pk), + pubkey_letter (pk->pubkey_algo), + keystr(keyid), datestr_from_pk (pk), p); + else + tty_printf ("\npub %4u%c/%s %s %s\n", + nbits_from_pk (pk), pubkey_letter (pk->pubkey_algo), + keystr(keyid), datestr_from_pk (pk), p); + + xfree (p); +} + + +/* Print basic information of a secret key including the card serial + number information. */ +void +print_card_key_info (FILE *fp, KBNODE keyblock) +{ + KBNODE node; + int i; + + for (node = keyblock; node; node = node->next ) + { + if (node->pkt->pkttype == PKT_SECRET_KEY + || (node->pkt->pkttype == PKT_SECRET_SUBKEY) ) + { + PKT_secret_key *sk = node->pkt->pkt.secret_key; + + tty_fprintf (fp, "%s%c %4u%c/%s ", + node->pkt->pkttype == PKT_SECRET_KEY? "sec":"ssb", + (sk->protect.s2k.mode==1001)?'#': + (sk->protect.s2k.mode==1002)?'>':' ', + nbits_from_sk (sk), + pubkey_letter (sk->pubkey_algo), + keystr_from_sk(sk)); + tty_fprintf (fp, _("created: %s"), datestr_from_sk (sk)); + tty_fprintf (fp, " "); + tty_fprintf (fp, _("expires: %s"), expirestr_from_sk (sk)); + if (sk->is_protected && sk->protect.s2k.mode == 1002) + { + tty_fprintf (fp, "\n "); + tty_fprintf (fp, _("card-no: ")); + if (sk->protect.ivlen == 16 + && !memcmp (sk->protect.iv, "\xD2\x76\x00\x01\x24\x01", 6)) + { + /* This is an OpenPGP card. */ + for (i=8; i < 14; i++) + { + if (i == 10) + tty_fprintf (fp, " "); + tty_fprintf (fp, "%02X", sk->protect.iv[i]); + } + } + else + { /* Something is wrong: Print all. */ + for (i=0; i < sk->protect.ivlen; i++) + tty_fprintf (fp, "%02X", sk->protect.iv[i]); + } + } + tty_fprintf (fp, "\n"); + } + } +} + + + +/* Flags = 0x01 hashed 0x02 critical */ +static void +status_one_subpacket(sigsubpkttype_t type,size_t len,int flags,const byte *buf) +{ + char status[40]; + + /* Don't print these. */ + if(len>256) + return; + + sprintf(status,"%d %u %u ",type,flags,(unsigned int)len); + + write_status_text_and_buffer(STATUS_SIG_SUBPACKET,status,buf,len,0); +} + +/* + mode=0 for stdout. + mode=1 for log_info + status messages + mode=2 for status messages only +*/ + +void +show_policy_url(PKT_signature *sig,int indent,int mode) +{ + const byte *p; + size_t len; + int seq=0,crit; + FILE *fp=mode?log_stream():stdout; + + while((p=enum_sig_subpkt(sig->hashed,SIGSUBPKT_POLICY,&len,&seq,&crit))) + { + if(mode!=2) + { + int i; + const char *str; + + for(i=0;i<indent;i++) + putchar(' '); + + if(crit) + str=_("Critical signature policy: "); + else + str=_("Signature policy: "); + if(mode) + log_info("%s",str); + else + printf("%s",str); + print_utf8_string(fp,p,len); + fprintf(fp,"\n"); + } + + if(mode) + write_status_buffer ( STATUS_POLICY_URL, p, len, 0 ); + } +} + +/* + mode=0 for stdout. + mode=1 for log_info + status messages + mode=2 for status messages only +*/ +/* TODO: use this */ +void +show_keyserver_url(PKT_signature *sig,int indent,int mode) +{ + const byte *p; + size_t len; + int seq=0,crit; + FILE *fp=mode?log_stream():stdout; + + while((p=enum_sig_subpkt(sig->hashed,SIGSUBPKT_PREF_KS,&len,&seq,&crit))) + { + if(mode!=2) + { + int i; + const char *str; + + for(i=0;i<indent;i++) + putchar(' '); + + if(crit) + str=_("Critical preferred keyserver: "); + else + str=_("Preferred keyserver: "); + if(mode) + log_info("%s",str); + else + printf("%s",str); + print_utf8_string(fp,p,len); + fprintf(fp,"\n"); + } + + if(mode) + status_one_subpacket(SIGSUBPKT_PREF_KS,len,(crit?0x02:0)|0x01,p); + } +} + +/* + mode=0 for stdout. + mode=1 for log_info + status messages + mode=2 for status messages only + + which bits: + 1 == standard notations + 2 == user notations +*/ + +void +show_notation(PKT_signature *sig,int indent,int mode,int which) +{ + FILE *fp=mode?log_stream():stdout; + struct notation *nd,*notations; + + if(which==0) + which=3; + + notations=sig_to_notation(sig); + + /* There may be multiple notations in the same sig. */ + for(nd=notations;nd;nd=nd->next) + { + if(mode!=2) + { + int has_at=!!strchr(nd->name,'@'); + + if((which&1 && !has_at) || (which&2 && has_at)) + { + int i; + const char *str; + + for(i=0;i<indent;i++) + putchar(' '); + + if(nd->flags.critical) + str=_("Critical signature notation: "); + else + str=_("Signature notation: "); + if(mode) + log_info("%s",str); + else + printf("%s",str); + /* This is all UTF8 */ + print_utf8_string(fp,nd->name,strlen(nd->name)); + fprintf(fp,"="); + print_utf8_string(fp,nd->value,strlen(nd->value)); + fprintf(fp,"\n"); + } + } + + if(mode) + { + write_status_buffer(STATUS_NOTATION_NAME, + nd->name,strlen(nd->name),0); + write_status_buffer(STATUS_NOTATION_DATA, + nd->value,strlen(nd->value),50); + } + } + + free_notation(notations); +} + +static void +print_signature_stats(struct sig_stats *s) +{ + if( s->inv_sigs == 1 ) + tty_printf(_("1 bad signature\n") ); + else if( s->inv_sigs ) + tty_printf(_("%d bad signatures\n"), s->inv_sigs ); + if( s->no_key == 1 ) + tty_printf(_("1 signature not checked due to a missing key\n") ); + else if( s->no_key ) + tty_printf(_("%d signatures not checked due to missing keys\n"),s->no_key); + if( s->oth_err == 1 ) + tty_printf(_("1 signature not checked due to an error\n") ); + else if( s->oth_err ) + tty_printf(_("%d signatures not checked due to errors\n"), s->oth_err ); +} + +static void +list_all( int secret ) +{ + KEYDB_HANDLE hd; + KBNODE keyblock = NULL; + int rc=0; + const char *lastresname, *resname; + struct sig_stats stats; + + memset(&stats,0,sizeof(stats)); + + hd = keydb_new (secret); + if (!hd) + rc = G10ERR_GENERAL; + else + rc = keydb_search_first (hd); + if( rc ) { + if( rc != -1 ) + log_error("keydb_search_first failed: %s\n", g10_errstr(rc) ); + goto leave; + } + + lastresname = NULL; + do { + rc = keydb_get_keyblock (hd, &keyblock); + if (rc) { + log_error ("keydb_get_keyblock failed: %s\n", g10_errstr(rc)); + goto leave; + } + if(!opt.with_colons) + { + resname = keydb_get_resource_name (hd); + if (lastresname != resname ) + { + int i; + + printf("%s\n", resname ); + for(i=strlen(resname); i; i-- ) + putchar('-'); + putchar('\n'); + lastresname = resname; + } + } + merge_keys_and_selfsig( keyblock ); + list_keyblock( keyblock, secret, opt.fingerprint, + opt.check_sigs?&stats:NULL); + release_kbnode( keyblock ); + keyblock = NULL; + } while (!(rc = keydb_search_next (hd))); + if( rc && rc != -1 ) + log_error ("keydb_search_next failed: %s\n", g10_errstr(rc)); + + if(opt.check_sigs && !opt.with_colons) + print_signature_stats(&stats); + + leave: + release_kbnode (keyblock); + keydb_release (hd); +} + + +static void +list_one( STRLIST names, int secret ) +{ + int rc = 0; + KBNODE keyblock = NULL; + GETKEY_CTX ctx; + const char *resname; + const char *keyring_str = _("Keyring"); + int i; + struct sig_stats stats; + + memset(&stats,0,sizeof(stats)); + + /* fixme: using the bynames function has the disadvantage that we + * don't know wether one of the names given was not found. OTOH, + * this function has the advantage to list the names in the + * sequence as defined by the keyDB and does not duplicate + * outputs. A solution could be do test whether all given have + * been listed (this needs a way to use the keyDB search + * functions) or to have the search function return indicators for + * found names. Yet another way is to use the keydb search + * facilities directly. */ + if( secret ) { + rc = get_seckey_bynames( &ctx, NULL, names, &keyblock ); + if( rc ) { + log_error("error reading key: %s\n", g10_errstr(rc) ); + get_seckey_end( ctx ); + return; + } + do { + if ((opt.list_options&LIST_SHOW_KEYRING) && !opt.with_colons) { + resname = keydb_get_resource_name (get_ctx_handle(ctx)); + printf("%s: %s\n", keyring_str, resname); + for(i = strlen(resname) + strlen(keyring_str) + 2; i; i-- ) + putchar('-'); + putchar('\n'); + } + list_keyblock( keyblock, 1, opt.fingerprint, NULL ); + release_kbnode( keyblock ); + } while( !get_seckey_next( ctx, NULL, &keyblock ) ); + get_seckey_end( ctx ); + } + else { + rc = get_pubkey_bynames( &ctx, NULL, names, &keyblock ); + if( rc ) { + log_error("error reading key: %s\n", g10_errstr(rc) ); + get_pubkey_end( ctx ); + return; + } + do { + if ((opt.list_options&LIST_SHOW_KEYRING) && !opt.with_colons) { + resname = keydb_get_resource_name (get_ctx_handle(ctx)); + printf("%s: %s\n", keyring_str, resname); + for(i = strlen(resname) + strlen(keyring_str) + 2; i; i-- ) + putchar('-'); + putchar('\n'); + } + list_keyblock( keyblock, 0, opt.fingerprint, + opt.check_sigs?&stats:NULL ); + release_kbnode( keyblock ); + } while( !get_pubkey_next( ctx, NULL, &keyblock ) ); + get_pubkey_end( ctx ); + } + + if(opt.check_sigs && !opt.with_colons) + print_signature_stats(&stats); +} + +static void +print_key_data( PKT_public_key *pk ) +{ + int n = pk ? pubkey_get_npkey( pk->pubkey_algo ) : 0; + int i; + + for(i=0; i < n; i++ ) { + printf("pkd:%d:%u:", i, mpi_get_nbits( pk->pkey[i] ) ); + mpi_print(stdout, pk->pkey[i], 1 ); + putchar(':'); + putchar('\n'); + } +} + +static void +print_capabilities (PKT_public_key *pk, PKT_secret_key *sk, KBNODE keyblock) +{ + if(pk || (sk && sk->protect.s2k.mode!=1001)) + { + unsigned int use = pk? pk->pubkey_usage : sk->pubkey_usage; + + if ( use & PUBKEY_USAGE_ENC ) + putchar ('e'); + + if ( use & PUBKEY_USAGE_SIG ) + { + putchar ('s'); + if( pk? pk->is_primary : sk->is_primary ) + putchar ('c'); + } + + if ( (use & PUBKEY_USAGE_AUTH) ) + putchar ('a'); + } + + if ( keyblock ) { /* figure out the usable capabilities */ + KBNODE k; + int enc=0, sign=0, cert=0, auth=0, disabled=0; + + for (k=keyblock; k; k = k->next ) { + if ( k->pkt->pkttype == PKT_PUBLIC_KEY + || k->pkt->pkttype == PKT_PUBLIC_SUBKEY ) { + pk = k->pkt->pkt.public_key; + + if(pk->is_primary) + disabled=pk_is_disabled(pk); + + if ( pk->is_valid && !pk->is_revoked && !pk->has_expired ) { + if ( pk->pubkey_usage & PUBKEY_USAGE_ENC ) + enc = 1; + if ( pk->pubkey_usage & PUBKEY_USAGE_SIG ) + { + sign = 1; + if(pk->is_primary) + cert = 1; + } + if ( (pk->pubkey_usage & PUBKEY_USAGE_AUTH) ) + auth = 1; + } + } + else if ( k->pkt->pkttype == PKT_SECRET_KEY + || k->pkt->pkttype == PKT_SECRET_SUBKEY ) { + sk = k->pkt->pkt.secret_key; + if ( sk->is_valid && !sk->is_revoked && !sk->has_expired + && sk->protect.s2k.mode!=1001 ) { + if ( sk->pubkey_usage & PUBKEY_USAGE_ENC ) + enc = 1; + if ( sk->pubkey_usage & PUBKEY_USAGE_SIG ) + { + sign = 1; + if(sk->is_primary) + cert = 1; + } + if ( (sk->pubkey_usage & PUBKEY_USAGE_AUTH) ) + auth = 1; + } + } + } + if (enc) + putchar ('E'); + if (sign) + putchar ('S'); + if (cert) + putchar ('C'); + if (auth) + putchar ('A'); + if (disabled) + putchar ('D'); + } + + putchar(':'); +} + +/* Flags = 0x01 hashed 0x02 critical */ +static void +print_one_subpacket(sigsubpkttype_t type,size_t len,int flags,const byte *buf) +{ + size_t i; + + printf("spk:%d:%u:%u:",type,flags,(unsigned int)len); + + for(i=0;i<len;i++) + { + /* printable ascii other than : and % */ + if(buf[i]>=32 && buf[i]<=126 && buf[i]!=':' && buf[i]!='%') + printf("%c",buf[i]); + else + printf("%%%02X",buf[i]); + } + + printf("\n"); +} + +void +print_subpackets_colon(PKT_signature *sig) +{ + byte *i; + + assert(opt.show_subpackets); + + for(i=opt.show_subpackets;*i;i++) + { + const byte *p; + size_t len; + int seq,crit; + + seq=0; + + while((p=enum_sig_subpkt(sig->hashed,*i,&len,&seq,&crit))) + print_one_subpacket(*i,len,0x01|(crit?0x02:0),p); + + seq=0; + + while((p=enum_sig_subpkt(sig->unhashed,*i,&len,&seq,&crit))) + print_one_subpacket(*i,len,0x00|(crit?0x02:0),p); + } +} + +void +dump_attribs(const PKT_user_id *uid,PKT_public_key *pk,PKT_secret_key *sk) +{ + int i; + + if(!attrib_fp) + return; + + for(i=0;i<uid->numattribs;i++) + { + if(is_status_enabled()) + { + byte array[MAX_FINGERPRINT_LEN], *p; + char buf[(MAX_FINGERPRINT_LEN*2)+90]; + size_t j,n; + + if(pk) + fingerprint_from_pk( pk, array, &n ); + else if(sk) + fingerprint_from_sk( sk, array, &n ); + else + BUG(); + + p = array; + for(j=0; j < n ; j++, p++ ) + sprintf(buf+2*j, "%02X", *p ); + + sprintf(buf+strlen(buf)," %lu %u %u %u %lu %lu %u", + (ulong)uid->attribs[i].len,uid->attribs[i].type,i+1, + uid->numattribs,(ulong)uid->created,(ulong)uid->expiredate, + ((uid->is_primary?0x01:0)| + (uid->is_revoked?0x02:0)| + (uid->is_expired?0x04:0))); + write_status_text(STATUS_ATTRIBUTE,buf); + } + + fwrite(uid->attribs[i].data,uid->attribs[i].len,1,attrib_fp); + } +} + +static void +list_keyblock_print ( KBNODE keyblock, int secret, int fpr, void *opaque ) +{ + int rc = 0; + KBNODE kbctx; + KBNODE node; + PKT_public_key *pk; + PKT_secret_key *sk; + struct sig_stats *stats=opaque; + int skip_sigs=0; + + /* get the keyid from the keyblock */ + node = find_kbnode( keyblock, secret? PKT_SECRET_KEY : PKT_PUBLIC_KEY ); + if( !node ) { + log_error("Oops; key lost!\n"); + dump_kbnode( keyblock ); + return; + } + + if( secret ) + { + pk = NULL; + sk = node->pkt->pkt.secret_key; + + printf("sec%c %4u%c/%s %s",(sk->protect.s2k.mode==1001)?'#': + (sk->protect.s2k.mode==1002)?'>':' ', + nbits_from_sk( sk ),pubkey_letter( sk->pubkey_algo ), + keystr_from_sk(sk),datestr_from_sk( sk )); + + if(sk->has_expired) + { + printf(" ["); + printf(_("expired: %s"),expirestr_from_sk(sk)); + printf("]"); + } + else if(sk->expiredate ) + { + printf(" ["); + printf(_("expires: %s"),expirestr_from_sk(sk)); + printf("]"); + } + + printf("\n"); + } + else + { + pk = node->pkt->pkt.public_key; + sk = NULL; + + check_trustdb_stale(); + + printf("pub %4u%c/%s %s", + nbits_from_pk(pk),pubkey_letter(pk->pubkey_algo), + keystr_from_pk(pk),datestr_from_pk( pk )); + + /* We didn't include this before in the key listing, but there + is room in the new format, so why not? */ + + if(pk->is_revoked) + { + printf(" ["); + printf(_("revoked: %s"),revokestr_from_pk(pk)); + printf("]"); + } + else if(pk->has_expired) + { + printf(" ["); + printf(_("expired: %s"),expirestr_from_pk(pk)); + printf("]"); + } + else if(pk->expiredate) + { + printf(" ["); + printf(_("expires: %s"),expirestr_from_pk(pk)); + printf("]"); + } + +#if 0 + /* I need to think about this some more. It's easy enough to + include, but it looks sort of confusing in the + listing... */ + if(opt.list_options&LIST_SHOW_VALIDITY) + { + int validity=get_validity(pk,NULL); + printf(" [%s]",trust_value_to_string(validity)); + } +#endif + + printf("\n"); + } + + if( fpr ) + print_fingerprint( pk, sk, 0 ); + print_card_serialno (sk); + if( opt.with_key_data ) + print_key_data( pk ); + + for( kbctx=NULL; (node=walk_kbnode( keyblock, &kbctx, 0)) ; ) { + if( node->pkt->pkttype == PKT_USER_ID && !opt.fast_list_mode ) { + PKT_user_id *uid=node->pkt->pkt.user_id; + + if(pk && (uid->is_expired || uid->is_revoked) + && !(opt.list_options&LIST_SHOW_UNUSABLE_UIDS)) + { + skip_sigs=1; + continue; + } + else + skip_sigs=0; + + if(attrib_fp && uid->attrib_data!=NULL) + dump_attribs(uid,pk,sk); + + if((uid->is_revoked || uid->is_expired) + || ((opt.list_options&LIST_SHOW_UID_VALIDITY) && pk)) + { + const char *validity; + int indent; + + validity=uid_trust_string_fixed(pk,uid); + indent=(keystrlen()+9)-atoi(uid_trust_string_fixed(NULL,NULL)); + + if(indent<0 || indent>40) + indent=0; + + printf("uid%*s%s ",indent,"",validity); + } + else + printf("uid%*s", (int)keystrlen()+10,""); + + print_utf8_string( stdout, uid->name, uid->len ); + putchar('\n'); + + if((opt.list_options&LIST_SHOW_PHOTOS) && uid->attribs!=NULL) + show_photos(uid->attribs,uid->numattribs,pk,sk); + } + else if( node->pkt->pkttype == PKT_PUBLIC_SUBKEY ) + { + PKT_public_key *pk2 = node->pkt->pkt.public_key; + + if((pk2->is_revoked || pk2->has_expired) + && !(opt.list_options&LIST_SHOW_UNUSABLE_SUBKEYS)) + { + skip_sigs=1; + continue; + } + else + skip_sigs=0; + + printf("sub %4u%c/%s %s", + nbits_from_pk( pk2 ),pubkey_letter( pk2->pubkey_algo ), + keystr_from_pk(pk2),datestr_from_pk(pk2)); + if( pk2->is_revoked ) + { + printf(" ["); + printf(_("revoked: %s"),revokestr_from_pk(pk2)); + printf("]"); + } + else if( pk2->has_expired ) + { + printf(" ["); + printf(_("expired: %s"),expirestr_from_pk(pk2)); + printf("]"); + } + else if( pk2->expiredate ) + { + printf(" ["); + printf(_("expires: %s"),expirestr_from_pk(pk2)); + printf("]"); + } + putchar('\n'); + if( fpr > 1 ) + print_fingerprint( pk2, NULL, 0 ); + if( opt.with_key_data ) + print_key_data( pk2 ); + } + else if( node->pkt->pkttype == PKT_SECRET_SUBKEY ) + { + PKT_secret_key *sk2 = node->pkt->pkt.secret_key; + + printf("ssb%c %4u%c/%s %s", + (sk2->protect.s2k.mode==1001)?'#': + (sk2->protect.s2k.mode==1002)?'>':' ', + nbits_from_sk( sk2 ),pubkey_letter( sk2->pubkey_algo ), + keystr_from_sk(sk2),datestr_from_sk( sk2 ) ); + if( sk2->expiredate ) + { + printf(" ["); + printf(_("expires: %s"),expirestr_from_sk(sk2)); + printf("]"); + } + putchar('\n'); + if( fpr > 1 ) + { + print_fingerprint( NULL, sk2, 0 ); + print_card_serialno (sk2); + } + } + else if( opt.list_sigs + && node->pkt->pkttype == PKT_SIGNATURE + && !skip_sigs ) { + PKT_signature *sig = node->pkt->pkt.signature; + int sigrc; + char *sigstr; + + if( stats ) { + /*fflush(stdout);*/ + rc = check_key_signature( keyblock, node, NULL ); + switch( rc ) { + case 0: sigrc = '!'; break; + case G10ERR_BAD_SIGN: stats->inv_sigs++; sigrc = '-'; break; + case G10ERR_NO_PUBKEY: + case G10ERR_UNU_PUBKEY: stats->no_key++; continue; + default: stats->oth_err++; sigrc = '%'; break; + } + + /* TODO: Make sure a cached sig record here still has + the pk that issued it. See also + keyedit.c:print_and_check_one_sig */ + } + else { + rc = 0; + sigrc = ' '; + } + + if( sig->sig_class == 0x20 || sig->sig_class == 0x28 + || sig->sig_class == 0x30 ) + sigstr = "rev"; + else if( (sig->sig_class&~3) == 0x10 ) + sigstr = "sig"; + else if( sig->sig_class == 0x18 ) + sigstr = "sig"; + else if( sig->sig_class == 0x1F ) + sigstr = "sig"; + else { + printf("sig " + "[unexpected signature class 0x%02x]\n",sig->sig_class ); + continue; + } + + fputs( sigstr, stdout ); + printf("%c%c %c%c%c%c%c%c %s %s", + sigrc,(sig->sig_class-0x10>0 && + sig->sig_class-0x10<4)?'0'+sig->sig_class-0x10:' ', + sig->flags.exportable?' ':'L', + sig->flags.revocable?' ':'R', + sig->flags.policy_url?'P':' ', + sig->flags.notation?'N':' ', + sig->flags.expired?'X':' ', + (sig->trust_depth>9)?'T': + (sig->trust_depth>0)?'0'+sig->trust_depth:' ', + keystr(sig->keyid),datestr_from_sig(sig)); + if(opt.list_options&LIST_SHOW_SIG_EXPIRE) + printf(" %s", expirestr_from_sig(sig)); + printf(" "); + if( sigrc == '%' ) + printf("[%s] ", g10_errstr(rc) ); + else if( sigrc == '?' ) + ; + else if ( !opt.fast_list_mode ) { + size_t n; + char *p = get_user_id( sig->keyid, &n ); + print_utf8_string( stdout, p, n ); + xfree(p); + } + putchar('\n'); + + if(sig->flags.policy_url + && (opt.list_options&LIST_SHOW_POLICY_URLS)) + show_policy_url(sig,3,0); + + if(sig->flags.notation && (opt.list_options&LIST_SHOW_NOTATIONS)) + show_notation(sig,3,0, + ((opt.list_options&LIST_SHOW_STD_NOTATIONS)?1:0)+ + ((opt.list_options&LIST_SHOW_USER_NOTATIONS)?2:0)); + + if(sig->flags.pref_ks + && (opt.list_options&LIST_SHOW_KEYSERVER_URLS)) + show_keyserver_url(sig,3,0); + + /* fixme: check or list other sigs here */ + } + } + putchar('\n'); +} + +void +print_revokers(PKT_public_key *pk) +{ + /* print the revoker record */ + if( !pk->revkey && pk->numrevkeys ) + BUG(); + else + { + int i,j; + + for (i=0; i < pk->numrevkeys; i++) + { + byte *p; + + printf ("rvk:::%d::::::", pk->revkey[i].algid); + p = pk->revkey[i].fpr; + for (j=0; j < 20; j++, p++ ) + printf ("%02X", *p); + printf (":%02x%s:\n", pk->revkey[i].class, + (pk->revkey[i].class&0x40)?"s":""); + } + } +} + +static void +list_keyblock_colon( KBNODE keyblock, int secret, int fpr ) +{ + int rc = 0; + KBNODE kbctx; + KBNODE node; + PKT_public_key *pk; + PKT_secret_key *sk; + u32 keyid[2]; + int any=0; + int trustletter = 0; + int ulti_hack = 0; + int i; + + /* get the keyid from the keyblock */ + node = find_kbnode( keyblock, secret? PKT_SECRET_KEY : PKT_PUBLIC_KEY ); + if( !node ) { + log_error("Oops; key lost!\n"); + dump_kbnode( keyblock ); + return; + } + + if( secret ) { + pk = NULL; + sk = node->pkt->pkt.secret_key; + keyid_from_sk( sk, keyid ); + printf("sec::%u:%d:%08lX%08lX:%s:%s:::", + nbits_from_sk( sk ), + sk->pubkey_algo, + (ulong)keyid[0],(ulong)keyid[1], + colon_datestr_from_sk( sk ), + colon_strtime (sk->expiredate) + /* fixme: add LID here */ ); + } + else { + pk = node->pkt->pkt.public_key; + sk = NULL; + keyid_from_pk( pk, keyid ); + fputs( "pub:", stdout ); + if ( !pk->is_valid ) + putchar ('i'); + else if ( pk->is_revoked ) + putchar ('r'); + else if ( pk->has_expired ) + putchar ('e'); + else if ( opt.fast_list_mode || opt.no_expensive_trust_checks ) + ; + else { + trustletter = get_validity_info ( pk, NULL ); + if( trustletter == 'u' ) + ulti_hack = 1; + putchar(trustletter); + } + printf(":%u:%d:%08lX%08lX:%s:%s::", + nbits_from_pk( pk ), + pk->pubkey_algo, + (ulong)keyid[0],(ulong)keyid[1], + colon_datestr_from_pk( pk ), + colon_strtime (pk->expiredate) ); + if( !opt.fast_list_mode && !opt.no_expensive_trust_checks ) + putchar( get_ownertrust_info(pk) ); + putchar(':'); + } + + if (opt.fixed_list_mode) { + /* do not merge the first uid with the primary key */ + putchar(':'); + putchar(':'); + print_capabilities (pk, sk, keyblock); + if (secret) { + putchar(':'); /* End of field 13. */ + putchar(':'); /* End of field 14. */ + if (sk->protect.s2k.mode == 1001) + putchar('#'); /* Key is just a stub. */ + else if (sk->protect.s2k.mode == 1002) { + /* Key is stored on an external token (card) or handled by + the gpg-agent. Print the serial number of that token + here. */ + for (i=0; i < sk->protect.ivlen; i++) + printf ("%02X", sk->protect.iv[i]); + } + putchar(':'); /* End of field 15. */ + } + putchar('\n'); + if(pk) + print_revokers(pk); + if( fpr ) + print_fingerprint( pk, sk, 0 ); + if( opt.with_key_data ) + print_key_data( pk ); + any = 1; + } + + for( kbctx=NULL; (node=walk_kbnode( keyblock, &kbctx, 0)) ; ) { + if( node->pkt->pkttype == PKT_USER_ID && !opt.fast_list_mode ) { + PKT_user_id *uid=node->pkt->pkt.user_id; + if(attrib_fp && node->pkt->pkt.user_id->attrib_data!=NULL) + dump_attribs(node->pkt->pkt.user_id,pk,sk); + /* + * Fixme: We need a is_valid flag here too + */ + if( any ) { + char *str=uid->attrib_data?"uat":"uid"; + /* If we're listing a secret key, leave out the + validity values for now. This is handled better in + 1.9. */ + if ( sk ) + printf("%s:::::",str); + else if ( uid->is_revoked ) + printf("%s:r::::",str); + else if ( uid->is_expired ) + printf("%s:e::::",str); + else if ( opt.no_expensive_trust_checks ) + printf("%s:::::",str); + else { + int uid_validity; + + if( pk && !ulti_hack ) + uid_validity=get_validity_info (pk, uid); + else + uid_validity = 'u'; + printf("%s:%c::::",str,uid_validity); + } + + printf("%s:",colon_strtime(uid->created)); + printf("%s:",colon_strtime(uid->expiredate)); + + namehash_from_uid(uid); + + for(i=0; i < 20; i++ ) + printf("%02X",uid->namehash[i]); + + printf("::"); + } + if(uid->attrib_data) + printf("%u %lu",uid->numattribs,uid->attrib_len); + else + print_string(stdout,uid->name,uid->len, ':' ); + putchar(':'); + if (any) + putchar('\n'); + else { + putchar(':'); + print_capabilities (pk, sk, keyblock); + putchar('\n'); + if( fpr ) + print_fingerprint( pk, sk, 0 ); + if( opt.with_key_data ) + print_key_data( pk ); + any = 1; + } + } + else if( node->pkt->pkttype == PKT_PUBLIC_SUBKEY ) { + u32 keyid2[2]; + PKT_public_key *pk2 = node->pkt->pkt.public_key; + + if( !any ) { + putchar(':'); + putchar(':'); + print_capabilities (pk, sk, keyblock); + putchar('\n'); + if( fpr ) + print_fingerprint( pk, sk, 0 ); /* of the main key */ + any = 1; + } + + keyid_from_pk( pk2, keyid2 ); + fputs ("sub:", stdout ); + if ( !pk2->is_valid ) + putchar ('i'); + else if ( pk2->is_revoked ) + putchar ('r'); + else if ( pk2->has_expired ) + putchar ('e'); + else if ( opt.fast_list_mode || opt.no_expensive_trust_checks ) + ; + else { + /* trustletter should always be defined here */ + if(trustletter) + printf("%c", trustletter ); + } + printf(":%u:%d:%08lX%08lX:%s:%s:::::", + nbits_from_pk( pk2 ), + pk2->pubkey_algo, + (ulong)keyid2[0],(ulong)keyid2[1], + colon_datestr_from_pk( pk2 ), + colon_strtime (pk2->expiredate) + /* fixme: add LID and ownertrust here */ + ); + print_capabilities (pk2, NULL, NULL); + putchar('\n'); + if( fpr > 1 ) + print_fingerprint( pk2, NULL, 0 ); + if( opt.with_key_data ) + print_key_data( pk2 ); + } + else if( node->pkt->pkttype == PKT_SECRET_SUBKEY ) { + u32 keyid2[2]; + PKT_secret_key *sk2 = node->pkt->pkt.secret_key; + + if( !any ) { + putchar(':'); + putchar(':'); + print_capabilities (pk, sk, keyblock); + putchar('\n'); + if( fpr ) + print_fingerprint( pk, sk, 0 ); /* of the main key */ + any = 1; + } + + keyid_from_sk( sk2, keyid2 ); + printf("ssb::%u:%d:%08lX%08lX:%s:%s:::::", + nbits_from_sk( sk2 ), + sk2->pubkey_algo, + (ulong)keyid2[0],(ulong)keyid2[1], + colon_datestr_from_sk( sk2 ), + colon_strtime (sk2->expiredate) + /* fixme: add LID */ ); + print_capabilities (NULL, sk2, NULL); + if (opt.fixed_list_mode) { + /* We print the serial number only in fixed list mode + for the primary key so, so avoid questions we print + it for subkeys also only in this mode. There is no + technical reason, though. */ + putchar(':'); /* End of field 13. */ + putchar(':'); /* End of field 14. */ + if (sk2->protect.s2k.mode == 1001) + putchar('#'); /* Key is just a stub. */ + else if (sk2->protect.s2k.mode == 1002) { + /* Key is stored on an external token (card) or handled by + the gpg-agent. Print the serial number of that token + here. */ + for (i=0; i < sk2->protect.ivlen; i++) + printf ("%02X", sk2->protect.iv[i]); + } + putchar(':'); /* End of field 15. */ + } + putchar ('\n'); + if( fpr > 1 ) + print_fingerprint( NULL, sk2, 0 ); + } + else if( opt.list_sigs && node->pkt->pkttype == PKT_SIGNATURE ) { + PKT_signature *sig = node->pkt->pkt.signature; + int sigrc,fprokay=0; + char *sigstr; + size_t fplen; + byte fparray[MAX_FINGERPRINT_LEN]; + + if( !any ) { /* no user id, (maybe a revocation follows)*/ + if( sig->sig_class == 0x20 ) + fputs("[revoked]:", stdout); + else if( sig->sig_class == 0x18 ) + fputs("[key binding]:", stdout); + else if( sig->sig_class == 0x28 ) + fputs("[subkey revoked]:", stdout); + else + putchar (':'); + putchar(':'); + print_capabilities (pk, sk, keyblock); + putchar('\n'); + if( fpr ) + print_fingerprint( pk, sk, 0 ); + any=1; + } + + if( sig->sig_class == 0x20 || sig->sig_class == 0x28 + || sig->sig_class == 0x30 ) + sigstr = "rev"; + else if( (sig->sig_class&~3) == 0x10 ) + sigstr = "sig"; + else if( sig->sig_class == 0x18 ) + sigstr = "sig"; + else if( sig->sig_class == 0x1F ) + sigstr = "sig"; + else { + printf ("sig::::::::::%02x%c:\n", + sig->sig_class, sig->flags.exportable?'x':'l'); + continue; + } + if( opt.check_sigs ) { + PKT_public_key *signer_pk=NULL; + + fflush(stdout); + if(opt.no_sig_cache) + signer_pk=xmalloc_clear(sizeof(PKT_public_key)); + + rc = check_key_signature2( keyblock, node, NULL, signer_pk, + NULL, NULL, NULL ); + switch( rc ) { + case 0: sigrc = '!'; break; + case G10ERR_BAD_SIGN: sigrc = '-'; break; + case G10ERR_NO_PUBKEY: + case G10ERR_UNU_PUBKEY: sigrc = '?'; break; + default: sigrc = '%'; break; + } + + if(opt.no_sig_cache) + { + if(rc==0) + { + fingerprint_from_pk (signer_pk, fparray, &fplen); + fprokay=1; + } + free_public_key(signer_pk); + } + } + else { + rc = 0; + sigrc = ' '; + } + fputs( sigstr, stdout ); + putchar(':'); + if( sigrc != ' ' ) + putchar(sigrc); + printf("::%d:%08lX%08lX:%s:%s:", sig->pubkey_algo, + (ulong)sig->keyid[0], (ulong)sig->keyid[1], + colon_datestr_from_sig(sig), + colon_expirestr_from_sig(sig)); + + if(sig->trust_depth || sig->trust_value) + printf("%d %d",sig->trust_depth,sig->trust_value); + printf(":"); + + if(sig->trust_regexp) + print_string(stdout,sig->trust_regexp, + strlen(sig->trust_regexp),':'); + printf(":"); + + if( sigrc == '%' ) + printf("[%s] ", g10_errstr(rc) ); + else if( sigrc == '?' ) + ; + else if ( !opt.fast_list_mode ) { + size_t n; + char *p = get_user_id( sig->keyid, &n ); + print_string( stdout, p, n, ':' ); + xfree(p); + } + printf(":%02x%c:", sig->sig_class,sig->flags.exportable?'x':'l'); + + if(opt.no_sig_cache && opt.check_sigs && fprokay) + { + printf(":"); + + for (i=0; i < fplen ; i++ ) + printf ("%02X", fparray[i] ); + + printf(":"); + } + + printf("\n"); + + if(opt.show_subpackets) + print_subpackets_colon(sig); + + /* fixme: check or list other sigs here */ + } + } + if( !any ) {/* oops, no user id */ + putchar(':'); + putchar(':'); + print_capabilities (pk, sk, keyblock); + putchar('\n'); + } +} + +/* + * Reorder the keyblock so that the primary user ID (and not attribute + * packet) comes first. Fixme: Replace this by a generic sort + * function. */ +static void +do_reorder_keyblock (KBNODE keyblock,int attr) +{ + KBNODE primary = NULL, primary0 = NULL, primary2 = NULL; + KBNODE last, node; + + for (node=keyblock; node; primary0=node, node = node->next) { + if( node->pkt->pkttype == PKT_USER_ID && + ((attr && node->pkt->pkt.user_id->attrib_data) || + (!attr && !node->pkt->pkt.user_id->attrib_data)) && + node->pkt->pkt.user_id->is_primary ) { + primary = primary2 = node; + for (node=node->next; node; primary2=node, node = node->next ) { + if( node->pkt->pkttype == PKT_USER_ID + || node->pkt->pkttype == PKT_PUBLIC_SUBKEY + || node->pkt->pkttype == PKT_SECRET_SUBKEY ) { + break; + } + } + break; + } + } + if ( !primary ) + return; /* no primary key flag found (should not happen) */ + + for (last=NULL, node=keyblock; node; last = node, node = node->next) { + if( node->pkt->pkttype == PKT_USER_ID ) + break; + } + assert (node); + assert (last); /* the user ID is never the first packet */ + assert (primary0); /* ditto (this is the node before primary) */ + if ( node == primary ) + return; /* already the first one */ + + last->next = primary; + primary0->next = primary2->next; + primary2->next = node; +} + +void +reorder_keyblock (KBNODE keyblock) +{ + do_reorder_keyblock(keyblock,1); + do_reorder_keyblock(keyblock,0); +} + +void +list_keyblock( KBNODE keyblock, int secret, int fpr, void *opaque ) +{ + reorder_keyblock (keyblock); + if (opt.with_colons) + list_keyblock_colon (keyblock, secret, fpr ); + else + list_keyblock_print (keyblock, secret, fpr, opaque ); +} + +/* + * standard function to print the finperprint. + * mode 0: as used in key listings, opt.with_colons is honored + * 1: print using log_info () + * 2: direct use of tty + * 3: direct use of tty but only primary key. + * modes 1 and 2 will try and print both subkey and primary key fingerprints + */ +void +print_fingerprint (PKT_public_key *pk, PKT_secret_key *sk, int mode ) +{ + byte array[MAX_FINGERPRINT_LEN], *p; + size_t i, n; + FILE *fp; + const char *text; + int primary=0; + + if(sk) + { + if(sk->main_keyid[0]==sk->keyid[0] && sk->main_keyid[1]==sk->keyid[1]) + primary=1; + } + else + { + if(pk->main_keyid[0]==pk->keyid[0] && pk->main_keyid[1]==pk->keyid[1]) + primary=1; + } + + /* Just to be safe */ + if(mode&0x80 && !primary) + { + log_error("primary key is not really primary!\n"); + return; + } + + mode&=~0x80; + + if(!primary && (mode==1 || mode==2)) + { + if(sk) + { + PKT_secret_key *primary_sk=xmalloc_clear(sizeof(*primary_sk)); + get_seckey(primary_sk,sk->main_keyid); + print_fingerprint(NULL,primary_sk,mode|0x80); + free_secret_key(primary_sk); + } + else + { + PKT_public_key *primary_pk=xmalloc_clear(sizeof(*primary_pk)); + get_pubkey(primary_pk,pk->main_keyid); + print_fingerprint(primary_pk,NULL,mode|0x80); + free_public_key(primary_pk); + } + } + + if (mode == 1) { + fp = log_stream (); + if(primary) + text = _("Primary key fingerprint:"); + else + text = _(" Subkey fingerprint:"); + } + else if (mode == 2) { + fp = NULL; /* use tty */ + if(primary) + /* TRANSLATORS: this should fit into 24 bytes to that the + * fingerprint data is properly aligned with the user ID */ + text = _(" Primary key fingerprint:"); + else + text = _(" Subkey fingerprint:"); + } + else if (mode == 3) { + fp = NULL; /* use tty */ + text = _(" Key fingerprint ="); + } + else { + fp = stdout; + text = _(" Key fingerprint ="); + } + + if (sk) + fingerprint_from_sk (sk, array, &n); + else + fingerprint_from_pk (pk, array, &n); + p = array; + if (opt.with_colons && !mode) { + fprintf (fp, "fpr:::::::::"); + for (i=0; i < n ; i++, p++ ) + fprintf (fp, "%02X", *p ); + putc(':', fp); + } + else { + if (fp) + fputs (text, fp); + else + tty_printf ("%s", text); + if (n == 20) { + for (i=0; i < n ; i++, i++, p += 2 ) { + if (fp) { + if (i == 10 ) + putc(' ', fp); + fprintf (fp, " %02X%02X", *p, p[1] ); + } + else { + if (i == 10 ) + tty_printf (" "); + tty_printf (" %02X%02X", *p, p[1]); + } + } + } + else { + for (i=0; i < n ; i++, p++ ) { + if (fp) { + if (i && !(i%8) ) + putc (' ', fp); + fprintf (fp, " %02X", *p ); + } + else { + if (i && !(i%8) ) + tty_printf (" "); + tty_printf (" %02X", *p ); + } + } + } + } + if (fp) + putc ('\n', fp); + else + tty_printf ("\n"); +} + +/* Print the serial number of an OpenPGP card if available. */ +static void +print_card_serialno (PKT_secret_key *sk) +{ + int i; + + if (!sk) + return; + if (!sk->is_protected || sk->protect.s2k.mode != 1002) + return; /* Not a card. */ + if (opt.with_colons) + return; /* Handled elsewhere. */ + + fputs (_(" Card serial no. ="), stdout); + putchar (' '); + if (sk->protect.ivlen == 16 + && !memcmp (sk->protect.iv, "\xD2\x76\x00\x01\x24\x01", 6) ) + { /* This is an OpenPGP card. Just print the relevant part. */ + for (i=8; i < 14; i++) + { + if (i == 10) + putchar (' '); + printf ("%02X", sk->protect.iv[i]); + } + } + else + { /* Something is wrong: Print all. */ + for (i=0; i < sk->protect.ivlen; i++) + printf ("%02X", sk->protect.iv[i]); + } + putchar ('\n'); +} + + + +void set_attrib_fd(int fd) +{ + static int last_fd=-1; + + if ( fd != -1 && last_fd == fd ) + return; + + if ( attrib_fp && attrib_fp != stdout && attrib_fp != stderr ) + fclose (attrib_fp); + attrib_fp = NULL; + if ( fd == -1 ) + return; + + if( fd == 1 ) + attrib_fp = stdout; + else if( fd == 2 ) + attrib_fp = stderr; + else + attrib_fp = fdopen( fd, "wb" ); + if( !attrib_fp ) { + log_fatal("can't open fd %d for attribute output: %s\n", + fd, strerror(errno)); + } + + last_fd = fd; +} diff --git a/g10/keyring.c b/g10/keyring.c new file mode 100644 index 0000000..9ef5b1b --- /dev/null +++ b/g10/keyring.c @@ -0,0 +1,1624 @@ +/* keyring.c - keyring file handling + * Copyright (C) 2001, 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 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <assert.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> + +#include "util.h" +#include "keyring.h" +#include "packet.h" +#include "keydb.h" +#include "options.h" +#include "main.h" /*for check_key_signature()*/ +#include "i18n.h" + +/* off_item is a funny named for an object used to keep track of known + * keys. The idea was to use the offset to seek to the known keyblock, but + * this is not possible if more than one process is using the keyring. + */ +struct off_item { + struct off_item *next; + u32 kid[2]; + /*off_t off;*/ +}; + +typedef struct off_item **OffsetHashTable; + + +typedef struct keyring_name *KR_NAME; +struct keyring_name { + struct keyring_name *next; + int secret; + DOTLOCK lockhd; + int is_locked; + int did_full_scan; + char fname[1]; +}; +typedef struct keyring_name const * CONST_KR_NAME; + +static KR_NAME kr_names; +static int active_handles; + +static OffsetHashTable kr_offtbl; +static int kr_offtbl_ready; + + +struct keyring_handle { + CONST_KR_NAME resource; + int secret; /* this is for a secret keyring */ + struct { + CONST_KR_NAME kr; + IOBUF iobuf; + int eof; + int error; + } current; + struct { + CONST_KR_NAME kr; + off_t offset; + size_t pk_no; + size_t uid_no; + unsigned int n_packets; /*used for delete and update*/ + } found; + struct { + char *name; + char *pattern; + } word_match; +}; + + + +static int do_copy (int mode, const char *fname, KBNODE root, int secret, + off_t start_offset, unsigned int n_packets ); + + + +static struct off_item * +new_offset_item (void) +{ + struct off_item *k; + + k = xmalloc_clear (sizeof *k); + return k; +} + +#if 0 +static void +release_offset_items (struct off_item *k) +{ + struct off_item *k2; + + for (; k; k = k2) + { + k2 = k->next; + xfree (k); + } +} +#endif + +static OffsetHashTable +new_offset_hash_table (void) +{ + struct off_item **tbl; + + tbl = xmalloc_clear (2048 * sizeof *tbl); + return tbl; +} + +#if 0 +static void +release_offset_hash_table (OffsetHashTable tbl) +{ + int i; + + if (!tbl) + return; + for (i=0; i < 2048; i++) + release_offset_items (tbl[i]); + xfree (tbl); +} +#endif + +static struct off_item * +lookup_offset_hash_table (OffsetHashTable tbl, u32 *kid) +{ + struct off_item *k; + + for (k = tbl[(kid[1] & 0x07ff)]; k; k = k->next) + if (k->kid[0] == kid[0] && k->kid[1] == kid[1]) + return k; + return NULL; +} + +static void +update_offset_hash_table (OffsetHashTable tbl, u32 *kid, off_t off) +{ + struct off_item *k; + + for (k = tbl[(kid[1] & 0x07ff)]; k; k = k->next) + { + if (k->kid[0] == kid[0] && k->kid[1] == kid[1]) + { + /*k->off = off;*/ + return; + } + } + + k = new_offset_item (); + k->kid[0] = kid[0]; + k->kid[1] = kid[1]; + /*k->off = off;*/ + k->next = tbl[(kid[1] & 0x07ff)]; + tbl[(kid[1] & 0x07ff)] = k; +} + +static void +update_offset_hash_table_from_kb (OffsetHashTable tbl, KBNODE node, off_t off) +{ + for (; node; node = node->next) + { + if (node->pkt->pkttype == PKT_PUBLIC_KEY + || node->pkt->pkttype == PKT_PUBLIC_SUBKEY) + { + u32 aki[2]; + keyid_from_pk (node->pkt->pkt.public_key, aki); + update_offset_hash_table (tbl, aki, off); + } + } +} + +/* + * Register a filename for plain keyring files. ptr is set to a + * pointer to be used to create a handles etc, or the already-issued + * pointer if it has already been registered. The function returns 1 + * if a new keyring was registered. +*/ +int +keyring_register_filename (const char *fname, int secret, void **ptr) +{ + KR_NAME kr; + + if (active_handles) + BUG (); /* We don't allow that */ + + for (kr=kr_names; kr; kr = kr->next) + { + if ( !compare_filenames (kr->fname, fname) ) + { + *ptr=kr; + return 0; /* already registered */ + } + } + + if (secret) + register_secured_file (fname); + + kr = xmalloc (sizeof *kr + strlen (fname)); + strcpy (kr->fname, fname); + kr->secret = !!secret; + kr->lockhd = NULL; + kr->is_locked = 0; + kr->did_full_scan = 0; + /* keep a list of all issued pointers */ + kr->next = kr_names; + kr_names = kr; + + /* create the offset table the first time a function here is used */ + if (!kr_offtbl) + kr_offtbl = new_offset_hash_table (); + + *ptr=kr; + + return 1; +} + +int +keyring_is_writable (void *token) +{ + KR_NAME r = token; + + return r? !access (r->fname, W_OK) : 0; +} + + + +/* Create a new handle for the resource associated with TOKEN. SECRET + is just just as a cross-check. + + The returned handle must be released using keyring_release (). */ +KEYRING_HANDLE +keyring_new (void *token, int secret) +{ + KEYRING_HANDLE hd; + KR_NAME resource = token; + + assert (resource && !resource->secret == !secret); + + hd = xmalloc_clear (sizeof *hd); + hd->resource = resource; + hd->secret = !!secret; + active_handles++; + return hd; +} + +void +keyring_release (KEYRING_HANDLE hd) +{ + if (!hd) + return; + assert (active_handles > 0); + active_handles--; + xfree (hd->word_match.name); + xfree (hd->word_match.pattern); + iobuf_close (hd->current.iobuf); + xfree (hd); +} + + +const char * +keyring_get_resource_name (KEYRING_HANDLE hd) +{ + if (!hd || !hd->resource) + return NULL; + return hd->resource->fname; +} + + +/* + * Lock the keyring with the given handle, or unlok if yes is false. + * We ignore the handle and lock all registered files. + */ +int +keyring_lock (KEYRING_HANDLE hd, int yes) +{ + KR_NAME kr; + int rc = 0; + + if (yes) { + /* first make sure the lock handles are created */ + for (kr=kr_names; kr; kr = kr->next) { + if (!keyring_is_writable(kr)) + continue; + if (!kr->lockhd) { + kr->lockhd = create_dotlock( kr->fname ); + if (!kr->lockhd) { + log_info ("can't allocate lock for `%s'\n", kr->fname ); + rc = G10ERR_GENERAL; + } + } + } + if (rc) + return rc; + + /* and now set the locks */ + for (kr=kr_names; kr; kr = kr->next) { + if (!keyring_is_writable(kr)) + continue; + if (kr->is_locked) + ; + else if (make_dotlock (kr->lockhd, -1) ) { + log_info ("can't lock `%s'\n", kr->fname ); + rc = G10ERR_GENERAL; + } + else + kr->is_locked = 1; + } + } + + if (rc || !yes) { + for (kr=kr_names; kr; kr = kr->next) { + if (!keyring_is_writable(kr)) + continue; + if (!kr->is_locked) + ; + else if (release_dotlock (kr->lockhd)) + log_info ("can't unlock `%s'\n", kr->fname ); + else + kr->is_locked = 0; + } + } + + return rc; +} + + + +/* + * Return the last found keyring. Caller must free it. + * The returned keyblock has the kbode flag bit 0 set for the node with + * the public key used to locate the keyblock or flag bit 1 set for + * the user ID node. + */ +int +keyring_get_keyblock (KEYRING_HANDLE hd, KBNODE *ret_kb) +{ + PACKET *pkt; + int rc; + KBNODE keyblock = NULL, node, lastnode; + IOBUF a; + int in_cert = 0; + int pk_no = 0; + int uid_no = 0; + int save_mode; + + if (ret_kb) + *ret_kb = NULL; + + if (!hd->found.kr) + return -1; /* no successful search */ + + a = iobuf_open (hd->found.kr->fname); + if (!a) + { + log_error(_("can't open `%s'\n"), hd->found.kr->fname); + return G10ERR_KEYRING_OPEN; + } + + if (iobuf_seek (a, hd->found.offset) ) { + log_error ("can't seek `%s'\n", hd->found.kr->fname); + iobuf_close(a); + return G10ERR_KEYRING_OPEN; + } + + pkt = xmalloc (sizeof *pkt); + init_packet (pkt); + hd->found.n_packets = 0;; + lastnode = NULL; + save_mode = set_packet_list_mode(0); + while ((rc=parse_packet (a, pkt)) != -1) { + hd->found.n_packets++; + if (rc == G10ERR_UNKNOWN_PACKET) { + free_packet (pkt); + init_packet (pkt); + continue; + } + if (rc) { + log_error ("keyring_get_keyblock: read error: %s\n", + g10_errstr(rc) ); + rc = G10ERR_INV_KEYRING; + break; + } + if (pkt->pkttype == PKT_COMPRESSED) { + log_error ("skipped compressed packet in keyring\n"); + free_packet(pkt); + init_packet(pkt); + continue; + } + + if (in_cert && (pkt->pkttype == PKT_PUBLIC_KEY + || pkt->pkttype == PKT_SECRET_KEY)) { + hd->found.n_packets--; /* fix counter */ + break; /* ready */ + } + + in_cert = 1; + if (pkt->pkttype == PKT_RING_TRUST) { + /*(this code is duplicated after the loop)*/ + if ( lastnode + && lastnode->pkt->pkttype == PKT_SIGNATURE + && (pkt->pkt.ring_trust->sigcache & 1) ) { + /* this is a ring trust packet with a checked signature + * status cache following directly a signature paket. + * Set the cache status into that signature packet */ + PKT_signature *sig = lastnode->pkt->pkt.signature; + + sig->flags.checked = 1; + sig->flags.valid = !!(pkt->pkt.ring_trust->sigcache & 2); + } + /* reset lastnode, so that we set the cache status only from + * the ring trust packet immediately folling a signature */ + lastnode = NULL; + } + else { + node = lastnode = new_kbnode (pkt); + if (!keyblock) + keyblock = node; + else + add_kbnode (keyblock, node); + + if ( pkt->pkttype == PKT_PUBLIC_KEY + || pkt->pkttype == PKT_PUBLIC_SUBKEY + || pkt->pkttype == PKT_SECRET_KEY + || pkt->pkttype == PKT_SECRET_SUBKEY) { + if (++pk_no == hd->found.pk_no) + node->flag |= 1; + } + else if ( pkt->pkttype == PKT_USER_ID) { + if (++uid_no == hd->found.uid_no) + node->flag |= 2; + } + } + + pkt = xmalloc (sizeof *pkt); + init_packet(pkt); + } + set_packet_list_mode(save_mode); + + if (rc == -1 && keyblock) + rc = 0; /* got the entire keyblock */ + + if (rc || !ret_kb) + release_kbnode (keyblock); + else { + /*(duplicated form the loop body)*/ + if ( pkt && pkt->pkttype == PKT_RING_TRUST + && lastnode + && lastnode->pkt->pkttype == PKT_SIGNATURE + && (pkt->pkt.ring_trust->sigcache & 1) ) { + PKT_signature *sig = lastnode->pkt->pkt.signature; + sig->flags.checked = 1; + sig->flags.valid = !!(pkt->pkt.ring_trust->sigcache & 2); + } + *ret_kb = keyblock; + } + free_packet (pkt); + xfree (pkt); + iobuf_close(a); + + /* Make sure that future search operations fail immediately when + * we know that we are working on a invalid keyring + */ + if (rc == G10ERR_INV_KEYRING) + hd->current.error = rc; + + return rc; +} + +int +keyring_update_keyblock (KEYRING_HANDLE hd, KBNODE kb) +{ + int rc; + + if (!hd->found.kr) + return -1; /* no successful prior search */ + + if (!hd->found.n_packets) { + /* need to know the number of packets - do a dummy get_keyblock*/ + rc = keyring_get_keyblock (hd, NULL); + if (rc) { + log_error ("re-reading keyblock failed: %s\n", g10_errstr (rc)); + return rc; + } + if (!hd->found.n_packets) + BUG (); + } + + /* The open iobuf isn't needed anymore and in fact is a problem when + it comes to renaming the keyring files on some operating systems, + so close it here */ + iobuf_close(hd->current.iobuf); + hd->current.iobuf = NULL; + + /* do the update */ + rc = do_copy (3, hd->found.kr->fname, kb, hd->secret, + hd->found.offset, hd->found.n_packets ); + if (!rc) { + if (!hd->secret && kr_offtbl) + { + update_offset_hash_table_from_kb (kr_offtbl, kb, 0); + } + /* better reset the found info */ + hd->found.kr = NULL; + hd->found.offset = 0; + } + return rc; +} + +int +keyring_insert_keyblock (KEYRING_HANDLE hd, KBNODE kb) +{ + int rc; + const char *fname; + + if (!hd) + fname = NULL; + else if (hd->found.kr) + fname = hd->found.kr->fname; + else if (hd->current.kr) + fname = hd->current.kr->fname; + else + fname = hd->resource? hd->resource->fname:NULL; + + if (!fname) + return G10ERR_GENERAL; + + /* close this one otherwise we will lose the position for + * a next search. Fixme: it would be better to adjust the position + * after the write opertions. + */ + iobuf_close (hd->current.iobuf); + hd->current.iobuf = NULL; + + /* do the insert */ + rc = do_copy (1, fname, kb, hd->secret, 0, 0 ); + if (!rc && !hd->secret && kr_offtbl) + { + update_offset_hash_table_from_kb (kr_offtbl, kb, 0); + } + + return rc; +} + + +int +keyring_delete_keyblock (KEYRING_HANDLE hd) +{ + int rc; + + if (!hd->found.kr) + return -1; /* no successful prior search */ + + if (!hd->found.n_packets) { + /* need to know the number of packets - do a dummy get_keyblock*/ + rc = keyring_get_keyblock (hd, NULL); + if (rc) { + log_error ("re-reading keyblock failed: %s\n", g10_errstr (rc)); + return rc; + } + if (!hd->found.n_packets) + BUG (); + } + + /* close this one otherwise we will lose the position for + * a next search. Fixme: it would be better to adjust the position + * after the write opertions. + */ + iobuf_close (hd->current.iobuf); + hd->current.iobuf = NULL; + + /* do the delete */ + rc = do_copy (2, hd->found.kr->fname, NULL, hd->secret, + hd->found.offset, hd->found.n_packets ); + if (!rc) { + /* better reset the found info */ + hd->found.kr = NULL; + hd->found.offset = 0; + /* Delete is a rare operations, so we don't remove the keys + * from the offset table */ + } + return rc; +} + + + +/* + * Start the next search on this handle right at the beginning + */ +int +keyring_search_reset (KEYRING_HANDLE hd) +{ + assert (hd); + + hd->current.kr = NULL; + iobuf_close (hd->current.iobuf); + hd->current.iobuf = NULL; + hd->current.eof = 0; + hd->current.error = 0; + + hd->found.kr = NULL; + hd->found.offset = 0; + return 0; +} + + +static int +prepare_search (KEYRING_HANDLE hd) +{ + if (hd->current.error) + return hd->current.error; /* still in error state */ + + if (hd->current.kr && !hd->current.eof) { + if ( !hd->current.iobuf ) + return G10ERR_GENERAL; /* position invalid after a modify */ + return 0; /* okay */ + } + + if (!hd->current.kr && hd->current.eof) + return -1; /* still EOF */ + + if (!hd->current.kr) { /* start search with first keyring */ + hd->current.kr = hd->resource; + if (!hd->current.kr) { + hd->current.eof = 1; + return -1; /* keyring not available */ + } + assert (!hd->current.iobuf); + } + else { /* EOF */ + iobuf_close (hd->current.iobuf); + hd->current.iobuf = NULL; + hd->current.kr = NULL; + hd->current.eof = 1; + return -1; + } + + hd->current.eof = 0; + hd->current.iobuf = iobuf_open (hd->current.kr->fname); + if (!hd->current.iobuf) + { + log_error(_("can't open `%s'\n"), hd->current.kr->fname ); + return (hd->current.error = G10ERR_OPEN_FILE); + } + + return 0; +} + + +/* A map of the all characters valid used for word_match() + * Valid characters are in in this table converted to uppercase. + * because the upper 128 bytes have special meaning, we assume + * that they are all valid. + * Note: We must use numerical values here in case that this program + * will be converted to those little blue HAL9000s with their strange + * EBCDIC character set (user ids are UTF-8). + * wk 2000-04-13: Hmmm, does this really make sense, given the fact that + * we can run gpg now on a S/390 running GNU/Linux, where the code + * translation is done by the device drivers? + */ +static const byte word_match_chars[256] = { + /* 00 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* 08 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* 10 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* 18 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* 20 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* 28 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* 30 */ 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + /* 38 */ 0x38, 0x39, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* 40 */ 0x00, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + /* 48 */ 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + /* 50 */ 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + /* 58 */ 0x58, 0x59, 0x5a, 0x00, 0x00, 0x00, 0x00, 0x00, + /* 60 */ 0x00, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + /* 68 */ 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + /* 70 */ 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + /* 78 */ 0x58, 0x59, 0x5a, 0x00, 0x00, 0x00, 0x00, 0x00, + /* 80 */ 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + /* 88 */ 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + /* 90 */ 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + /* 98 */ 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, + /* a0 */ 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, + /* a8 */ 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, + /* b0 */ 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, + /* b8 */ 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, + /* c0 */ 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, + /* c8 */ 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, + /* d0 */ 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, + /* d8 */ 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, + /* e0 */ 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, + /* e8 */ 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, + /* f0 */ 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, + /* f8 */ 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff +}; + +/**************** + * Do a word match (original user id starts with a '+'). + * The pattern is already tokenized to a more suitable format: + * There are only the real words in it delimited by one space + * and all converted to uppercase. + * + * Returns: 0 if all words match. + * + * Note: This algorithm is a straightforward one and not very + * fast. It works for UTF-8 strings. The uidlen should + * be removed but due to the fact that old versions of + * pgp don't use UTF-8 we still use the length; this should + * be fixed in parse-packet (and replace \0 by some special + * UTF-8 encoding) + */ +static int +word_match( const byte *uid, size_t uidlen, const byte *pattern ) +{ + size_t wlen, n; + const byte *p; + const byte *s; + + for( s=pattern; *s; ) { + do { + /* skip leading delimiters */ + while( uidlen && !word_match_chars[*uid] ) + uid++, uidlen--; + /* get length of the word */ + n = uidlen; p = uid; + while( n && word_match_chars[*p] ) + p++, n--; + wlen = p - uid; + /* and compare against the current word from pattern */ + for(n=0, p=uid; n < wlen && s[n] != ' ' && s[n] ; n++, p++ ) { + if( word_match_chars[*p] != s[n] ) + break; + } + if( n == wlen && (s[n] == ' ' || !s[n]) ) + break; /* found */ + uid += wlen; + uidlen -= wlen; + } while( uidlen ); + if( !uidlen ) + return -1; /* not found */ + + /* advance to next word in pattern */ + for(; *s != ' ' && *s ; s++ ) + ; + if( *s ) + s++ ; + } + return 0; /* found */ +} + +/**************** + * prepare word word_match; that is parse the name and + * build the pattern. + * caller has to free the returned pattern + */ +static char* +prepare_word_match (const byte *name) +{ + byte *pattern, *p; + int c; + + /* the original length is always enough for the pattern */ + p = pattern = xmalloc(strlen(name)+1); + do { + /* skip leading delimiters */ + while( *name && !word_match_chars[*name] ) + name++; + /* copy as long as we don't have a delimiter and convert + * to uppercase. + * fixme: how can we handle utf8 uppercasing */ + for( ; *name && (c=word_match_chars[*name]); name++ ) + *p++ = c; + *p++ = ' '; /* append pattern delimiter */ + } while( *name ); + p[-1] = 0; /* replace last pattern delimiter by EOS */ + + return pattern; +} + + + + +static int +compare_name (int mode, const char *name, const char *uid, size_t uidlen) +{ + int i; + const char *s, *se; + + if (mode == KEYDB_SEARCH_MODE_EXACT) { + for (i=0; name[i] && uidlen; i++, uidlen--) + if (uid[i] != name[i]) + break; + if (!uidlen && !name[i]) + return 0; /* found */ + } + else if (mode == KEYDB_SEARCH_MODE_SUBSTR) { + if (ascii_memistr( uid, uidlen, name )) + return 0; + } + else if ( mode == KEYDB_SEARCH_MODE_MAIL + || mode == KEYDB_SEARCH_MODE_MAILSUB + || mode == KEYDB_SEARCH_MODE_MAILEND) { + for (i=0, s= uid; i < uidlen && *s != '<'; s++, i++) + ; + if (i < uidlen) { + /* skip opening delim and one char and look for the closing one*/ + s++; i++; + for (se=s+1, i++; i < uidlen && *se != '>'; se++, i++) + ; + if (i < uidlen) { + i = se - s; + if (mode == KEYDB_SEARCH_MODE_MAIL) { + if( strlen(name)-2 == i + && !ascii_memcasecmp( s, name+1, i) ) + return 0; + } + else if (mode == KEYDB_SEARCH_MODE_MAILSUB) { + if( ascii_memistr( s, i, name ) ) + return 0; + } + else { /* email from end */ + /* nyi */ + } + } + } + } + else if (mode == KEYDB_SEARCH_MODE_WORDS) + return word_match (uid, uidlen, name); + else + BUG(); + + return -1; /* not found */ +} + + +/* + * Search through the keyring(s), starting at the current position, + * for a keyblock which contains one of the keys described in the DESC array. + */ +int +keyring_search (KEYRING_HANDLE hd, KEYDB_SEARCH_DESC *desc, + size_t ndesc, size_t *descindex) +{ + int rc; + PACKET pkt; + int save_mode; + off_t offset, main_offset; + size_t n; + int need_uid, need_words, need_keyid, need_fpr, any_skip; + int pk_no, uid_no; + int initial_skip; + int use_offtbl; + PKT_user_id *uid = NULL; + PKT_public_key *pk = NULL; + PKT_secret_key *sk = NULL; + u32 aki[2]; + + /* figure out what information we need */ + need_uid = need_words = need_keyid = need_fpr = any_skip = 0; + for (n=0; n < ndesc; n++) + { + switch (desc[n].mode) + { + case KEYDB_SEARCH_MODE_EXACT: + case KEYDB_SEARCH_MODE_SUBSTR: + case KEYDB_SEARCH_MODE_MAIL: + case KEYDB_SEARCH_MODE_MAILSUB: + case KEYDB_SEARCH_MODE_MAILEND: + need_uid = 1; + break; + case KEYDB_SEARCH_MODE_WORDS: + need_uid = 1; + need_words = 1; + break; + case KEYDB_SEARCH_MODE_SHORT_KID: + case KEYDB_SEARCH_MODE_LONG_KID: + need_keyid = 1; + break; + case KEYDB_SEARCH_MODE_FPR16: + case KEYDB_SEARCH_MODE_FPR20: + case KEYDB_SEARCH_MODE_FPR: + need_fpr = 1; + break; + case KEYDB_SEARCH_MODE_FIRST: + /* always restart the search in this mode */ + keyring_search_reset (hd); + break; + default: break; + } + if (desc[n].skipfnc) + { + any_skip = 1; + need_keyid = 1; + } + } + + rc = prepare_search (hd); + if (rc) + return rc; + + use_offtbl = !hd->secret && kr_offtbl; + if (!use_offtbl) + ; + else if (!kr_offtbl_ready) + need_keyid = 1; + else if (ndesc == 1 && desc[0].mode == KEYDB_SEARCH_MODE_LONG_KID) + { + struct off_item *oi; + + oi = lookup_offset_hash_table (kr_offtbl, desc[0].u.kid); + if (!oi) + { /* We know that we don't have this key */ + hd->found.kr = NULL; + hd->current.eof = 1; + return -1; + } + /* We could now create a positive search status and return. + * However the problem is that another instance of gpg may + * have changed the keyring so that the offsets are not valid + * anymore - therefore we don't do it + */ + } + + if (need_words) + { + const char *name = NULL; + + log_debug ("word search mode does not yet work\n"); + /* FIXME: here is a long standing bug in our function and in addition we + just use the first search description */ + for (n=0; n < ndesc && !name; n++) + { + if (desc[n].mode == KEYDB_SEARCH_MODE_WORDS) + name = desc[n].u.name; + } + assert (name); + if ( !hd->word_match.name || strcmp (hd->word_match.name, name) ) + { + /* name changed */ + xfree (hd->word_match.name); + xfree (hd->word_match.pattern); + hd->word_match.name = xstrdup (name); + hd->word_match.pattern = prepare_word_match (name); + } + name = hd->word_match.pattern; + } + + init_packet(&pkt); + save_mode = set_packet_list_mode(0); + + hd->found.kr = NULL; + main_offset = 0; + pk_no = uid_no = 0; + initial_skip = 1; /* skip until we see the start of a keyblock */ + while (!(rc=search_packet (hd->current.iobuf, &pkt, &offset, need_uid))) + { + byte afp[MAX_FINGERPRINT_LEN]; + size_t an; + + if (pkt.pkttype == PKT_PUBLIC_KEY || pkt.pkttype == PKT_SECRET_KEY) + { + main_offset = offset; + pk_no = uid_no = 0; + initial_skip = 0; + } + if (initial_skip) + { + free_packet (&pkt); + continue; + } + + pk = NULL; + sk = NULL; + uid = NULL; + if ( pkt.pkttype == PKT_PUBLIC_KEY + || pkt.pkttype == PKT_PUBLIC_SUBKEY) + { + pk = pkt.pkt.public_key; + ++pk_no; + + if (need_fpr) { + fingerprint_from_pk (pk, afp, &an); + while (an < 20) /* fill up to 20 bytes */ + afp[an++] = 0; + } + if (need_keyid) + keyid_from_pk (pk, aki); + + if (use_offtbl && !kr_offtbl_ready) + update_offset_hash_table (kr_offtbl, aki, main_offset); + } + else if (pkt.pkttype == PKT_USER_ID) + { + uid = pkt.pkt.user_id; + ++uid_no; + } + else if ( pkt.pkttype == PKT_SECRET_KEY + || pkt.pkttype == PKT_SECRET_SUBKEY) + { + sk = pkt.pkt.secret_key; + ++pk_no; + + if (need_fpr) { + fingerprint_from_sk (sk, afp, &an); + while (an < 20) /* fill up to 20 bytes */ + afp[an++] = 0; + } + if (need_keyid) + keyid_from_sk (sk, aki); + + } + + for (n=0; n < ndesc; n++) + { + switch (desc[n].mode) { + case KEYDB_SEARCH_MODE_NONE: + BUG (); + break; + case KEYDB_SEARCH_MODE_EXACT: + case KEYDB_SEARCH_MODE_SUBSTR: + case KEYDB_SEARCH_MODE_MAIL: + case KEYDB_SEARCH_MODE_MAILSUB: + case KEYDB_SEARCH_MODE_MAILEND: + case KEYDB_SEARCH_MODE_WORDS: + if ( uid && !compare_name (desc[n].mode, + desc[n].u.name, + uid->name, uid->len)) + goto found; + break; + + case KEYDB_SEARCH_MODE_SHORT_KID: + if ((pk||sk) && desc[n].u.kid[1] == aki[1]) + goto found; + break; + case KEYDB_SEARCH_MODE_LONG_KID: + if ((pk||sk) && desc[n].u.kid[0] == aki[0] + && desc[n].u.kid[1] == aki[1]) + goto found; + break; + case KEYDB_SEARCH_MODE_FPR16: + if ((pk||sk) && !memcmp (desc[n].u.fpr, afp, 16)) + goto found; + break; + case KEYDB_SEARCH_MODE_FPR20: + case KEYDB_SEARCH_MODE_FPR: + if ((pk||sk) && !memcmp (desc[n].u.fpr, afp, 20)) + goto found; + break; + case KEYDB_SEARCH_MODE_FIRST: + if (pk||sk) + goto found; + break; + case KEYDB_SEARCH_MODE_NEXT: + if (pk||sk) + goto found; + break; + default: + rc = G10ERR_INV_ARG; + goto found; + } + } + free_packet (&pkt); + continue; + found: + /* Record which desc we matched on. Note this value is only + meaningful if this function returns with no errors. */ + if(descindex) + *descindex=n; + for (n=any_skip?0:ndesc; n < ndesc; n++) + { + if (desc[n].skipfnc + && desc[n].skipfnc (desc[n].skipfncvalue, aki, uid)) + break; + } + if (n == ndesc) + goto real_found; + free_packet (&pkt); + } + real_found: + if (!rc) + { + hd->found.offset = main_offset; + hd->found.kr = hd->current.kr; + hd->found.pk_no = (pk||sk)? pk_no : 0; + hd->found.uid_no = uid? uid_no : 0; + } + else if (rc == -1) + { + hd->current.eof = 1; + /* if we scanned all keyrings, we are sure that + * all known key IDs are in our offtbl, mark that. */ + if (use_offtbl && !kr_offtbl_ready) + { + KR_NAME kr; + + /* First set the did_full_scan flag for this keyring (ignore + secret keyrings) */ + for (kr=kr_names; kr; kr = kr->next) + { + if (!kr->secret && hd->resource == kr) + { + kr->did_full_scan = 1; + break; + } + } + /* Then check whether all flags are set and if so, mark the + offtbl ready */ + for (kr=kr_names; kr; kr = kr->next) + { + if (!kr->secret && !kr->did_full_scan) + break; + } + if (!kr) + kr_offtbl_ready = 1; + } + } + else + hd->current.error = rc; + + free_packet(&pkt); + set_packet_list_mode(save_mode); + return rc; +} + + +static int +create_tmp_file (const char *template, + char **r_bakfname, char **r_tmpfname, IOBUF *r_fp) +{ + char *bakfname, *tmpfname; + mode_t oldmask; + + *r_bakfname = NULL; + *r_tmpfname = NULL; + +# ifdef USE_ONLY_8DOT3 + /* Here is another Windoze bug?: + * you cant rename("pubring.gpg.tmp", "pubring.gpg"); + * but rename("pubring.gpg.tmp", "pubring.aaa"); + * works. So we replace .gpg by .bak or .tmp + */ + if (strlen (template) > 4 + && !strcmp (template+strlen(template)-4, EXTSEP_S "gpg") ) + { + bakfname = xmalloc (strlen (template) + 1); + strcpy (bakfname, template); + strcpy (bakfname+strlen(template)-4, EXTSEP_S "bak"); + + tmpfname = xmalloc (strlen( template ) + 1 ); + strcpy (tmpfname,template); + strcpy (tmpfname+strlen(template)-4, EXTSEP_S "tmp"); + } + else + { /* file does not end with gpg; hmmm */ + bakfname = xmalloc (strlen( template ) + 5); + strcpy (stpcpy(bakfname, template), EXTSEP_S "bak"); + + tmpfname = xmalloc (strlen( template ) + 5); + strcpy (stpcpy(tmpfname, template), EXTSEP_S "tmp"); + } +# else /* Posix file names */ + bakfname = xmalloc (strlen( template ) + 2); + strcpy (stpcpy (bakfname,template),"~"); + + tmpfname = xmalloc (strlen( template ) + 5); + strcpy (stpcpy(tmpfname,template), EXTSEP_S "tmp"); +# endif /* Posix filename */ + + /* Create the temp file with limited access */ + oldmask=umask(077); + if (is_secured_filename (tmpfname)) + { + *r_fp = NULL; + errno = EPERM; + } + else + *r_fp = iobuf_create (tmpfname); + umask(oldmask); + if (!*r_fp) + { + log_error(_("can't create `%s': %s\n"), tmpfname, strerror(errno) ); + xfree (tmpfname); + xfree (bakfname); + return G10ERR_OPEN_FILE; + } + + *r_bakfname = bakfname; + *r_tmpfname = tmpfname; + return 0; +} + + +static int +rename_tmp_file (const char *bakfname, const char *tmpfname, + const char *fname, int secret ) +{ + int rc=0; + + /* invalidate close caches*/ + iobuf_ioctl (NULL, 2, 0, (char*)tmpfname ); + iobuf_ioctl (NULL, 2, 0, (char*)bakfname ); + iobuf_ioctl (NULL, 2, 0, (char*)fname ); + + /* first make a backup file except for secret keyrings */ + if (!secret) + { +#if defined(HAVE_DOSISH_SYSTEM) || defined(__riscos__) + remove (bakfname); +#endif + if (rename (fname, bakfname) ) + { + log_error ("renaming `%s' to `%s' failed: %s\n", + fname, bakfname, strerror(errno) ); + return G10ERR_RENAME_FILE; + } + } + + /* then rename the file */ +#if defined(HAVE_DOSISH_SYSTEM) || defined(__riscos__) + remove( fname ); +#endif + if (secret) + unregister_secured_file (fname); + if (rename (tmpfname, fname) ) + { + log_error (_("renaming `%s' to `%s' failed: %s\n"), + tmpfname, fname, strerror(errno) ); + register_secured_file (fname); + rc = G10ERR_RENAME_FILE; + if (secret) + { + log_info(_("WARNING: 2 files with confidential" + " information exists.\n")); + log_info(_("%s is the unchanged one\n"), fname ); + log_info(_("%s is the new one\n"), tmpfname ); + log_info(_("Please fix this possible security flaw\n")); + } + return rc; + } + + /* Now make sure the file has the same permissions as the original */ + +#ifndef HAVE_DOSISH_SYSTEM + { + struct stat statbuf; + + statbuf.st_mode=S_IRUSR | S_IWUSR; + + if(((secret && !opt.preserve_permissions) || + (stat(bakfname,&statbuf)==0)) && + (chmod(fname,statbuf.st_mode)==0)) + ; + else + log_error("WARNING: unable to restore permissions to `%s': %s", + fname,strerror(errno)); + } +#endif + + return 0; +} + + +static int +write_keyblock (IOBUF fp, KBNODE keyblock) +{ + KBNODE kbctx = NULL, node; + int rc; + + while ( (node = walk_kbnode (keyblock, &kbctx, 0)) ) + { + if (node->pkt->pkttype == PKT_RING_TRUST) + continue; /* we write it later on our own */ + + if ( (rc = build_packet (fp, node->pkt) )) + { + log_error ("build_packet(%d) failed: %s\n", + node->pkt->pkttype, g10_errstr(rc) ); + return rc; + } + if (node->pkt->pkttype == PKT_SIGNATURE) + { /* always write a signature cache packet */ + PKT_signature *sig = node->pkt->pkt.signature; + unsigned int cacheval = 0; + + if (sig->flags.checked) + { + cacheval |= 1; + if (sig->flags.valid) + cacheval |= 2; + } + iobuf_put (fp, 0xb0); /* old style packet 12, 1 byte len*/ + iobuf_put (fp, 2); /* 2 bytes */ + iobuf_put (fp, 0); /* unused */ + if (iobuf_put (fp, cacheval)) { + log_error ("writing sigcache packet failed\n"); + return G10ERR_WRITE_FILE; + } + } + } + return 0; +} + +/* + * Walk over all public keyrings, check the signatures and replace the + * keyring with a new one where the signature cache is then updated. + * This is only done for the public keyrings. + */ +int +keyring_rebuild_cache (void *token,int noisy) +{ + KEYRING_HANDLE hd; + KEYDB_SEARCH_DESC desc; + KBNODE keyblock = NULL, node; + const char *lastresname = NULL, *resname; + IOBUF tmpfp = NULL; + char *tmpfilename = NULL; + char *bakfilename = NULL; + int rc; + ulong count = 0, sigcount = 0; + + hd = keyring_new (token, 0); + memset (&desc, 0, sizeof desc); + desc.mode = KEYDB_SEARCH_MODE_FIRST; + + rc=keyring_lock (hd, 1); + if(rc) + goto leave; + + while ( !(rc = keyring_search (hd, &desc, 1, NULL)) ) + { + desc.mode = KEYDB_SEARCH_MODE_NEXT; + resname = keyring_get_resource_name (hd); + if (lastresname != resname ) + { /* we have switched to a new keyring - commit changes */ + if (tmpfp) + { + if (iobuf_close (tmpfp)) + { + log_error ("error closing `%s': %s\n", + tmpfilename, strerror (errno)); + rc = G10ERR_CLOSE_FILE; + goto leave; + } + /* because we have switched resources, we can be sure that + * the original file is closed */ + tmpfp = NULL; + } + rc = lastresname? rename_tmp_file (bakfilename, tmpfilename, + lastresname, 0) : 0; + xfree (tmpfilename); tmpfilename = NULL; + xfree (bakfilename); bakfilename = NULL; + if (rc) + goto leave; + lastresname = resname; + if (noisy && !opt.quiet) + log_info (_("caching keyring `%s'\n"), resname); + rc = create_tmp_file (resname, &bakfilename, &tmpfilename, &tmpfp); + if (rc) + goto leave; + } + + release_kbnode (keyblock); + rc = keyring_get_keyblock (hd, &keyblock); + if (rc) + { + log_error ("keyring_get_keyblock failed: %s\n", g10_errstr(rc)); + goto leave; + } + assert (keyblock->pkt->pkttype == PKT_PUBLIC_KEY); + + /* check all signature to set the signature's cache flags */ + for (node=keyblock; node; node=node->next) + { + /* Note that this doesn't cache the result of a revocation + issued by a designated revoker. This is because the pk + in question does not carry the revkeys as we haven't + merged the key and selfsigs. It is questionable whether + this matters very much since there are very very few + designated revoker revocation packets out there. */ + + if (node->pkt->pkttype == PKT_SIGNATURE) + { + PKT_signature *sig=node->pkt->pkt.signature; + + if(!opt.no_sig_cache && sig->flags.checked && sig->flags.valid + && (check_digest_algo(sig->digest_algo) + || check_pubkey_algo(sig->pubkey_algo))) + sig->flags.checked=sig->flags.valid=0; + else + check_key_signature (keyblock, node, NULL); + + sigcount++; + } + } + + /* write the keyblock to the temporary file */ + rc = write_keyblock (tmpfp, keyblock); + if (rc) + goto leave; + + if ( !(++count % 50) && noisy && !opt.quiet) + log_info(_("%lu keys cached so far (%lu signatures)\n"), + count, sigcount ); + + } /* end main loop */ + if (rc == -1) + rc = 0; + if (rc) + { + log_error ("keyring_search failed: %s\n", g10_errstr(rc)); + goto leave; + } + if(noisy || opt.verbose) + log_info(_("%lu keys cached (%lu signatures)\n"), count, sigcount ); + if (tmpfp) + { + if (iobuf_close (tmpfp)) + { + log_error ("error closing `%s': %s\n", + tmpfilename, strerror (errno)); + rc = G10ERR_CLOSE_FILE; + goto leave; + } + /* because we have switched resources, we can be sure that + * the original file is closed */ + tmpfp = NULL; + } + rc = lastresname? rename_tmp_file (bakfilename, tmpfilename, + lastresname, 0) : 0; + xfree (tmpfilename); tmpfilename = NULL; + xfree (bakfilename); bakfilename = NULL; + + leave: + if (tmpfp) + iobuf_cancel (tmpfp); + xfree (tmpfilename); + xfree (bakfilename); + release_kbnode (keyblock); + keyring_lock (hd, 0); + keyring_release (hd); + return rc; +} + + +/**************** + * Perform insert/delete/update operation. + * mode 1 = insert + * 2 = delete + * 3 = update + */ +static int +do_copy (int mode, const char *fname, KBNODE root, int secret, + off_t start_offset, unsigned int n_packets ) +{ + IOBUF fp, newfp; + int rc=0; + char *bakfname = NULL; + char *tmpfname = NULL; + + /* Open the source file. Because we do a rename, we have to check the + permissions of the file */ + if (access (fname, W_OK)) + return G10ERR_WRITE_FILE; + + fp = iobuf_open (fname); + if (mode == 1 && !fp && errno == ENOENT) { + /* insert mode but file does not exist: create a new file */ + KBNODE kbctx, node; + mode_t oldmask; + + oldmask=umask(077); + if (!secret && is_secured_filename (fname)) { + newfp = NULL; + errno = EPERM; + } + else + newfp = iobuf_create (fname); + umask(oldmask); + if( !newfp ) + { + log_error (_("can't create `%s': %s\n"), fname, strerror(errno)); + return G10ERR_OPEN_FILE; + } + if( !opt.quiet ) + log_info(_("%s: keyring created\n"), fname ); + + kbctx=NULL; + while ( (node = walk_kbnode( root, &kbctx, 0 )) ) { + if( (rc = build_packet( newfp, node->pkt )) ) { + log_error("build_packet(%d) failed: %s\n", + node->pkt->pkttype, g10_errstr(rc) ); + iobuf_cancel(newfp); + return G10ERR_WRITE_FILE; + } + } + if( iobuf_close(newfp) ) { + log_error ("%s: close failed: %s\n", fname, strerror(errno)); + return G10ERR_CLOSE_FILE; + } + return 0; /* ready */ + } + + if( !fp ) + { + log_error(_("can't open `%s': %s\n"), fname, strerror(errno) ); + rc = G10ERR_OPEN_FILE; + goto leave; + } + + /* Create the new file. */ + rc = create_tmp_file (fname, &bakfname, &tmpfname, &newfp); + if (rc) { + iobuf_close(fp); + goto leave; + } + if (secret) + register_secured_file (tmpfname); + + if( mode == 1 ) { /* insert */ + /* copy everything to the new file */ + rc = copy_all_packets (fp, newfp); + if( rc != -1 ) { + log_error("%s: copy to `%s' failed: %s\n", + fname, tmpfname, g10_errstr(rc) ); + iobuf_close(fp); + if (secret) + unregister_secured_file (tmpfname); + iobuf_cancel(newfp); + goto leave; + } + rc = 0; + } + + if( mode == 2 || mode == 3 ) { /* delete or update */ + /* copy first part to the new file */ + rc = copy_some_packets( fp, newfp, start_offset ); + if( rc ) { /* should never get EOF here */ + log_error ("%s: copy to `%s' failed: %s\n", + fname, tmpfname, g10_errstr(rc) ); + iobuf_close(fp); + if (secret) + unregister_secured_file (tmpfname); + iobuf_cancel(newfp); + goto leave; + } + /* skip this keyblock */ + assert( n_packets ); + rc = skip_some_packets( fp, n_packets ); + if( rc ) { + log_error("%s: skipping %u packets failed: %s\n", + fname, n_packets, g10_errstr(rc)); + iobuf_close(fp); + if (secret) + unregister_secured_file (tmpfname); + iobuf_cancel(newfp); + goto leave; + } + } + + if( mode == 1 || mode == 3 ) { /* insert or update */ + rc = write_keyblock (newfp, root); + if (rc) { + iobuf_close(fp); + if (secret) + unregister_secured_file (tmpfname); + iobuf_cancel(newfp); + goto leave; + } + } + + if( mode == 2 || mode == 3 ) { /* delete or update */ + /* copy the rest */ + rc = copy_all_packets( fp, newfp ); + if( rc != -1 ) { + log_error("%s: copy to `%s' failed: %s\n", + fname, tmpfname, g10_errstr(rc) ); + iobuf_close(fp); + if (secret) + unregister_secured_file (tmpfname); + iobuf_cancel(newfp); + goto leave; + } + rc = 0; + } + + /* close both files */ + if( iobuf_close(fp) ) { + log_error("%s: close failed: %s\n", fname, strerror(errno) ); + rc = G10ERR_CLOSE_FILE; + goto leave; + } + if( iobuf_close(newfp) ) { + log_error("%s: close failed: %s\n", tmpfname, strerror(errno) ); + rc = G10ERR_CLOSE_FILE; + goto leave; + } + + rc = rename_tmp_file (bakfname, tmpfname, fname, secret); + + leave: + xfree(bakfname); + xfree(tmpfname); + return rc; +} diff --git a/g10/keyring.h b/g10/keyring.h new file mode 100644 index 0000000..8c4ea73 --- /dev/null +++ b/g10/keyring.h @@ -0,0 +1,46 @@ +/* keyring.h - Keyring operations + * Copyright (C) 2001 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#ifndef GPG_KEYRING_H +#define GPG_KEYRING_H 1 + +#include "global.h" + +typedef struct keyring_handle *KEYRING_HANDLE; + +int keyring_register_filename (const char *fname, int secret, void **ptr); +int keyring_is_writable (void *token); + +KEYRING_HANDLE keyring_new (void *token, int secret); +void keyring_release (KEYRING_HANDLE hd); +const char *keyring_get_resource_name (KEYRING_HANDLE hd); +int keyring_lock (KEYRING_HANDLE hd, int yes); +int keyring_get_keyblock (KEYRING_HANDLE hd, KBNODE *ret_kb); +int keyring_update_keyblock (KEYRING_HANDLE hd, KBNODE kb); +int keyring_insert_keyblock (KEYRING_HANDLE hd, KBNODE kb); +int keyring_locate_writable (KEYRING_HANDLE hd); +int keyring_delete_keyblock (KEYRING_HANDLE hd); +int keyring_search_reset (KEYRING_HANDLE hd); +int keyring_search (KEYRING_HANDLE hd, KEYDB_SEARCH_DESC *desc, + size_t ndesc, size_t *descindex); +int keyring_rebuild_cache (void *token,int noisy); + +#endif /*GPG_KEYRING_H*/ diff --git a/g10/keyserver-internal.h b/g10/keyserver-internal.h new file mode 100644 index 0000000..a365986 --- /dev/null +++ b/g10/keyserver-internal.h @@ -0,0 +1,54 @@ +/* keyserver-internal.h - Keyserver internals + * Copyright (C) 2001, 2002, 2004, 2005, 2006 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#ifndef _KEYSERVER_INTERNAL_H_ +#define _KEYSERVER_INTERNAL_H_ + +#include <time.h> +#include "keyserver.h" +#include "iobuf.h" +#include "types.h" + +int parse_keyserver_options(char *options); +void free_keyserver_spec(struct keyserver_spec *keyserver); +struct keyserver_spec *keyserver_match(struct keyserver_spec *spec); +struct keyserver_spec *parse_keyserver_uri(const char *string, + int require_scheme, + const char *configname, + unsigned int configlineno); +struct keyserver_spec *parse_preferred_keyserver(PKT_signature *sig); +int keyserver_export(STRLIST users); +int keyserver_import(STRLIST users); +int keyserver_import_fprint(const byte *fprint,size_t fprint_len, + struct keyserver_spec *keyserver); +int keyserver_import_keyid(u32 *keyid,struct keyserver_spec *keyserver); +int keyserver_refresh(STRLIST users); +int keyserver_search(STRLIST tokens); +int keyserver_fetch(STRLIST urilist); +int keyserver_import_cert(const char *name, + unsigned char **fpr,size_t *fpr_len); +int keyserver_import_pka(const char *name,unsigned char **fpr,size_t *fpr_len); +int keyserver_import_name(const char *name,unsigned char **fpr,size_t *fpr_len, + struct keyserver_spec *keyserver); +int keyserver_import_ldap(const char *name, + unsigned char **fpr,size_t *fpr_len); + +#endif /* !_KEYSERVER_INTERNAL_H_ */ diff --git a/g10/keyserver.c b/g10/keyserver.c new file mode 100644 index 0000000..4f69245 --- /dev/null +++ b/g10/keyserver.c @@ -0,0 +1,2132 @@ +/* keyserver.c - generic keyserver code + * Copyright (C) 2001, 2002, 2003, 2004, 2005, + * 2006 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#include <config.h> +#include <ctype.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <assert.h> +#include "filter.h" +#include "keydb.h" +#include "status.h" +#include "exec.h" +#include "main.h" +#include "i18n.h" +#include "iobuf.h" +#include "memory.h" +#include "ttyio.h" +#include "options.h" +#include "packet.h" +#include "trustdb.h" +#include "keyserver-internal.h" +#include "util.h" + +struct keyrec +{ + KEYDB_SEARCH_DESC desc; + u32 createtime,expiretime; + int size,flags; + byte type; + IOBUF uidbuf; + unsigned int lines; +}; + +enum ks_action {KS_UNKNOWN=0,KS_GET,KS_GETNAME,KS_SEND,KS_SEARCH}; + +static struct parse_options keyserver_opts[]= + { + /* some of these options are not real - just for the help + message */ + {"max-cert-size",0,NULL,NULL}, + {"include-revoked",0,NULL,N_("include revoked keys in search results")}, + {"include-subkeys",0,NULL,N_("include subkeys when searching by key ID")}, + {"use-temp-files",0,NULL, + N_("use temporary files to pass data to keyserver helpers")}, + {"keep-temp-files",KEYSERVER_KEEP_TEMP_FILES,NULL, + N_("do not delete temporary files after using them")}, + {"refresh-add-fake-v3-keyids",KEYSERVER_ADD_FAKE_V3,NULL, + NULL}, + {"auto-key-retrieve",KEYSERVER_AUTO_KEY_RETRIEVE,NULL, + N_("automatically retrieve keys when verifying signatures")}, + {"honor-keyserver-url",KEYSERVER_HONOR_KEYSERVER_URL,NULL, + N_("honor the preferred keyserver URL set on the key")}, + {"honor-pka-record",KEYSERVER_HONOR_PKA_RECORD,NULL, + N_("honor the PKA record set on a key when retrieving keys")}, + {NULL,0,NULL,NULL} + }; + +static int keyserver_work(enum ks_action action,STRLIST list, + KEYDB_SEARCH_DESC *desc,int count, + unsigned char **fpr,size_t *fpr_len, + struct keyserver_spec *keyserver); + +/* Reasonable guess */ +#define DEFAULT_MAX_CERT_SIZE 16384 + +static size_t max_cert_size=DEFAULT_MAX_CERT_SIZE; + +static void +add_canonical_option(char *option,STRLIST *list) +{ + char *arg=argsplit(option); + + if(arg) + { + char *joined; + + joined=xmalloc(strlen(option)+1+strlen(arg)+1); + /* Make a canonical name=value form with no spaces */ + strcpy(joined,option); + strcat(joined,"="); + strcat(joined,arg); + append_to_strlist(list,joined); + xfree(joined); + } + else + append_to_strlist(list,option); +} + +int +parse_keyserver_options(char *options) +{ + int ret=1; + char *tok; + char *max_cert=NULL; + + keyserver_opts[0].value=&max_cert; + + while((tok=optsep(&options))) + { + if(tok[0]=='\0') + continue; + + /* For backwards compatibility. 1.2.x used honor-http-proxy and + there are a good number of documents published that recommend + it. */ + if(ascii_strcasecmp(tok,"honor-http-proxy")==0) + tok="http-proxy"; + else if(ascii_strcasecmp(tok,"no-honor-http-proxy")==0) + tok="no-http-proxy"; + + /* We accept quite a few possible options here - some options to + handle specially, the keyserver_options list, and import and + export options that pertain to keyserver operations. Note + that you must use strncasecmp here as there might be an + =argument attached which will foil the use of strcasecmp. */ + +#ifdef EXEC_TEMPFILE_ONLY + if(ascii_strncasecmp(tok,"use-temp-files",14)==0 || + ascii_strncasecmp(tok,"no-use-temp-files",17)==0) + log_info(_("WARNING: keyserver option `%s' is not used" + " on this platform\n"),tok); +#else + if(ascii_strncasecmp(tok,"use-temp-files",14)==0) + opt.keyserver_options.options|=KEYSERVER_USE_TEMP_FILES; + else if(ascii_strncasecmp(tok,"no-use-temp-files",17)==0) + opt.keyserver_options.options&=~KEYSERVER_USE_TEMP_FILES; +#endif + else if(!parse_options(tok,&opt.keyserver_options.options, + keyserver_opts,0) + && !parse_import_options(tok, + &opt.keyserver_options.import_options,0) + && !parse_export_options(tok, + &opt.keyserver_options.export_options,0)) + { + /* All of the standard options have failed, so the option is + destined for a keyserver plugin. */ + add_canonical_option(tok,&opt.keyserver_options.other); + } + } + + if(max_cert) + { + max_cert_size=strtoul(max_cert,(char **)NULL,10); + + if(max_cert_size==0) + max_cert_size=DEFAULT_MAX_CERT_SIZE; + } + + return ret; +} + +void +free_keyserver_spec(struct keyserver_spec *keyserver) +{ + xfree(keyserver->uri); + xfree(keyserver->scheme); + xfree(keyserver->auth); + xfree(keyserver->host); + xfree(keyserver->port); + xfree(keyserver->path); + xfree(keyserver->opaque); + free_strlist(keyserver->options); + xfree(keyserver); +} + +/* Return 0 for match */ +static int +cmp_keyserver_spec(struct keyserver_spec *one,struct keyserver_spec *two) +{ + if(ascii_strcasecmp(one->scheme,two->scheme)==0) + { + if(one->host && two->host && ascii_strcasecmp(one->host,two->host)==0) + { + if((one->port && two->port + && ascii_strcasecmp(one->port,two->port)==0) + || (!one->port && !two->port)) + return 0; + } + else if(one->opaque && two->opaque + && ascii_strcasecmp(one->opaque,two->opaque)==0) + return 0; + } + + return 1; +} + +/* Try and match one of our keyservers. If we can, return that. If + we can't, return our input. */ +struct keyserver_spec * +keyserver_match(struct keyserver_spec *spec) +{ + struct keyserver_spec *ks; + + for(ks=opt.keyserver;ks;ks=ks->next) + if(cmp_keyserver_spec(spec,ks)==0) + return ks; + + return spec; +} + +/* TODO: once we cut over to an all-curl world, we don't need this + parser any longer so it can be removed, or at least moved to + keyserver/ksutil.c for limited use in gpgkeys_ldap or the like. */ + +struct keyserver_spec * +parse_keyserver_uri(const char *string,int require_scheme, + const char *configname,unsigned int configlineno) +{ + int assume_hkp=0; + struct keyserver_spec *keyserver; + const char *idx; + int count; + char *uri,*options; + + assert(string!=NULL); + + keyserver=xmalloc_clear(sizeof(struct keyserver_spec)); + + uri=xstrdup(string); + + options=strchr(uri,' '); + if(options) + { + char *tok; + + *options='\0'; + options++; + + while((tok=optsep(&options))) + add_canonical_option(tok,&keyserver->options); + } + + /* Get the scheme */ + + for(idx=uri,count=0;*idx && *idx!=':';idx++) + { + count++; + + /* Do we see the start of an RFC-2732 ipv6 address here? If so, + there clearly isn't a scheme so get out early. */ + if(*idx=='[') + { + /* Was the '[' the first thing in the string? If not, we + have a mangled scheme with a [ in it so fail. */ + if(count==1) + break; + else + goto fail; + } + } + + if(count==0) + goto fail; + + if(*idx=='\0' || *idx=='[') + { + if(require_scheme) + return NULL; + + /* Assume HKP if there is no scheme */ + assume_hkp=1; + keyserver->scheme=xstrdup("hkp"); + + keyserver->uri=xmalloc(strlen(keyserver->scheme)+3+strlen(uri)+1); + strcpy(keyserver->uri,keyserver->scheme); + strcat(keyserver->uri,"://"); + strcat(keyserver->uri,uri); + } + else + { + int i; + + keyserver->uri=xstrdup(uri); + + keyserver->scheme=xmalloc(count+1); + + /* Force to lowercase */ + for(i=0;i<count;i++) + keyserver->scheme[i]=ascii_tolower(uri[i]); + + keyserver->scheme[i]='\0'; + + /* Skip past the scheme and colon */ + uri+=count+1; + } + + if(ascii_strcasecmp(keyserver->scheme,"x-broken-hkp")==0) + { + deprecated_warning(configname,configlineno,"x-broken-hkp", + "--keyserver-options ","broken-http-proxy"); + xfree(keyserver->scheme); + keyserver->scheme=xstrdup("hkp"); + append_to_strlist(&opt.keyserver_options.other,"broken-http-proxy"); + } + else if(ascii_strcasecmp(keyserver->scheme,"x-hkp")==0) + { + /* Canonicalize this to "hkp" so it works with both the internal + and external keyserver interface. */ + xfree(keyserver->scheme); + keyserver->scheme=xstrdup("hkp"); + } + + if(assume_hkp || (uri[0]=='/' && uri[1]=='/')) + { + /* Two slashes means network path. */ + + /* Skip over the "//", if any */ + if(!assume_hkp) + uri+=2; + + /* Do we have userinfo auth data present? */ + for(idx=uri,count=0;*idx && *idx!='@' && *idx!='/';idx++) + count++; + + /* We found a @ before the slash, so that means everything + before the @ is auth data. */ + if(*idx=='@') + { + if(count==0) + goto fail; + + keyserver->auth=xmalloc(count+1); + strncpy(keyserver->auth,uri,count); + keyserver->auth[count]='\0'; + uri+=count+1; + } + + /* Is it an RFC-2732 ipv6 [literal address] ? */ + if(*uri=='[') + { + for(idx=uri+1,count=1;*idx + && ((isascii (*idx) && isxdigit(*idx)) + || *idx==':' || *idx=='.');idx++) + count++; + + /* Is the ipv6 literal address terminated? */ + if(*idx==']') + count++; + else + goto fail; + } + else + for(idx=uri,count=0;*idx && *idx!=':' && *idx!='/';idx++) + count++; + + if(count==0) + goto fail; + + keyserver->host=xmalloc(count+1); + strncpy(keyserver->host,uri,count); + keyserver->host[count]='\0'; + + /* Skip past the host */ + uri+=count; + + if(*uri==':') + { + /* It would seem to be reasonable to limit the range of the + ports to values between 1-65535, but RFC 1738 and 1808 + imply there is no limit. Of course, the real world has + limits. */ + + for(idx=uri+1,count=0;*idx && *idx!='/';idx++) + { + count++; + + /* Ports are digits only */ + if(!digitp(idx)) + goto fail; + } + + keyserver->port=xmalloc(count+1); + strncpy(keyserver->port,uri+1,count); + keyserver->port[count]='\0'; + + /* Skip past the colon and port number */ + uri+=1+count; + } + + /* Everything else is the path */ + if(*uri) + keyserver->path=xstrdup(uri); + else + keyserver->path=xstrdup("/"); + + if(keyserver->path[1]) + keyserver->flags.direct_uri=1; + } + else if(uri[0]!='/') + { + /* No slash means opaque. Just record the opaque blob and get + out. */ + keyserver->opaque=xstrdup(uri); + } + else + { + /* One slash means absolute path. We don't need to support that + yet. */ + goto fail; + } + + return keyserver; + + fail: + free_keyserver_spec(keyserver); + + return NULL; +} + +struct keyserver_spec * +parse_preferred_keyserver(PKT_signature *sig) +{ + struct keyserver_spec *spec=NULL; + const byte *p; + size_t plen; + + p=parse_sig_subpkt(sig->hashed,SIGSUBPKT_PREF_KS,&plen); + if(p && plen) + { + byte *dupe=xmalloc(plen+1); + + memcpy(dupe,p,plen); + dupe[plen]='\0'; + spec=parse_keyserver_uri(dupe,1,NULL,0); + xfree(dupe); + } + + return spec; +} + +static void +print_keyrec(int number,struct keyrec *keyrec) +{ + int i; + + iobuf_writebyte(keyrec->uidbuf,0); + iobuf_flush_temp(keyrec->uidbuf); + printf("(%d)\t%s ",number,iobuf_get_temp_buffer(keyrec->uidbuf)); + + if(keyrec->size>0) + printf("%d bit ",keyrec->size); + + if(keyrec->type) + { + const char *str=pubkey_algo_to_string(keyrec->type); + + if(str) + printf("%s ",str); + else + printf("unknown "); + } + + switch(keyrec->desc.mode) + { + /* If the keyserver helper gave us a short keyid, we have no + choice but to use it. Do check --keyid-format to add a 0x if + needed. */ + case KEYDB_SEARCH_MODE_SHORT_KID: + printf("key %s%08lX", + (opt.keyid_format==KF_0xSHORT + || opt.keyid_format==KF_0xLONG)?"0x":"", + (ulong)keyrec->desc.u.kid[1]); + break; + + /* However, if it gave us a long keyid, we can honor + --keyid-format */ + case KEYDB_SEARCH_MODE_LONG_KID: + printf("key %s",keystr(keyrec->desc.u.kid)); + break; + + case KEYDB_SEARCH_MODE_FPR16: + printf("key "); + for(i=0;i<16;i++) + printf("%02X",keyrec->desc.u.fpr[i]); + break; + + case KEYDB_SEARCH_MODE_FPR20: + printf("key "); + for(i=0;i<20;i++) + printf("%02X",keyrec->desc.u.fpr[i]); + break; + + default: + BUG(); + break; + } + + if(keyrec->createtime>0) + { + printf(", "); + printf(_("created: %s"),strtimestamp(keyrec->createtime)); + } + + if(keyrec->expiretime>0) + { + printf(", "); + printf(_("expires: %s"),strtimestamp(keyrec->expiretime)); + } + + if(keyrec->flags&1) + printf(" (%s)",_("revoked")); + if(keyrec->flags&2) + printf(" (%s)",_("disabled")); + if(keyrec->flags&4) + printf(" (%s)",_("expired")); + + printf("\n"); +} + +/* Returns a keyrec (which must be freed) once a key is complete, and + NULL otherwise. Call with a NULL keystring once key parsing is + complete to return any unfinished keys. */ +static struct keyrec * +parse_keyrec(char *keystring) +{ + static struct keyrec *work=NULL; + struct keyrec *ret=NULL; + char *record; + int i; + + if(keystring==NULL) + { + if(work==NULL) + return NULL; + else if(work->desc.mode==KEYDB_SEARCH_MODE_NONE) + { + xfree(work); + return NULL; + } + else + { + ret=work; + work=NULL; + return ret; + } + } + + if(work==NULL) + { + work=xmalloc_clear(sizeof(struct keyrec)); + work->uidbuf=iobuf_temp(); + } + + /* Remove trailing whitespace */ + for(i=strlen(keystring);i>0;i--) + if(ascii_isspace(keystring[i-1])) + keystring[i-1]='\0'; + else + break; + + if((record=strsep(&keystring,":"))==NULL) + return ret; + + if(ascii_strcasecmp("pub",record)==0) + { + char *tok; + + if(work->desc.mode) + { + ret=work; + work=xmalloc_clear(sizeof(struct keyrec)); + work->uidbuf=iobuf_temp(); + } + + if((tok=strsep(&keystring,":"))==NULL) + return ret; + + classify_user_id(tok,&work->desc); + if(work->desc.mode!=KEYDB_SEARCH_MODE_SHORT_KID + && work->desc.mode!=KEYDB_SEARCH_MODE_LONG_KID + && work->desc.mode!=KEYDB_SEARCH_MODE_FPR16 + && work->desc.mode!=KEYDB_SEARCH_MODE_FPR20) + { + work->desc.mode=KEYDB_SEARCH_MODE_NONE; + return ret; + } + + /* Note all items after this are optional. This allows us to + have a pub line as simple as pub:keyid and nothing else. */ + + work->lines++; + + if((tok=strsep(&keystring,":"))==NULL) + return ret; + + work->type=atoi(tok); + + if((tok=strsep(&keystring,":"))==NULL) + return ret; + + work->size=atoi(tok); + + if((tok=strsep(&keystring,":"))==NULL) + return ret; + + if(atoi(tok)<=0) + work->createtime=0; + else + work->createtime=atoi(tok); + + if((tok=strsep(&keystring,":"))==NULL) + return ret; + + if(atoi(tok)<=0) + work->expiretime=0; + else + { + work->expiretime=atoi(tok); + /* Force the 'e' flag on if this key is expired. */ + if(work->expiretime<=make_timestamp()) + work->flags|=4; + } + + if((tok=strsep(&keystring,":"))==NULL) + return ret; + + while(*tok) + switch(*tok++) + { + case 'r': + case 'R': + work->flags|=1; + break; + + case 'd': + case 'D': + work->flags|=2; + break; + + case 'e': + case 'E': + work->flags|=4; + break; + } + } + else if(ascii_strcasecmp("uid",record)==0 && work->desc.mode) + { + char *userid,*tok,*decoded; + + if((tok=strsep(&keystring,":"))==NULL) + return ret; + + if(strlen(tok)==0) + return ret; + + userid=tok; + + /* By definition, de-%-encoding is always smaller than the + original string so we can decode in place. */ + + i=0; + + while(*tok) + if(tok[0]=='%' && tok[1] && tok[2]) + { + if((userid[i]=hextobyte(&tok[1]))==-1) + userid[i]='?'; + + i++; + tok+=3; + } + else + userid[i++]=*tok++; + + /* We don't care about the other info provided in the uid: line + since no keyserver supports marking userids with timestamps + or revoked/expired/disabled yet. */ + + /* No need to check for control characters, as utf8_to_native + does this for us. */ + + decoded=utf8_to_native(userid,i,0); + if(strlen(decoded)>opt.screen_columns-10) + decoded[opt.screen_columns-10]='\0'; + iobuf_writestr(work->uidbuf,decoded); + xfree(decoded); + iobuf_writestr(work->uidbuf,"\n\t"); + work->lines++; + } + + /* Ignore any records other than "pri" and "uid" for easy future + growth. */ + + return ret; +} + +/* TODO: do this as a list sent to keyserver_work rather than calling + it once for each key to get the correct counts after the import + (cosmetics, really) and to better take advantage of the keyservers + that can do multiple fetches in one go (LDAP). */ +static int +show_prompt(KEYDB_SEARCH_DESC *desc,int numdesc,int count,const char *search) +{ + char *answer; + + if(count && opt.command_fd==-1) + { + static int from=1; + tty_printf("Keys %d-%d of %d for \"%s\". ",from,numdesc,count,search); + from=numdesc+1; + } + + answer=cpr_get_no_help("keysearch.prompt", + _("Enter number(s), N)ext, or Q)uit > ")); + /* control-d */ + if(answer[0]=='\x04') + { + printf("Q\n"); + answer[0]='q'; + } + + if(answer[0]=='q' || answer[0]=='Q') + { + xfree(answer); + return 1; + } + else if(atoi(answer)>=1 && atoi(answer)<=numdesc) + { + char *split=answer,*num; + + while((num=strsep(&split," ,"))!=NULL) + if(atoi(num)>=1 && atoi(num)<=numdesc) + keyserver_work(KS_GET,NULL,&desc[atoi(num)-1],1, + NULL,NULL,opt.keyserver); + + xfree(answer); + return 1; + } + + return 0; +} + +/* Count and searchstr are just for cosmetics. If the count is too + small, it will grow safely. If negative it disables the "Key x-y + of z" messages. searchstr should be UTF-8 (rather than native). */ +static void +keyserver_search_prompt(IOBUF buffer,const char *searchstr) +{ + int i=0,validcount=0,started=0,header=0,count=1; + unsigned int maxlen,buflen,numlines=0; + KEYDB_SEARCH_DESC *desc; + byte *line=NULL; + char *localstr=NULL; + + if(searchstr) + localstr=utf8_to_native(searchstr,strlen(searchstr),0); + + desc=xmalloc(count*sizeof(KEYDB_SEARCH_DESC)); + + for(;;) + { + struct keyrec *keyrec; + int rl; + + maxlen=1024; + rl=iobuf_read_line(buffer,&line,&buflen,&maxlen); + + if(opt.with_colons) + { + if(!header && ascii_strncasecmp("SEARCH ",line,7)==0 + && ascii_strncasecmp(" BEGIN",&line[strlen(line)-7],6)==0) + { + header=1; + continue; + } + else if(ascii_strncasecmp("SEARCH ",line,7)==0 + && ascii_strncasecmp(" END",&line[strlen(line)-5],4)==0) + continue; + + printf("%s",line); + } + + /* Look for an info: line. The only current info: values + defined are the version and key count. */ + if(!started && rl>0 && ascii_strncasecmp("info:",line,5)==0) + { + char *tok,*str=&line[5]; + + if((tok=strsep(&str,":"))!=NULL) + { + int version; + + if(sscanf(tok,"%d",&version)!=1) + version=1; + + if(version!=1) + { + log_error(_("invalid keyserver protocol " + "(us %d!=handler %d)\n"),1,version); + break; + } + } + + if((tok=strsep(&str,":"))!=NULL && sscanf(tok,"%d",&count)==1) + { + if(count==0) + goto notfound; + else if(count<0) + count=10; + else + validcount=1; + + desc=xrealloc(desc,count*sizeof(KEYDB_SEARCH_DESC)); + } + + started=1; + continue; + } + + if(rl==0) + { + keyrec=parse_keyrec(NULL); + + if(keyrec==NULL) + { + if(i==0) + { + count=0; + break; + } + + if(i!=count) + validcount=0; + + for(;;) + { + if(show_prompt(desc,i,validcount?count:0,localstr)) + break; + validcount=0; + } + + break; + } + } + else + keyrec=parse_keyrec(line); + + if(i==count) + { + /* keyserver helper sent more keys than they claimed in the + info: line. */ + count+=10; + desc=xrealloc(desc,count*sizeof(KEYDB_SEARCH_DESC)); + validcount=0; + } + + if(keyrec) + { + desc[i]=keyrec->desc; + + if(!opt.with_colons) + { + /* screen_lines - 1 for the prompt. */ + if(numlines+keyrec->lines>opt.screen_lines-1) + { + if(show_prompt(desc,i,validcount?count:0,localstr)) + break; + else + numlines=0; + } + + print_keyrec(i+1,keyrec); + } + + numlines+=keyrec->lines; + iobuf_close(keyrec->uidbuf); + xfree(keyrec); + + started=1; + i++; + } + } + + notfound: + /* Leave this commented out or now, and perhaps for a very long + time. All HKPish servers return HTML error messages for + no-key-found. */ + /* + if(!started) + log_info(_("keyserver does not support searching\n")); + else + */ + if(count==0) + { + if(localstr) + log_info(_("key \"%s\" not found on keyserver\n"),localstr); + else + log_info(_("key not found on keyserver\n")); + } + + xfree(localstr); + xfree(desc); + xfree(line); +} + +/* We sometimes want to use a different gpgkeys_xxx for a given + protocol (for example, ldaps is handled by gpgkeys_ldap). Map + these here. */ +static const char * +keyserver_typemap(const char *type) +{ + if(strcmp(type,"ldaps")==0) + return "ldap"; + else + return type; +} + +/* The PGP LDAP and the curl fetch-a-LDAP-object methodologies are + sufficiently different that we can't use curl to do LDAP. */ +static int +direct_uri_map(const char *scheme,unsigned int is_direct) +{ + if(is_direct && strcmp(scheme,"ldap")==0) + return 1; + + return 0; +} + +#define GPGKEYS_PREFIX "gpgkeys_" +#define GPGKEYS_CURL GPGKEYS_PREFIX "curl" EXEEXT +#define GPGKEYS_PREFIX_LEN (strlen(GPGKEYS_CURL)) +#define KEYSERVER_ARGS_KEEP " -o \"%O\" \"%I\"" +#define KEYSERVER_ARGS_NOKEEP " -o \"%o\" \"%i\"" + +static int +keyserver_spawn(enum ks_action action,STRLIST list,KEYDB_SEARCH_DESC *desc, + int count,int *prog,unsigned char **fpr,size_t *fpr_len, + struct keyserver_spec *keyserver) +{ + int ret=0,i,gotversion=0,outofband=0; + STRLIST temp; + unsigned int maxlen,buflen; + char *command,*end,*searchstr=NULL; + byte *line=NULL; + struct exec_info *spawn; + const char *scheme; + const char *libexecdir = get_libexecdir (); + + assert(keyserver); + +#ifdef EXEC_TEMPFILE_ONLY + opt.keyserver_options.options|=KEYSERVER_USE_TEMP_FILES; +#endif + + /* Build the filename for the helper to execute */ + scheme=keyserver_typemap(keyserver->scheme); + +#ifdef DISABLE_KEYSERVER_PATH + /* Destroy any path we might have. This is a little tricky, + portability-wise. It's not correct to delete the PATH + environment variable, as that may fall back to a system built-in + PATH. Similarly, it is not correct to set PATH to the null + string (PATH="") since this actually deletes the PATH environment + variable under MinGW. The safest thing to do here is to force + PATH to be GNUPG_LIBEXECDIR. All this is not that meaningful on + Unix-like systems (since we're going to give a full path to + gpgkeys_foo), but on W32 it prevents loading any DLLs from + directories in %PATH%. + + After some more thinking about this we came to the conclusion + that it is better to load the helpers from the directory where + the program of this process lives. Fortunately Windows provides + a way to retrieve this and our get_libexecdir function has been + modified to return just this. Setting the exec-path is not + anymore required. + set_exec_path(libexecdir); + */ +#else + if(opt.exec_path_set) + { + /* If exec-path was set, and DISABLE_KEYSERVER_PATH is + undefined, then don't specify a full path to gpgkeys_foo, so + that the PATH can work. */ + command=xmalloc(GPGKEYS_PREFIX_LEN+strlen(scheme)+3+strlen(EXEEXT)+1); + command[0]='\0'; + } + else +#endif + { + /* Specify a full path to gpgkeys_foo. */ + command=xmalloc(strlen(libexecdir)+strlen(DIRSEP_S)+ + GPGKEYS_PREFIX_LEN+strlen(scheme)+3+strlen(EXEEXT)+1); + strcpy(command,libexecdir); + strcat(command,DIRSEP_S); + } + + end=command+strlen(command); + + /* Build a path for the keyserver helper. If it is direct_uri + (i.e. an object fetch and not a keyserver), then add "_uri" to + the end to distinguish the keyserver helper from an object + fetcher that can speak that protocol (this is a problem for + LDAP). */ + + strcat(command,GPGKEYS_PREFIX); + strcat(command,scheme); + + /* This "_uri" thing is in case we need to call a direct handler + instead of the keyserver handler. This lets us use gpgkeys_curl + or gpgkeys_ldap_uri (we don't provide it, but a user might) + instead of gpgkeys_ldap to fetch things like + ldap://keyserver.pgp.com/o=PGP%20keys?pgpkey?sub?pgpkeyid=99242560 */ + + if(direct_uri_map(scheme,keyserver->flags.direct_uri)) + strcat(command,"_uri"); + + strcat(command,EXEEXT); + + /* Can we execute it? If not, try curl as our catchall. */ + if(path_access(command,X_OK)!=0) + strcpy(end,GPGKEYS_CURL); + + if(opt.keyserver_options.options&KEYSERVER_USE_TEMP_FILES) + { + if(opt.keyserver_options.options&KEYSERVER_KEEP_TEMP_FILES) + { + command=xrealloc(command,strlen(command)+ + strlen(KEYSERVER_ARGS_KEEP)+1); + strcat(command,KEYSERVER_ARGS_KEEP); + } + else + { + command=xrealloc(command,strlen(command)+ + strlen(KEYSERVER_ARGS_NOKEEP)+1); + strcat(command,KEYSERVER_ARGS_NOKEEP); + } + + ret=exec_write(&spawn,NULL,command,NULL,0,0); + } + else + ret=exec_write(&spawn,command,NULL,NULL,0,0); + + xfree(command); + + if(ret) + return ret; + + fprintf(spawn->tochild, + "# This is a GnuPG %s keyserver communications file\n",VERSION); + fprintf(spawn->tochild,"VERSION %d\n",KEYSERVER_PROTO_VERSION); + fprintf(spawn->tochild,"PROGRAM %s\n",VERSION); + fprintf(spawn->tochild,"SCHEME %s\n",keyserver->scheme); + + if(keyserver->opaque) + fprintf(spawn->tochild,"OPAQUE %s\n",keyserver->opaque); + else + { + if(keyserver->auth) + fprintf(spawn->tochild,"AUTH %s\n",keyserver->auth); + + if(keyserver->host) + fprintf(spawn->tochild,"HOST %s\n",keyserver->host); + + if(keyserver->port) + fprintf(spawn->tochild,"PORT %s\n",keyserver->port); + + if(keyserver->path) + fprintf(spawn->tochild,"PATH %s\n",keyserver->path); + } + + /* Write global options */ + + for(temp=opt.keyserver_options.other;temp;temp=temp->next) + fprintf(spawn->tochild,"OPTION %s\n",temp->d); + + /* Write per-keyserver options */ + + for(temp=keyserver->options;temp;temp=temp->next) + fprintf(spawn->tochild,"OPTION %s\n",temp->d); + + switch(action) + { + case KS_GET: + { + fprintf(spawn->tochild,"COMMAND GET\n\n"); + + /* Which keys do we want? */ + + for(i=0;i<count;i++) + { + int quiet=0; + + if(desc[i].mode==KEYDB_SEARCH_MODE_FPR20) + { + int f; + + fprintf(spawn->tochild,"0x"); + + for(f=0;f<MAX_FINGERPRINT_LEN;f++) + fprintf(spawn->tochild,"%02X",desc[i].u.fpr[f]); + + fprintf(spawn->tochild,"\n"); + } + else if(desc[i].mode==KEYDB_SEARCH_MODE_FPR16) + { + int f; + + fprintf(spawn->tochild,"0x"); + + for(f=0;f<16;f++) + fprintf(spawn->tochild,"%02X",desc[i].u.fpr[f]); + + fprintf(spawn->tochild,"\n"); + } + else if(desc[i].mode==KEYDB_SEARCH_MODE_LONG_KID) + fprintf(spawn->tochild,"0x%08lX%08lX\n", + (ulong)desc[i].u.kid[0], + (ulong)desc[i].u.kid[1]); + else if(desc[i].mode==KEYDB_SEARCH_MODE_SHORT_KID) + fprintf(spawn->tochild,"0x%08lX\n", + (ulong)desc[i].u.kid[1]); + else if(desc[i].mode==KEYDB_SEARCH_MODE_EXACT) + { + fprintf(spawn->tochild,"0x0000000000000000\n"); + quiet=1; + } + else if(desc[i].mode==KEYDB_SEARCH_MODE_NONE) + continue; + else + BUG(); + + if(!quiet) + { + if(keyserver->host) + log_info(_("requesting key %s from %s server %s\n"), + keystr_from_desc(&desc[i]), + keyserver->scheme,keyserver->host); + else + log_info(_("requesting key %s from %s\n"), + keystr_from_desc(&desc[i]),keyserver->uri); + } + } + + fprintf(spawn->tochild,"\n"); + + break; + } + + case KS_GETNAME: + { + STRLIST key; + + fprintf(spawn->tochild,"COMMAND GETNAME\n\n"); + + /* Which names do we want? */ + + for(key=list;key!=NULL;key=key->next) + fprintf(spawn->tochild,"%s\n",key->d); + + fprintf(spawn->tochild,"\n"); + + if(keyserver->host) + log_info(_("searching for names from %s server %s\n"), + keyserver->scheme,keyserver->host); + else + log_info(_("searching for names from %s\n"),keyserver->uri); + + break; + } + + case KS_SEND: + { + STRLIST key; + + /* Note the extra \n here to send an empty keylist block */ + fprintf(spawn->tochild,"COMMAND SEND\n\n\n"); + + for(key=list;key!=NULL;key=key->next) + { + armor_filter_context_t *afx; + IOBUF buffer=iobuf_temp(); + KBNODE block; + + temp=NULL; + add_to_strlist(&temp,key->d); + + afx = new_armor_context (); + afx->what = 1; + /* Tell the armor filter to use Unix-style \n line + endings, since we're going to fprintf this to a file + that (on Win32) is open in text mode. The win32 stdio + will transform the \n to \r\n and we'll end up with the + proper line endings on win32. This is a no-op on + Unix. */ + afx->eol[0]='\n'; + push_armor_filter (afx, buffer); + release_armor_context (afx); + + /* TODO: Remove Comment: lines from keys exported this + way? */ + + if(export_pubkeys_stream(buffer,temp,&block, + opt.keyserver_options.export_options)==-1) + iobuf_close(buffer); + else + { + KBNODE node; + + iobuf_flush_temp(buffer); + + merge_keys_and_selfsig(block); + + fprintf(spawn->tochild,"INFO %08lX%08lX BEGIN\n", + (ulong)block->pkt->pkt.public_key->keyid[0], + (ulong)block->pkt->pkt.public_key->keyid[1]); + + for(node=block;node;node=node->next) + { + switch(node->pkt->pkttype) + { + default: + continue; + + case PKT_PUBLIC_KEY: + case PKT_PUBLIC_SUBKEY: + { + PKT_public_key *pk=node->pkt->pkt.public_key; + + keyid_from_pk(pk,NULL); + + fprintf(spawn->tochild,"%sb:%08lX%08lX:%u:%u:%u:%u:", + node->pkt->pkttype==PKT_PUBLIC_KEY?"pu":"su", + (ulong)pk->keyid[0],(ulong)pk->keyid[1], + pk->pubkey_algo, + nbits_from_pk(pk), + pk->timestamp, + pk->expiredate); + + if(pk->is_revoked) + fprintf(spawn->tochild,"r"); + if(pk->has_expired) + fprintf(spawn->tochild,"e"); + + fprintf(spawn->tochild,"\n"); + } + break; + + case PKT_USER_ID: + { + PKT_user_id *uid=node->pkt->pkt.user_id; + int r; + + if(uid->attrib_data) + continue; + + fprintf(spawn->tochild,"uid:"); + + /* Quote ':', '%', and any 8-bit + characters */ + for(r=0;r<uid->len;r++) + { + if(uid->name[r]==':' || uid->name[r]=='%' + || uid->name[r]&0x80) + fprintf(spawn->tochild,"%%%02X", + (byte)uid->name[r]); + else + fprintf(spawn->tochild,"%c",uid->name[r]); + } + + fprintf(spawn->tochild,":%u:%u:", + uid->created,uid->expiredate); + + if(uid->is_revoked) + fprintf(spawn->tochild,"r"); + if(uid->is_expired) + fprintf(spawn->tochild,"e"); + + fprintf(spawn->tochild,"\n"); + } + break; + + /* This bit is really for the benefit of + people who store their keys in LDAP + servers. It makes it easy to do queries + for things like "all keys signed by + Isabella". */ + case PKT_SIGNATURE: + { + PKT_signature *sig=node->pkt->pkt.signature; + + if(!IS_UID_SIG(sig)) + continue; + + fprintf(spawn->tochild,"sig:%08lX%08lX:%X:%u:%u\n", + (ulong)sig->keyid[0],(ulong)sig->keyid[1], + sig->sig_class,sig->timestamp, + sig->expiredate); + } + break; + } + } + + fprintf(spawn->tochild,"INFO %08lX%08lX END\n", + (ulong)block->pkt->pkt.public_key->keyid[0], + (ulong)block->pkt->pkt.public_key->keyid[1]); + + fprintf(spawn->tochild,"KEY %08lX%08lX BEGIN\n", + (ulong)block->pkt->pkt.public_key->keyid[0], + (ulong)block->pkt->pkt.public_key->keyid[1]); + fwrite(iobuf_get_temp_buffer(buffer), + iobuf_get_temp_length(buffer),1,spawn->tochild); + fprintf(spawn->tochild,"KEY %08lX%08lX END\n", + (ulong)block->pkt->pkt.public_key->keyid[0], + (ulong)block->pkt->pkt.public_key->keyid[1]); + + iobuf_close(buffer); + + if(keyserver->host) + log_info(_("sending key %s to %s server %s\n"), + keystr(block->pkt->pkt.public_key->keyid), + keyserver->scheme,keyserver->host); + else + log_info(_("sending key %s to %s\n"), + keystr(block->pkt->pkt.public_key->keyid), + keyserver->uri); + + release_kbnode(block); + } + + free_strlist(temp); + } + + break; + } + + case KS_SEARCH: + { + STRLIST key; + + fprintf(spawn->tochild,"COMMAND SEARCH\n\n"); + + /* Which keys do we want? Remember that the gpgkeys_ program + is going to lump these together into a search string. */ + + for(key=list;key!=NULL;key=key->next) + { + fprintf(spawn->tochild,"%s\n",key->d); + if(key!=list) + { + searchstr=xrealloc(searchstr, + strlen(searchstr)+strlen(key->d)+2); + strcat(searchstr," "); + } + else + { + searchstr=xmalloc(strlen(key->d)+1); + searchstr[0]='\0'; + } + + strcat(searchstr,key->d); + } + + fprintf(spawn->tochild,"\n"); + + if(keyserver->host) + log_info(_("searching for \"%s\" from %s server %s\n"), + searchstr,keyserver->scheme,keyserver->host); + else + log_info(_("searching for \"%s\" from %s\n"), + searchstr,keyserver->uri); + + break; + } + + default: + log_fatal(_("no keyserver action!\n")); + break; + } + + /* Done sending, so start reading. */ + ret=exec_read(spawn); + if(ret) + goto fail; + + /* Now handle the response */ + + for(;;) + { + int plen; + char *ptr; + + maxlen=1024; + if(iobuf_read_line(spawn->fromchild,&line,&buflen,&maxlen)==0) + { + ret=G10ERR_READ_FILE; + goto fail; /* i.e. EOF */ + } + + ptr=line; + + /* remove trailing whitespace */ + plen=strlen(ptr); + while(plen>0 && ascii_isspace(ptr[plen-1])) + plen--; + plen[ptr]='\0'; + + if(*ptr=='\0') + break; + + if(ascii_strncasecmp(ptr,"VERSION ",8)==0) + { + gotversion=1; + + if(atoi(&ptr[8])!=KEYSERVER_PROTO_VERSION) + { + log_error(_("invalid keyserver protocol (us %d!=handler %d)\n"), + KEYSERVER_PROTO_VERSION,atoi(&ptr[8])); + goto fail; + } + } + else if(ascii_strncasecmp(ptr,"PROGRAM ",8)==0) + { + if(ascii_strncasecmp(&ptr[8],VERSION,strlen(VERSION))!=0) + log_info(_("WARNING: keyserver handler from a different" + " version of GnuPG (%s)\n"),&ptr[8]); + } + else if(ascii_strncasecmp(ptr,"OPTION OUTOFBAND",16)==0) + outofband=1; /* Currently the only OPTION */ + } + + if(!gotversion) + { + log_error(_("keyserver did not send VERSION\n")); + goto fail; + } + + if(!outofband) + switch(action) + { + case KS_GET: + case KS_GETNAME: + { + void *stats_handle; + + stats_handle=import_new_stats_handle(); + + /* Slurp up all the key data. In the future, it might be + nice to look for KEY foo OUTOFBAND and FAILED indicators. + It's harmless to ignore them, but ignoring them does make + gpg complain about "no valid OpenPGP data found". One + way to do this could be to continue parsing this + line-by-line and make a temp iobuf for each key. */ + + import_keys_stream(spawn->fromchild,stats_handle,fpr,fpr_len, + opt.keyserver_options.import_options); + + import_print_stats(stats_handle); + import_release_stats_handle(stats_handle); + + break; + } + + /* Nothing to do here */ + case KS_SEND: + break; + + case KS_SEARCH: + keyserver_search_prompt(spawn->fromchild,searchstr); + break; + + default: + log_fatal(_("no keyserver action!\n")); + break; + } + + fail: + xfree(line); + xfree(searchstr); + + + *prog=exec_finish(spawn); + + return ret; +} + +static int +keyserver_work(enum ks_action action,STRLIST list,KEYDB_SEARCH_DESC *desc, + int count,unsigned char **fpr,size_t *fpr_len, + struct keyserver_spec *keyserver) +{ + int rc=0,ret=0; + + if(!keyserver) + { + log_error(_("no keyserver known (use option --keyserver)\n")); + return G10ERR_BAD_URI; + } + +#ifdef DISABLE_KEYSERVER_HELPERS + + log_error(_("external keyserver calls are not supported in this build\n")); + return G10ERR_KEYSERVER; + +#else + /* Spawn a handler */ + + rc=keyserver_spawn(action,list,desc,count,&ret,fpr,fpr_len,keyserver); + if(ret) + { + switch(ret) + { + case KEYSERVER_SCHEME_NOT_FOUND: + log_error(_("no handler for keyserver scheme `%s'\n"), + keyserver->scheme); + break; + + case KEYSERVER_NOT_SUPPORTED: + log_error(_("action `%s' not supported with keyserver " + "scheme `%s'\n"), + action==KS_GET?"get":action==KS_SEND?"send": + action==KS_SEARCH?"search":"unknown", + keyserver->scheme); + break; + + case KEYSERVER_VERSION_ERROR: + log_error(_(GPGKEYS_PREFIX "%s does not support" + " handler version %d\n"), + keyserver_typemap(keyserver->scheme), + KEYSERVER_PROTO_VERSION); + break; + + case KEYSERVER_TIMEOUT: + log_error(_("keyserver timed out\n")); + break; + + case KEYSERVER_INTERNAL_ERROR: + default: + log_error(_("keyserver internal error\n")); + break; + } + + return G10ERR_KEYSERVER; + } + + if(rc) + { + log_error(_("keyserver communications error: %s\n"),g10_errstr(rc)); + + return rc; + } + + return 0; +#endif /* ! DISABLE_KEYSERVER_HELPERS*/ +} + +int +keyserver_export(STRLIST users) +{ + STRLIST sl=NULL; + KEYDB_SEARCH_DESC desc; + int rc=0; + + /* Weed out descriptors that we don't support sending */ + for(;users;users=users->next) + { + classify_user_id (users->d, &desc); + if(desc.mode!=KEYDB_SEARCH_MODE_SHORT_KID && + desc.mode!=KEYDB_SEARCH_MODE_LONG_KID && + desc.mode!=KEYDB_SEARCH_MODE_FPR16 && + desc.mode!=KEYDB_SEARCH_MODE_FPR20) + { + log_error(_("\"%s\" not a key ID: skipping\n"),users->d); + continue; + } + else + append_to_strlist(&sl,users->d); + } + + if(sl) + { + rc=keyserver_work(KS_SEND,sl,NULL,0,NULL,NULL,opt.keyserver); + free_strlist(sl); + } + + return rc; +} + +int +keyserver_import(STRLIST users) +{ + KEYDB_SEARCH_DESC *desc; + int num=100,count=0; + int rc=0; + + /* Build a list of key ids */ + desc=xmalloc(sizeof(KEYDB_SEARCH_DESC)*num); + + for(;users;users=users->next) + { + classify_user_id (users->d, &desc[count]); + if(desc[count].mode!=KEYDB_SEARCH_MODE_SHORT_KID && + desc[count].mode!=KEYDB_SEARCH_MODE_LONG_KID && + desc[count].mode!=KEYDB_SEARCH_MODE_FPR16 && + desc[count].mode!=KEYDB_SEARCH_MODE_FPR20) + { + log_error(_("\"%s\" not a key ID: skipping\n"),users->d); + continue; + } + + count++; + if(count==num) + { + num+=100; + desc=xrealloc(desc,sizeof(KEYDB_SEARCH_DESC)*num); + } + } + + if(count>0) + rc=keyserver_work(KS_GET,NULL,desc,count,NULL,NULL,opt.keyserver); + + xfree(desc); + + return rc; +} + +int +keyserver_import_fprint(const byte *fprint,size_t fprint_len, + struct keyserver_spec *keyserver) +{ + KEYDB_SEARCH_DESC desc; + + memset(&desc,0,sizeof(desc)); + + if(fprint_len==16) + desc.mode=KEYDB_SEARCH_MODE_FPR16; + else if(fprint_len==20) + desc.mode=KEYDB_SEARCH_MODE_FPR20; + else + return -1; + + memcpy(desc.u.fpr,fprint,fprint_len); + + /* TODO: Warn here if the fingerprint we got doesn't match the one + we asked for? */ + return keyserver_work(KS_GET,NULL,&desc,1,NULL,NULL,keyserver); +} + +int +keyserver_import_keyid(u32 *keyid,struct keyserver_spec *keyserver) +{ + KEYDB_SEARCH_DESC desc; + + memset(&desc,0,sizeof(desc)); + + desc.mode=KEYDB_SEARCH_MODE_LONG_KID; + desc.u.kid[0]=keyid[0]; + desc.u.kid[1]=keyid[1]; + + return keyserver_work(KS_GET,NULL,&desc,1,NULL,NULL,keyserver); +} + +/* code mostly stolen from do_export_stream */ +static int +keyidlist(STRLIST users,KEYDB_SEARCH_DESC **klist,int *count,int fakev3) +{ + int rc=0,ndesc,num=100; + KBNODE keyblock=NULL,node; + KEYDB_HANDLE kdbhd; + KEYDB_SEARCH_DESC *desc; + STRLIST sl; + + *count=0; + + *klist=xmalloc(sizeof(KEYDB_SEARCH_DESC)*num); + + kdbhd=keydb_new(0); + + if(!users) + { + ndesc = 1; + desc = xmalloc_clear ( ndesc * sizeof *desc); + desc[0].mode = KEYDB_SEARCH_MODE_FIRST; + } + else + { + for (ndesc=0, sl=users; sl; sl = sl->next, ndesc++) + ; + desc = xmalloc ( ndesc * sizeof *desc); + + for (ndesc=0, sl=users; sl; sl = sl->next) + { + if(classify_user_id (sl->d, desc+ndesc)) + ndesc++; + else + log_error (_("key \"%s\" not found: %s\n"), + sl->d, g10_errstr (G10ERR_INV_USER_ID)); + } + } + + while (!(rc = keydb_search (kdbhd, desc, ndesc))) + { + if (!users) + desc[0].mode = KEYDB_SEARCH_MODE_NEXT; + + /* read the keyblock */ + rc = keydb_get_keyblock (kdbhd, &keyblock ); + if( rc ) + { + log_error (_("error reading keyblock: %s\n"), g10_errstr(rc) ); + goto leave; + } + + if((node=find_kbnode(keyblock,PKT_PUBLIC_KEY))) + { + /* This is to work around a bug in some keyservers (pksd and + OKS) that calculate v4 RSA keyids as if they were v3 RSA. + The answer is to refresh both the correct v4 keyid + (e.g. 99242560) and the fake v3 keyid (e.g. 68FDDBC7). + This only happens for key refresh using the HKP scheme + and if the refresh-add-fake-v3-keyids keyserver option is + set. */ + if(fakev3 && is_RSA(node->pkt->pkt.public_key->pubkey_algo) && + node->pkt->pkt.public_key->version>=4) + { + (*klist)[*count].mode=KEYDB_SEARCH_MODE_LONG_KID; + mpi_get_keyid(node->pkt->pkt.public_key->pkey[0], + (*klist)[*count].u.kid); + (*count)++; + + if(*count==num) + { + num+=100; + *klist=xrealloc(*klist,sizeof(KEYDB_SEARCH_DESC)*num); + } + } + + /* v4 keys get full fingerprints. v3 keys get long keyids. + This is because it's easy to calculate any sort of keyid + from a v4 fingerprint, but not a v3 fingerprint. */ + + if(node->pkt->pkt.public_key->version<4) + { + (*klist)[*count].mode=KEYDB_SEARCH_MODE_LONG_KID; + keyid_from_pk(node->pkt->pkt.public_key, + (*klist)[*count].u.kid); + } + else + { + size_t dummy; + + (*klist)[*count].mode=KEYDB_SEARCH_MODE_FPR20; + fingerprint_from_pk(node->pkt->pkt.public_key, + (*klist)[*count].u.fpr,&dummy); + } + + /* This is a little hackish, using the skipfncvalue as a + void* pointer to the keyserver spec, but we don't need + the skipfnc here, and it saves having an additional field + for this (which would be wasted space most of the + time). */ + + (*klist)[*count].skipfncvalue=NULL; + + /* Are we honoring preferred keyservers? */ + if(opt.keyserver_options.options&KEYSERVER_HONOR_KEYSERVER_URL) + { + PKT_user_id *uid=NULL; + PKT_signature *sig=NULL; + + merge_keys_and_selfsig(keyblock); + + for(node=node->next;node;node=node->next) + { + if(node->pkt->pkttype==PKT_USER_ID + && node->pkt->pkt.user_id->is_primary) + uid=node->pkt->pkt.user_id; + else if(node->pkt->pkttype==PKT_SIGNATURE + && node->pkt->pkt.signature-> + flags.chosen_selfsig && uid) + { + sig=node->pkt->pkt.signature; + break; + } + } + + /* Try and parse the keyserver URL. If it doesn't work, + then we end up writing NULL which indicates we are + the same as any other key. */ + if(sig) + (*klist)[*count].skipfncvalue=parse_preferred_keyserver(sig); + } + + (*count)++; + + if(*count==num) + { + num+=100; + *klist=xrealloc(*klist,sizeof(KEYDB_SEARCH_DESC)*num); + } + } + } + + if(rc==-1) + rc=0; + + leave: + if(rc) + xfree(*klist); + xfree(desc); + keydb_release(kdbhd); + release_kbnode(keyblock); + + return rc; +} + +/* Note this is different than the original HKP refresh. It allows + usernames to refresh only part of the keyring. */ + +int +keyserver_refresh(STRLIST users) +{ + int rc,count,numdesc,fakev3=0; + KEYDB_SEARCH_DESC *desc; + unsigned int options=opt.keyserver_options.import_options; + + /* We switch merge-only on during a refresh, as 'refresh' should + never import new keys, even if their keyids match. */ + opt.keyserver_options.import_options|=IMPORT_MERGE_ONLY; + + /* Similarly, we switch on fast-import, since refresh may make + multiple import sets (due to preferred keyserver URLs). We don't + want each set to rebuild the trustdb. Instead we do it once at + the end here. */ + opt.keyserver_options.import_options|=IMPORT_FAST; + + /* If refresh_add_fake_v3_keyids is on and it's a HKP or MAILTO + scheme, then enable fake v3 keyid generation. */ + if((opt.keyserver_options.options&KEYSERVER_ADD_FAKE_V3) && opt.keyserver + && (ascii_strcasecmp(opt.keyserver->scheme,"hkp")==0 || + ascii_strcasecmp(opt.keyserver->scheme,"mailto")==0)) + fakev3=1; + + rc=keyidlist(users,&desc,&numdesc,fakev3); + if(rc) + return rc; + + count=numdesc; + if(count>0) + { + int i; + + /* Try to handle preferred keyserver keys first */ + for(i=0;i<numdesc;i++) + { + if(desc[i].skipfncvalue) + { + struct keyserver_spec *keyserver=desc[i].skipfncvalue; + + /* We use the keyserver structure we parsed out before. + Note that a preferred keyserver without a scheme:// + will be interpreted as hkp:// */ + + rc=keyserver_work(KS_GET,NULL,&desc[i],1,NULL,NULL,keyserver); + if(rc) + log_info(_("WARNING: unable to refresh key %s" + " via %s: %s\n"),keystr_from_desc(&desc[i]), + keyserver->uri,g10_errstr(rc)); + else + { + /* We got it, so mark it as NONE so we don't try and + get it again from the regular keyserver. */ + + desc[i].mode=KEYDB_SEARCH_MODE_NONE; + count--; + } + + free_keyserver_spec(keyserver); + } + } + } + + if(count>0) + { + if(opt.keyserver) + { + if(count==1) + log_info(_("refreshing 1 key from %s\n"),opt.keyserver->uri); + else + log_info(_("refreshing %d keys from %s\n"), + count,opt.keyserver->uri); + } + + rc=keyserver_work(KS_GET,NULL,desc,numdesc,NULL,NULL,opt.keyserver); + } + + xfree(desc); + + opt.keyserver_options.import_options=options; + + /* If the original options didn't have fast import, and the trustdb + is dirty, rebuild. */ + if(!(opt.keyserver_options.import_options&IMPORT_FAST)) + trustdb_check_or_update(); + + return rc; +} + +int +keyserver_search(STRLIST tokens) +{ + if(tokens) + return keyserver_work(KS_SEARCH,tokens,NULL,0,NULL,NULL,opt.keyserver); + else + return 0; +} + +int +keyserver_fetch(STRLIST urilist) +{ + KEYDB_SEARCH_DESC desc; + STRLIST sl; + unsigned int options=opt.keyserver_options.import_options; + + /* Switch on fast-import, since fetch can handle more than one + import and we don't want each set to rebuild the trustdb. + Instead we do it once at the end. */ + opt.keyserver_options.import_options|=IMPORT_FAST; + + /* A dummy desc since we're not actually fetching a particular key + ID */ + memset(&desc,0,sizeof(desc)); + desc.mode=KEYDB_SEARCH_MODE_EXACT; + + for(sl=urilist;sl;sl=sl->next) + { + struct keyserver_spec *spec; + + spec=parse_keyserver_uri(sl->d,1,NULL,0); + if(spec) + { + int rc; + + rc=keyserver_work(KS_GET,NULL,&desc,1,NULL,NULL,spec); + if(rc) + log_info (_("WARNING: unable to fetch URI %s: %s\n"), + sl->d,g10_errstr(rc)); + + free_keyserver_spec(spec); + } + else + log_info (_("WARNING: unable to parse URI %s\n"),sl->d); + } + + opt.keyserver_options.import_options=options; + + /* If the original options didn't have fast import, and the trustdb + is dirty, rebuild. */ + if(!(opt.keyserver_options.import_options&IMPORT_FAST)) + trustdb_check_or_update(); + + return 0; +} + +/* Import key in a CERT or pointed to by a CERT */ +int +keyserver_import_cert(const char *name,unsigned char **fpr,size_t *fpr_len) +{ + char *domain,*look,*url; + IOBUF key; + int type,rc=G10ERR_GENERAL; + + look=xstrdup(name); + + domain=strrchr(look,'@'); + if(domain) + *domain='.'; + + type=get_cert(look,max_cert_size,&key,fpr,fpr_len,&url); + if(type==1) + { + int armor_status=opt.no_armor; + + /* CERTs are always in binary format */ + opt.no_armor=1; + + rc=import_keys_stream(key,NULL,fpr,fpr_len, + opt.keyserver_options.import_options); + + opt.no_armor=armor_status; + + iobuf_close(key); + } + else if(type==2 && *fpr) + { + /* We only consider the IPGP type if a fingerprint was provided. + This lets us select the right key regardless of what a URL + points to, or get the key from a keyserver. */ + if(url) + { + struct keyserver_spec *spec; + + spec=parse_keyserver_uri(url,1,NULL,0); + if(spec) + { + STRLIST list=NULL; + + add_to_strlist(&list,url); + + rc=keyserver_fetch(list); + + free_strlist(list); + free_keyserver_spec(spec); + } + } + else if(opt.keyserver) + { + /* If only a fingerprint is provided, try and fetch it from + our --keyserver */ + + rc=keyserver_import_fprint(*fpr,*fpr_len,opt.keyserver); + } + else + log_info(_("no keyserver known (use option --keyserver)\n")); + + /* Give a better string here? "CERT fingerprint for \"%s\" + found, but no keyserver" " known (use option + --keyserver)\n" ? */ + + xfree(url); + } + + xfree(look); + + return rc; +} + +/* Import key pointed to by a PKA record. Return the requested + fingerprint in fpr. */ +int +keyserver_import_pka(const char *name,unsigned char **fpr,size_t *fpr_len) +{ + char *uri; + int rc=-1; + + *fpr=xmalloc(20); + *fpr_len=20; + + uri = get_pka_info (name, *fpr); + if (uri) + { + struct keyserver_spec *spec; + spec = parse_keyserver_uri (uri, 1, NULL, 0); + if (spec) + { + rc=keyserver_import_fprint (*fpr, 20, spec); + free_keyserver_spec (spec); + } + xfree (uri); + } + + if(rc!=0) + xfree(*fpr); + + return rc; +} + +/* Import all keys that match name */ +int +keyserver_import_name(const char *name,unsigned char **fpr,size_t *fpr_len, + struct keyserver_spec *keyserver) +{ + STRLIST list=NULL; + int rc; + + append_to_strlist(&list,name); + + rc=keyserver_work(KS_GETNAME,list,NULL,0,fpr,fpr_len,keyserver); + + free_strlist(list); + + return rc; +} + +/* Use the PGP Universal trick of asking ldap://keys.(maildomain) for + the key. */ +int +keyserver_import_ldap(const char *name,unsigned char **fpr,size_t *fpr_len) +{ + char *domain; + struct keyserver_spec *keyserver; + STRLIST list=NULL; + int rc; + + append_to_strlist(&list,name); + + /* Parse out the domain */ + domain=strrchr(name,'@'); + if(!domain) + return G10ERR_GENERAL; + + domain++; + + keyserver=xmalloc_clear(sizeof(struct keyserver_spec)); + + keyserver->scheme=xstrdup("ldap"); + keyserver->host=xmalloc(5+strlen(domain)+1); + strcpy(keyserver->host,"keys."); + strcat(keyserver->host,domain); + keyserver->uri=xmalloc(strlen(keyserver->scheme)+ + 3+strlen(keyserver->host)+1); + strcpy(keyserver->uri,keyserver->scheme); + strcat(keyserver->uri,"://"); + strcat(keyserver->uri,keyserver->host); + + rc=keyserver_work(KS_GETNAME,list,NULL,0,fpr,fpr_len,keyserver); + + free_strlist(list); + + free_keyserver_spec(keyserver); + + return rc; +} diff --git a/g10/main.h b/g10/main.h new file mode 100644 index 0000000..7abb2b3 --- /dev/null +++ b/g10/main.h @@ -0,0 +1,300 @@ +/* main.h + * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, + * 2006 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ +#ifndef G10_MAIN_H +#define G10_MAIN_H +#include "types.h" +#include "iobuf.h" +#include "mpi.h" +#include "cipher.h" +#include "keydb.h" + +/* It could be argued that the default cipher should be 3DES rather + than CAST5, and the default compression should be 0 + (i.e. uncompressed) rather than 1 (zip). However, the real world + issues of speed and size come into play here. */ + +#define DEFAULT_CIPHER_ALGO CIPHER_ALGO_CAST5 +#define DEFAULT_DIGEST_ALGO DIGEST_ALGO_SHA1 +#define DEFAULT_COMPRESS_ALGO COMPRESS_ALGO_ZIP +#define DEFAULT_S2K_DIGEST_ALGO DIGEST_ALGO_SHA1 + +#define S2K_DIGEST_ALGO (opt.s2k_digest_algo?opt.s2k_digest_algo:DEFAULT_S2K_DIGEST_ALGO) + +typedef struct +{ + int header_okay; + PK_LIST pk_list; + DEK *symkey_dek; + STRING2KEY *symkey_s2k; + cipher_filter_context_t cfx; +} encrypt_filter_context_t; + +struct groupitem +{ + char *name; + STRLIST values; + struct groupitem *next; +}; + +/*-- gpg.c --*/ +extern int g10_errors_seen; + +#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 5 ) + void g10_exit(int rc) __attribute__ ((noreturn)); +#else + void g10_exit(int rc); +#endif +void print_pubkey_algo_note( int algo ); +void print_cipher_algo_note( int algo ); +void print_digest_algo_note( int algo ); + +/*-- armor.c --*/ +char *make_radix64_string( const byte *data, size_t len ); + +/*-- misc.c --*/ +void trap_unaligned(void); +int disable_core_dumps(void); +void register_secured_file (const char *fname); +void unregister_secured_file (const char *fname); +int is_secured_file (int fd); +int is_secured_filename (const char *fname); +u16 checksum_u16( unsigned n ); +u16 checksum( byte *p, unsigned n ); +u16 checksum_mpi( MPI a ); +u32 buffer_to_u32( const byte *buffer ); +const byte *get_session_marker( size_t *rlen ); +int openpgp_cipher_test_algo( int algo ); +int openpgp_pk_test_algo( int algo, unsigned int usage_flags ); +int openpgp_pk_algo_usage ( int algo ); +int openpgp_md_test_algo( int algo ); + +#ifdef USE_IDEA +void idea_cipher_warn( int show ); +#else +#define idea_cipher_warn(a) +#endif + +struct expando_args +{ + PKT_public_key *pk; + PKT_secret_key *sk; + byte imagetype; +}; + +char *pct_expando(const char *string,struct expando_args *args); +void deprecated_warning(const char *configname,unsigned int configlineno, + const char *option,const char *repl1,const char *repl2); +void deprecated_command (const char *name); + +const char *compress_algo_to_string(int algo); +int string_to_compress_algo(const char *string); +int check_compress_algo(int algo); +int default_cipher_algo(void); +int default_compress_algo(void); +const char *compliance_option_string(void); +void compliance_failure(void); + +struct parse_options +{ + char *name; + unsigned int bit; + char **value; + char *help; +}; + +char *optsep(char **stringp); +char *argsplit(char *string); +int parse_options(char *str,unsigned int *options, + struct parse_options *opts,int noisy); +char *unescape_percent_string (const unsigned char *s); +int has_invalid_email_chars (const char *s); +int is_valid_mailbox (const char *name); +char *default_homedir (void); +const char *get_libexecdir (void); +int path_access(const char *file,int mode); + +/*-- helptext.c --*/ +void display_online_help( const char *keyword ); + +/*-- encode.c --*/ +int setup_symkey(STRING2KEY **symkey_s2k,DEK **symkey_dek); +int encode_symmetric( const char *filename ); +int encode_store( const char *filename ); +int encode_crypt( const char *filename, STRLIST remusr, int use_symkey ); +void encode_crypt_files(int nfiles, char **files, STRLIST remusr); +int encrypt_filter( void *opaque, int control, + IOBUF a, byte *buf, size_t *ret_len); + + +/*-- sign.c --*/ +int complete_sig( PKT_signature *sig, PKT_secret_key *sk, MD_HANDLE md ); +int sign_file( STRLIST filenames, int detached, STRLIST locusr, + int do_encrypt, STRLIST remusr, const char *outfile ); +int clearsign_file( const char *fname, STRLIST locusr, const char *outfile ); +int sign_symencrypt_file (const char *fname, STRLIST locusr); + +/*-- sig-check.c --*/ +int check_revocation_keys (PKT_public_key *pk, PKT_signature *sig); +int check_backsig(PKT_public_key *main_pk,PKT_public_key *sub_pk, + PKT_signature *backsig); +int check_key_signature( KBNODE root, KBNODE node, int *is_selfsig ); +int check_key_signature2( KBNODE root, KBNODE node, PKT_public_key *check_pk, + PKT_public_key *ret_pk, int *is_selfsig, + u32 *r_expiredate, int *r_expired ); + +/*-- delkey.c --*/ +int delete_keys( STRLIST names, int secret, int allow_both ); + +/*-- keyedit.c --*/ +void keyedit_menu( const char *username, STRLIST locusr, + STRLIST commands, int quiet, int seckey_check ); +void show_basic_key_info (KBNODE keyblock); + +/*-- keygen.c --*/ +u32 parse_expire_string(const char *string); +u32 ask_expire_interval(int object,const char *def_expire); +u32 ask_expiredate(void); +void generate_keypair( const char *fname, const char *card_serialno, + const char *backup_encryption_dir ); +int keygen_set_std_prefs (const char *string,int personal); +PKT_user_id *keygen_get_std_prefs (void); +int keygen_add_key_expire( PKT_signature *sig, void *opaque ); +int keygen_add_std_prefs( PKT_signature *sig, void *opaque ); +int keygen_upd_std_prefs( PKT_signature *sig, void *opaque ); +int keygen_add_keyserver_url(PKT_signature *sig, void *opaque); +int keygen_add_notations(PKT_signature *sig,void *opaque); +int keygen_add_revkey(PKT_signature *sig, void *opaque); +int make_backsig(PKT_signature *sig,PKT_public_key *pk, + PKT_public_key *sub_pk,PKT_secret_key *sub_sk); +int generate_subkeypair( KBNODE pub_keyblock, KBNODE sec_keyblock ); +#ifdef ENABLE_CARD_SUPPORT +int generate_card_subkeypair (KBNODE pub_keyblock, KBNODE sec_keyblock, + int keyno, const char *serialno); +int save_unprotected_key_to_card (PKT_secret_key *sk, int keyno); +#endif + +/*-- openfile.c --*/ +int overwrite_filep( const char *fname ); +char *make_outfile_name( const char *iname ); +char *ask_outfile_name( const char *name, size_t namelen ); +int open_outfile( const char *iname, int mode, IOBUF *a ); +IOBUF open_sigfile( const char *iname, progress_filter_context_t *pfx ); +void try_make_homedir( const char *fname ); + +/*-- seskey.c --*/ +void make_session_key( DEK *dek ); +MPI encode_session_key( DEK *dek, unsigned nbits ); +MPI encode_md_value( PKT_public_key *pk, PKT_secret_key *sk, + MD_HANDLE md, int hash_algo ); + +/*-- import.c --*/ +int parse_import_options(char *str,unsigned int *options,int noisy); +void import_keys( char **fnames, int nnames, + void *stats_hd, unsigned int options ); +int import_keys_stream( IOBUF inp,void *stats_hd,unsigned char **fpr, + size_t *fpr_len,unsigned int options ); +void *import_new_stats_handle (void); +void import_release_stats_handle (void *p); +void import_print_stats (void *hd); + +int collapse_uids( KBNODE *keyblock ); + +int auto_create_card_key_stub ( const char *serialnostr, + const unsigned char *fpr1, + const unsigned char *fpr2, + const unsigned char *fpr3); + +/*-- export.c --*/ +int parse_export_options(char *str,unsigned int *options,int noisy); +int export_pubkeys( STRLIST users, unsigned int options ); +int export_pubkeys_stream( IOBUF out, STRLIST users, + KBNODE *keyblock_out, unsigned int options ); +int export_seckeys( STRLIST users ); +int export_secsubkeys( STRLIST users ); + +/* dearmor.c --*/ +int dearmor_file( const char *fname ); +int enarmor_file( const char *fname ); + +/*-- revoke.c --*/ +struct revocation_reason_info; +int gen_revoke( const char *uname ); +int gen_desig_revoke( const char *uname, STRLIST locusr); +int revocation_reason_build_cb( PKT_signature *sig, void *opaque ); +struct revocation_reason_info * + ask_revocation_reason( int key_rev, int cert_rev, int hint ); +void release_revocation_reason_info( struct revocation_reason_info *reason ); + +/*-- keylist.c --*/ +void public_key_list( STRLIST list ); +void secret_key_list( STRLIST list ); +void print_subpackets_colon(PKT_signature *sig); +void reorder_keyblock (KBNODE keyblock); +void list_keyblock( KBNODE keyblock, int secret, int fpr, void *opaque ); +void print_fingerprint (PKT_public_key *pk, PKT_secret_key *sk, int mode); +void print_revokers(PKT_public_key *pk); +void show_policy_url(PKT_signature *sig,int indent,int mode); +void show_keyserver_url(PKT_signature *sig,int indent,int mode); +void show_notation(PKT_signature *sig,int indent,int mode,int which); +void dump_attribs(const PKT_user_id *uid, + PKT_public_key *pk,PKT_secret_key *sk); +void set_attrib_fd(int fd); +void print_seckey_info (PKT_secret_key *sk); +void print_pubkey_info (FILE *fp, PKT_public_key *pk); +void print_card_key_info (FILE *fp, KBNODE keyblock); + +/*-- verify.c --*/ +void print_file_status( int status, const char *name, int what ); +int verify_signatures( int nfiles, char **files ); +int verify_files( int nfiles, char **files ); + +/*-- decrypt.c --*/ +int decrypt_message( const char *filename ); +void decrypt_messages(int nfiles, char *files[]); + +/*-- plaintext.c --*/ +int hash_datafiles( MD_HANDLE md, MD_HANDLE md2, + STRLIST files, const char *sigfilename, int textmode ); +PKT_plaintext *setup_plaintext_name(const char *filename,IOBUF iobuf); + +/*-- pipemode.c --*/ +void run_in_pipemode (void); + +/*-- signal.c --*/ +void init_signals(void); +void pause_on_sigusr( int which ); +void block_all_signals(void); +void unblock_all_signals(void); + + +#ifdef ENABLE_CARD_SUPPORT +/*-- card-util.c --*/ +void change_pin (int no, int allow_admin); +void card_status (FILE *fp, char *serialno, size_t serialnobuflen); +void card_edit (STRLIST commands); +int card_generate_subkey (KBNODE pub_keyblock, KBNODE sec_keyblock); +int card_store_subkey (KBNODE node, int use); +#endif + +#define S2K_DECODE_COUNT(_val) ((16ul + ((_val) & 15)) << (((_val) >> 4) + 6)) + +#endif /*G10_MAIN_H*/ diff --git a/g10/mainproc.c b/g10/mainproc.c new file mode 100644 index 0000000..cb4432a --- /dev/null +++ b/g10/mainproc.c @@ -0,0 +1,2083 @@ +/* mainproc.c - handle packets + * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, + * 2005, 2006 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <time.h> + +#include "packet.h" +#include "iobuf.h" +#include "memory.h" +#include "options.h" +#include "util.h" +#include "cipher.h" +#include "keydb.h" +#include "filter.h" +#include "main.h" +#include "status.h" +#include "i18n.h" +#include "trustdb.h" +#include "keyserver-internal.h" +#include "photoid.h" + + +struct kidlist_item { + struct kidlist_item *next; + u32 kid[2]; + int pubkey_algo; + int reason; +}; + + +/**************** + * Structure to hold the context + */ +typedef struct mainproc_context *CTX; +struct mainproc_context +{ + struct mainproc_context *anchor; /* May be useful in the future. */ + PKT_public_key *last_pubkey; + PKT_secret_key *last_seckey; + PKT_user_id *last_user_id; + md_filter_context_t mfx; + int sigs_only; /* Process only signatures and reject all other stuff. */ + int encrypt_only; /* Process only encryption messages. */ + STRLIST signed_data; + const char *sigfilename; + DEK *dek; + int last_was_session_key; + KBNODE list; /* The current list of packets. */ + int have_data; + IOBUF iobuf; /* Used to get the filename etc. */ + int trustletter; /* Temporary usage in list_node. */ + ulong symkeys; + struct kidlist_item *pkenc_list; /* List of encryption packets. */ + struct + { + int op; + int stop_now; + } pipemode; + int any_sig_seen; /* Set to true if a signature packet has been seen. */ +}; + + +static int do_proc_packets( CTX c, IOBUF a ); + +static void list_node( CTX c, KBNODE node ); +static void proc_tree( CTX c, KBNODE node ); + + +static void +release_list( CTX c ) +{ + if( !c->list ) + return; + proc_tree(c, c->list ); + release_kbnode( c->list ); + while( c->pkenc_list ) { + struct kidlist_item *tmp = c->pkenc_list->next; + xfree( c->pkenc_list ); + c->pkenc_list = tmp; + } + c->pkenc_list = NULL; + c->list = NULL; + c->have_data = 0; + c->last_was_session_key = 0; + c->pipemode.op = 0; + c->pipemode.stop_now = 0; + xfree(c->dek); c->dek = NULL; +} + + +static int +add_onepass_sig( CTX c, PACKET *pkt ) +{ + KBNODE node; + + if ( c->list ) /* add another packet */ + add_kbnode( c->list, new_kbnode( pkt )); + else /* insert the first one */ + c->list = node = new_kbnode( pkt ); + + return 1; +} + + +static int +add_gpg_control( CTX c, PACKET *pkt ) +{ + if ( pkt->pkt.gpg_control->control == CTRLPKT_CLEARSIGN_START ) { + /* New clear text signature. + * Process the last one and reset everything */ + release_list(c); + } + else if ( pkt->pkt.gpg_control->control == CTRLPKT_PIPEMODE ) { + /* Pipemode control packet */ + if ( pkt->pkt.gpg_control->datalen < 2 ) + log_fatal ("invalid pipemode control packet length\n"); + if (pkt->pkt.gpg_control->data[0] == 1) { + /* start the whole thing */ + assert ( !c->list ); /* we should be in a pretty virgin state */ + assert ( !c->pipemode.op ); + c->pipemode.op = pkt->pkt.gpg_control->data[1]; + } + else if (pkt->pkt.gpg_control->data[0] == 2) { + /* the signed material follows in a plaintext packet */ + assert ( c->pipemode.op == 'B' ); + } + else if (pkt->pkt.gpg_control->data[0] == 3) { + assert ( c->pipemode.op == 'B' ); + release_list (c); + /* and tell the outer loop to terminate */ + c->pipemode.stop_now = 1; + } + else + log_fatal ("invalid pipemode control packet code\n"); + return 0; /* no need to store the packet */ + } + + if( c->list ) /* add another packet */ + add_kbnode( c->list, new_kbnode( pkt )); + else /* insert the first one */ + c->list = new_kbnode( pkt ); + + return 1; +} + + + +static int +add_user_id( CTX c, PACKET *pkt ) +{ + if( !c->list ) { + log_error("orphaned user ID\n" ); + return 0; + } + add_kbnode( c->list, new_kbnode( pkt ) ); + return 1; +} + +static int +add_subkey( CTX c, PACKET *pkt ) +{ + if( !c->list ) { + log_error("subkey w/o mainkey\n" ); + return 0; + } + add_kbnode( c->list, new_kbnode( pkt ) ); + return 1; +} + +static int +add_ring_trust( CTX c, PACKET *pkt ) +{ + if( !c->list ) { + log_error("ring trust w/o key\n" ); + return 0; + } + add_kbnode( c->list, new_kbnode( pkt ) ); + return 1; +} + + +static int +add_signature( CTX c, PACKET *pkt ) +{ + KBNODE node; + + c->any_sig_seen = 1; + if( pkt->pkttype == PKT_SIGNATURE && !c->list ) { + /* This is the first signature for the following datafile. + * GPG does not write such packets; instead it always uses + * onepass-sig packets. The drawback of PGP's method + * of prepending the signature to the data is + * that it is not possible to make a signature from data read + * from stdin. (GPG is able to read PGP stuff anyway.) */ + node = new_kbnode( pkt ); + c->list = node; + return 1; + } + else if( !c->list ) + return 0; /* oops (invalid packet sequence)*/ + else if( !c->list->pkt ) + BUG(); /* so nicht */ + + /* add a new signature node id at the end */ + node = new_kbnode( pkt ); + add_kbnode( c->list, node ); + return 1; +} + +static int +symkey_decrypt_seskey( DEK *dek, byte *seskey, size_t slen ) +{ + CIPHER_HANDLE hd; + + if(slen < 17 || slen > 33) + { + log_error ( _("weird size for an encrypted session key (%d)\n"), + (int)slen); + return G10ERR_BAD_KEY; + } + + hd = cipher_open( dek->algo, CIPHER_MODE_CFB, 1 ); + cipher_setkey( hd, dek->key, dek->keylen ); + cipher_setiv( hd, NULL, 0 ); + cipher_decrypt( hd, seskey, seskey, slen ); + cipher_close( hd ); + + /* now we replace the dek components with the real session key to + decrypt the contents of the sequencing packet. */ + + dek->keylen=slen-1; + dek->algo=seskey[0]; + + if(dek->keylen > DIM(dek->key)) + BUG (); + + /* This is not completely accurate, since a bad passphrase may have + resulted in a garbage algorithm byte, but it's close enough since + a bogus byte here will fail later. */ + if(dek->algo==CIPHER_ALGO_IDEA) + idea_cipher_warn(0); + + memcpy(dek->key, seskey + 1, dek->keylen); + + /*log_hexdump( "thekey", dek->key, dek->keylen );*/ + + return 0; +} + +static void +proc_symkey_enc( CTX c, PACKET *pkt ) +{ + PKT_symkey_enc *enc; + + enc = pkt->pkt.symkey_enc; + if (!enc) + log_error ("invalid symkey encrypted packet\n"); + else if(!c->dek) + { + int algo = enc->cipher_algo; + const char *s = cipher_algo_to_string (algo); + + if(s) + { + if(!opt.quiet) + { + if(enc->seskeylen) + log_info(_("%s encrypted session key\n"), s ); + else + log_info(_("%s encrypted data\n"), s ); + } + } + else + log_error(_("encrypted with unknown algorithm %d\n"), algo ); + + if(check_digest_algo(enc->s2k.hash_algo)) + { + log_error(_("passphrase generated with unknown digest" + " algorithm %d\n"),enc->s2k.hash_algo); + s=NULL; + } + + c->last_was_session_key = 2; + if(!s || opt.list_only) + goto leave; + + if(opt.override_session_key) + { + c->dek = xmalloc_clear( sizeof *c->dek ); + if(get_override_session_key(c->dek, opt.override_session_key)) + { + xfree(c->dek); + c->dek = NULL; + } + } + else + { + int canceled; + + c->dek = passphrase_to_dek (NULL, 0, algo, &enc->s2k, 0, + NULL, &canceled); + if (canceled) + { + /* For unknown reasons passphrase_to_dek does only + return NULL if a new passphrase has been requested + and has not been repeated correctly. Thus even + with a cancel requested (by means of the gpg-agent) + it won't return NULL but an empty passphrase. We + take the most conservative approach for now and + work around it right here. */ + xfree (c->dek); + c->dek = NULL; + } + + if(c->dek) + { + c->dek->symmetric=1; + + /* FIXME: This doesn't work perfectly if a symmetric + key comes before a public key in the message - if + the user doesn't know the passphrase, then there is + a chance that the "decrypted" algorithm will happen + to be a valid one, which will make the returned dek + appear valid, so we won't try any public keys that + come later. */ + if(enc->seskeylen) + { + if(symkey_decrypt_seskey(c->dek, enc->seskey, + enc->seskeylen)) + { + xfree(c->dek); + c->dek=NULL; + } + } + else + c->dek->algo_info_printed = 1; + } + } + } + + leave: + c->symkeys++; + free_packet(pkt); +} + +static void +proc_pubkey_enc( CTX c, PACKET *pkt ) +{ + PKT_pubkey_enc *enc; + int result = 0; + + /* check whether the secret key is available and store in this case */ + c->last_was_session_key = 1; + enc = pkt->pkt.pubkey_enc; + /*printf("enc: encrypted by a pubkey with keyid %08lX\n", enc->keyid[1] );*/ + /* Hmmm: why do I have this algo check here - anyway there is + * function to check it. */ + if( opt.verbose ) + log_info(_("public key is %s\n"), keystr(enc->keyid) ); + + if( is_status_enabled() ) { + char buf[50]; + sprintf(buf, "%08lX%08lX %d 0", + (ulong)enc->keyid[0], (ulong)enc->keyid[1], enc->pubkey_algo ); + write_status_text( STATUS_ENC_TO, buf ); + } + + if( !opt.list_only && opt.override_session_key ) { + /* It does not make much sense to store the session key in + * secure memory because it has already been passed on the + * command line and the GCHQ knows about it. */ + c->dek = xmalloc_clear( sizeof *c->dek ); + result = get_override_session_key ( c->dek, opt.override_session_key ); + if ( result ) { + xfree(c->dek); c->dek = NULL; + } + } + else if( is_ELGAMAL(enc->pubkey_algo) + || enc->pubkey_algo == PUBKEY_ALGO_DSA + || is_RSA(enc->pubkey_algo) ) { + /* FIXME: strore this all in a list and process it later */ + + if ( !c->dek && ((!enc->keyid[0] && !enc->keyid[1]) + || opt.try_all_secrets + || !seckey_available( enc->keyid )) ) { + if( opt.list_only ) + result = -1; + else { + c->dek = xmalloc_secure_clear( sizeof *c->dek ); + if( (result = get_session_key( enc, c->dek )) ) { + /* error: delete the DEK */ + xfree(c->dek); c->dek = NULL; + } + } + } + else + result = G10ERR_NO_SECKEY; + } + else + result = G10ERR_PUBKEY_ALGO; + + if( result == -1 ) + ; + else + { + /* store it for later display */ + struct kidlist_item *x = xmalloc( sizeof *x ); + x->kid[0] = enc->keyid[0]; + x->kid[1] = enc->keyid[1]; + x->pubkey_algo = enc->pubkey_algo; + x->reason = result; + x->next = c->pkenc_list; + c->pkenc_list = x; + + if( !result && opt.verbose > 1 ) + log_info( _("public key encrypted data: good DEK\n") ); + } + + free_packet(pkt); +} + + + +/**************** + * Print the list of public key encrypted packets which we could + * not decrypt. + */ +static void +print_pkenc_list( struct kidlist_item *list, int failed ) +{ + for( ; list; list = list->next ) { + PKT_public_key *pk; + const char *algstr; + + if ( failed && !list->reason ) + continue; + if ( !failed && list->reason ) + continue; + + algstr = pubkey_algo_to_string( list->pubkey_algo ); + pk = xmalloc_clear( sizeof *pk ); + + if( !algstr ) + algstr = "[?]"; + pk->pubkey_algo = list->pubkey_algo; + if( !get_pubkey( pk, list->kid ) ) + { + char *p; + log_info( _("encrypted with %u-bit %s key, ID %s, created %s\n"), + nbits_from_pk( pk ), algstr, keystr_from_pk(pk), + strtimestamp(pk->timestamp) ); + p=get_user_id_native(list->kid); + fprintf(log_stream(),_(" \"%s\"\n"),p); + xfree(p); + } + else + log_info(_("encrypted with %s key, ID %s\n"), + algstr,keystr(list->kid)); + + free_public_key( pk ); + + if( list->reason == G10ERR_NO_SECKEY ) { + if( is_status_enabled() ) { + char buf[20]; + sprintf(buf,"%08lX%08lX", (ulong)list->kid[0], + (ulong)list->kid[1] ); + write_status_text( STATUS_NO_SECKEY, buf ); + } + } + else if (list->reason) + log_info(_("public key decryption failed: %s\n"), + g10_errstr(list->reason)); + } +} + + +static void +proc_encrypted( CTX c, PACKET *pkt ) +{ + int result = 0; + + if (!opt.quiet) + { + if(c->symkeys>1) + log_info(_("encrypted with %lu passphrases\n"),c->symkeys); + else if(c->symkeys==1) + log_info(_("encrypted with 1 passphrase\n")); + print_pkenc_list ( c->pkenc_list, 1 ); + print_pkenc_list ( c->pkenc_list, 0 ); + } + + /* FIXME: Figure out the session key by looking at all pkenc packets. */ + + + write_status( STATUS_BEGIN_DECRYPTION ); + + /*log_debug("dat: %sencrypted data\n", c->dek?"":"conventional ");*/ + if( opt.list_only ) + result = -1; + else if( !c->dek && !c->last_was_session_key ) { + int algo; + STRING2KEY s2kbuf, *s2k = NULL; + + if(opt.override_session_key) + { + c->dek = xmalloc_clear( sizeof *c->dek ); + result=get_override_session_key(c->dek, opt.override_session_key); + if(result) + { + xfree(c->dek); + c->dek = NULL; + } + } + else + { + /* assume this is old style conventional encrypted data */ + if ( (algo = opt.def_cipher_algo)) + log_info (_("assuming %s encrypted data\n"), + cipher_algo_to_string(algo)); + else if ( check_cipher_algo(CIPHER_ALGO_IDEA) ) + { + algo = opt.def_cipher_algo; + if (!algo) + algo = opt.s2k_cipher_algo; + idea_cipher_warn(1); + log_info (_("IDEA cipher unavailable, " + "optimistically attempting to use %s instead\n"), + cipher_algo_to_string(algo)); + } + else + { + algo = CIPHER_ALGO_IDEA; + if (!opt.s2k_digest_algo) + { + /* If no digest is given we assume MD5 */ + s2kbuf.mode = 0; + s2kbuf.hash_algo = DIGEST_ALGO_MD5; + s2k = &s2kbuf; + } + log_info (_("assuming %s encrypted data\n"), "IDEA"); + } + + c->dek = passphrase_to_dek ( NULL, 0, algo, s2k, 0, NULL, NULL ); + if (c->dek) + c->dek->algo_info_printed = 1; + } + } + else if( !c->dek ) + result = G10ERR_NO_SECKEY; + if( !result ) + result = decrypt_data( c, pkt->pkt.encrypted, c->dek ); + + if( result == -1 ) + ; + else if( !result || (result==G10ERR_BAD_SIGN && opt.ignore_mdc_error)) { + write_status( STATUS_DECRYPTION_OKAY ); + if( opt.verbose > 1 ) + log_info(_("decryption okay\n")); + if( pkt->pkt.encrypted->mdc_method && !result ) + write_status( STATUS_GOODMDC ); + else if(!opt.no_mdc_warn) + log_info (_("WARNING: message was not integrity protected\n")); + if(opt.show_session_key) + { + int i; + char *buf = xmalloc ( c->dek->keylen*2 + 20 ); + sprintf ( buf, "%d:", c->dek->algo ); + for(i=0; i < c->dek->keylen; i++ ) + sprintf(buf+strlen(buf), "%02X", c->dek->key[i] ); + log_info( "session key: `%s'\n", buf ); + write_status_text ( STATUS_SESSION_KEY, buf ); + } + } + else if( result == G10ERR_BAD_SIGN ) { + log_error(_("WARNING: encrypted message has been manipulated!\n")); + write_status( STATUS_BADMDC ); + write_status( STATUS_DECRYPTION_FAILED ); + } + else { + write_status( STATUS_DECRYPTION_FAILED ); + log_error(_("decryption failed: %s\n"), g10_errstr(result)); + /* Hmmm: does this work when we have encrypted using multiple + * ways to specify the session key (symmmetric and PK)*/ + } + xfree(c->dek); c->dek = NULL; + free_packet(pkt); + c->last_was_session_key = 0; + write_status( STATUS_END_DECRYPTION ); +} + + +static void +proc_plaintext( CTX c, PACKET *pkt ) +{ + PKT_plaintext *pt = pkt->pkt.plaintext; + int any, clearsig, only_md5, rc; + KBNODE n; + + if( pt->namelen == 8 && !memcmp( pt->name, "_CONSOLE", 8 ) ) + log_info(_("NOTE: sender requested \"for-your-eyes-only\"\n")); + else if( opt.verbose ) + log_info(_("original file name='%.*s'\n"), pt->namelen, pt->name); + free_md_filter_context( &c->mfx ); + c->mfx.md = md_open( 0, 0); + /* fixme: we may need to push the textfilter if we have sigclass 1 + * and no armoring - Not yet tested + * Hmmm, why don't we need it at all if we have sigclass 1 + * Should we assume that plaintext in mode 't' has always sigclass 1?? + * See: Russ Allbery's mail 1999-02-09 + */ + any = clearsig = only_md5 = 0; + for(n=c->list; n; n = n->next ) + { + if( n->pkt->pkttype == PKT_ONEPASS_SIG ) + { + /* For the onepass signature case */ + if( n->pkt->pkt.onepass_sig->digest_algo ) + { + md_enable( c->mfx.md, n->pkt->pkt.onepass_sig->digest_algo ); + if( !any && n->pkt->pkt.onepass_sig->digest_algo + == DIGEST_ALGO_MD5 ) + only_md5 = 1; + else + only_md5 = 0; + any = 1; + } + if( n->pkt->pkt.onepass_sig->sig_class != 0x01 ) + only_md5 = 0; + } + else if( n->pkt->pkttype == PKT_GPG_CONTROL + && n->pkt->pkt.gpg_control->control + == CTRLPKT_CLEARSIGN_START ) + { + /* For the clearsigned message case */ + size_t datalen = n->pkt->pkt.gpg_control->datalen; + const byte *data = n->pkt->pkt.gpg_control->data; + + /* check that we have at least the sigclass and one hash */ + if ( datalen < 2 ) + log_fatal("invalid control packet CTRLPKT_CLEARSIGN_START\n"); + /* Note that we don't set the clearsig flag for not-dash-escaped + * documents */ + clearsig = (*data == 0x01); + for( data++, datalen--; datalen; datalen--, data++ ) + md_enable( c->mfx.md, *data ); + any = 1; + break; /* Stop here as one-pass signature packets are not + expected. */ + } + else if(n->pkt->pkttype==PKT_SIGNATURE) + { + /* For the SIG+LITERAL case that PGP used to use. */ + md_enable( c->mfx.md, n->pkt->pkt.signature->digest_algo ); + any=1; + } + } + + if( !any && !opt.skip_verify ) + { + /* This is for the old GPG LITERAL+SIG case. It's not legal + according to 2440, so hopefully it won't come up that + often. There is no good way to specify what algorithms to + use in that case, so these three are the historical + answer. */ + md_enable( c->mfx.md, DIGEST_ALGO_RMD160 ); + md_enable( c->mfx.md, DIGEST_ALGO_SHA1 ); + md_enable( c->mfx.md, DIGEST_ALGO_MD5 ); + } + if( opt.pgp2_workarounds && only_md5 && !opt.skip_verify ) { + /* This is a kludge to work around a bug in pgp2. It does only + * catch those mails which are armored. To catch the non-armored + * pgp mails we could see whether there is the signature packet + * in front of the plaintext. If someone needs this, send me a patch. + */ + c->mfx.md2 = md_open( DIGEST_ALGO_MD5, 0); + } + if ( DBG_HASHING ) { + md_start_debug( c->mfx.md, "verify" ); + if ( c->mfx.md2 ) + md_start_debug( c->mfx.md2, "verify2" ); + } + if ( c->pipemode.op == 'B' ) + rc = handle_plaintext( pt, &c->mfx, 1, 0 ); + else { + rc = handle_plaintext( pt, &c->mfx, c->sigs_only, clearsig ); + if( rc == G10ERR_CREATE_FILE && !c->sigs_only) { + /* can't write output but we hash it anyway to + * check the signature */ + rc = handle_plaintext( pt, &c->mfx, 1, clearsig ); + } + } + if( rc ) + log_error( "handle plaintext failed: %s\n", g10_errstr(rc)); + free_packet(pkt); + c->last_was_session_key = 0; + + /* We add a marker control packet instead of the plaintext packet. + * This is so that we can later detect invalid packet sequences. + */ + n = new_kbnode (create_gpg_control (CTRLPKT_PLAINTEXT_MARK, NULL, 0)); + if (c->list) + add_kbnode (c->list, n); + else + c->list = n; +} + + +static int +proc_compressed_cb( IOBUF a, void *info ) +{ + return proc_signature_packets( info, a, ((CTX)info)->signed_data, + ((CTX)info)->sigfilename ); +} + +static int +proc_encrypt_cb( IOBUF a, void *info ) +{ + return proc_encryption_packets( info, a ); +} + +static void +proc_compressed( CTX c, PACKET *pkt ) +{ + PKT_compressed *zd = pkt->pkt.compressed; + int rc; + + /*printf("zip: compressed data packet\n");*/ + if( !zd->algorithm ) + rc=G10ERR_COMPR_ALGO; + else if( c->sigs_only ) + rc = handle_compressed( c, zd, proc_compressed_cb, c ); + else if( c->encrypt_only ) + rc = handle_compressed( c, zd, proc_encrypt_cb, c ); + else + rc = handle_compressed( c, zd, NULL, NULL ); + if( rc ) + log_error("uncompressing failed: %s\n", g10_errstr(rc)); + free_packet(pkt); + c->last_was_session_key = 0; +} + +/**************** + * check the signature + * Returns: 0 = valid signature or an error code + */ +static int +do_check_sig( CTX c, KBNODE node, int *is_selfsig, + int *is_expkey, int *is_revkey ) +{ + PKT_signature *sig; + MD_HANDLE md = NULL, md2 = NULL; + int algo, rc; + + assert( node->pkt->pkttype == PKT_SIGNATURE ); + if( is_selfsig ) + *is_selfsig = 0; + sig = node->pkt->pkt.signature; + + algo = sig->digest_algo; + if( (rc=check_digest_algo(algo)) ) + return rc; + + if( sig->sig_class == 0x00 ) { + if( c->mfx.md ) + md = md_copy( c->mfx.md ); + else /* detached signature */ + md = md_open( 0, 0 ); /* signature_check() will enable the md*/ + } + else if( sig->sig_class == 0x01 ) { + /* how do we know that we have to hash the (already hashed) text + * in canonical mode ??? (calculating both modes???) */ + if( c->mfx.md ) { + md = md_copy( c->mfx.md ); + if( c->mfx.md2 ) + md2 = md_copy( c->mfx.md2 ); + } + else { /* detached signature */ + log_debug("Do we really need this here?"); + md = md_open( 0, 0 ); /* signature_check() will enable the md*/ + md2 = md_open( 0, 0 ); + } + } + else if( (sig->sig_class&~3) == 0x10 + || sig->sig_class == 0x18 + || sig->sig_class == 0x1f + || sig->sig_class == 0x20 + || sig->sig_class == 0x28 + || sig->sig_class == 0x30 ) { + if( c->list->pkt->pkttype == PKT_PUBLIC_KEY + || c->list->pkt->pkttype == PKT_PUBLIC_SUBKEY ) { + return check_key_signature( c->list, node, is_selfsig ); + } + else if( sig->sig_class == 0x20 ) { + log_error (_("standalone revocation - " + "use \"gpg --import\" to apply\n")); + return G10ERR_NOT_PROCESSED; + } + else { + log_error("invalid root packet for sigclass %02x\n", + sig->sig_class); + return G10ERR_SIG_CLASS; + } + } + else + return G10ERR_SIG_CLASS; + rc = signature_check2( sig, md, NULL, is_expkey, is_revkey, NULL ); + if( rc == G10ERR_BAD_SIGN && md2 ) + rc = signature_check2( sig, md2, NULL, is_expkey, is_revkey, NULL ); + md_close(md); + md_close(md2); + + return rc; +} + + +static void +print_userid( PACKET *pkt ) +{ + if( !pkt ) + BUG(); + if( pkt->pkttype != PKT_USER_ID ) { + printf("ERROR: unexpected packet type %d", pkt->pkttype ); + return; + } + if( opt.with_colons ) + { + if(pkt->pkt.user_id->attrib_data) + printf("%u %lu", + pkt->pkt.user_id->numattribs, + pkt->pkt.user_id->attrib_len); + else + print_string( stdout, pkt->pkt.user_id->name, + pkt->pkt.user_id->len, ':'); + } + else + print_utf8_string( stdout, pkt->pkt.user_id->name, + pkt->pkt.user_id->len ); +} + + +/**************** + * List the certificate in a user friendly way + */ + +static void +list_node( CTX c, KBNODE node ) +{ + int any=0; + int mainkey; + + if( !node ) + ; + else if( (mainkey = (node->pkt->pkttype == PKT_PUBLIC_KEY) ) + || node->pkt->pkttype == PKT_PUBLIC_SUBKEY ) { + PKT_public_key *pk = node->pkt->pkt.public_key; + + if( opt.with_colons ) + { + u32 keyid[2]; + keyid_from_pk( pk, keyid ); + if( mainkey ) + c->trustletter = opt.fast_list_mode? + 0 : get_validity_info( pk, NULL ); + printf("%s:", mainkey? "pub":"sub" ); + if( c->trustletter ) + putchar( c->trustletter ); + printf(":%u:%d:%08lX%08lX:%s:%s::", + nbits_from_pk( pk ), + pk->pubkey_algo, + (ulong)keyid[0],(ulong)keyid[1], + colon_datestr_from_pk( pk ), + colon_strtime (pk->expiredate) ); + if( mainkey && !opt.fast_list_mode ) + putchar( get_ownertrust_info (pk) ); + putchar(':'); + if( node->next && node->next->pkt->pkttype == PKT_RING_TRUST) { + putchar('\n'); any=1; + if( opt.fingerprint ) + print_fingerprint( pk, NULL, 0 ); + printf("rtv:1:%u:\n", + node->next->pkt->pkt.ring_trust->trustval ); + } + } + else + printf("%s %4u%c/%s %s%s", + mainkey? "pub":"sub", nbits_from_pk( pk ), + pubkey_letter( pk->pubkey_algo ), keystr_from_pk( pk ), + datestr_from_pk( pk ), mainkey?" ":""); + + if( mainkey ) { + /* and now list all userids with their signatures */ + for( node = node->next; node; node = node->next ) { + if( node->pkt->pkttype == PKT_SIGNATURE ) { + if( !any ) { + if( node->pkt->pkt.signature->sig_class == 0x20 ) + puts("[revoked]"); + else + putchar('\n'); + any = 1; + } + list_node(c, node ); + } + else if( node->pkt->pkttype == PKT_USER_ID ) { + if( any ) { + if( opt.with_colons ) + printf("%s:::::::::", + node->pkt->pkt.user_id->attrib_data?"uat":"uid"); + else + printf( "uid%*s", 28, "" ); + } + print_userid( node->pkt ); + if( opt.with_colons ) + putchar(':'); + putchar('\n'); + if( opt.fingerprint && !any ) + print_fingerprint( pk, NULL, 0 ); + if( opt.with_colons + && node->next + && node->next->pkt->pkttype == PKT_RING_TRUST ) { + printf("rtv:2:%u:\n", + node->next->pkt->pkt.ring_trust? + node->next->pkt->pkt.ring_trust->trustval : 0); + } + any=1; + } + else if( node->pkt->pkttype == PKT_PUBLIC_SUBKEY ) { + if( !any ) { + putchar('\n'); + any = 1; + } + list_node(c, node ); + } + } + } + else + { + /* of subkey */ + if( pk->is_revoked ) + { + printf(" ["); + printf(_("revoked: %s"),revokestr_from_pk(pk)); + printf("]"); + } + else if( pk->expiredate ) + { + printf(" ["); + printf(_("expires: %s"),expirestr_from_pk(pk)); + printf("]"); + } + } + + if( !any ) + putchar('\n'); + if( !mainkey && opt.fingerprint > 1 ) + print_fingerprint( pk, NULL, 0 ); + } + else if( (mainkey = (node->pkt->pkttype == PKT_SECRET_KEY) ) + || node->pkt->pkttype == PKT_SECRET_SUBKEY ) { + PKT_secret_key *sk = node->pkt->pkt.secret_key; + + if( opt.with_colons ) + { + u32 keyid[2]; + keyid_from_sk( sk, keyid ); + printf("%s::%u:%d:%08lX%08lX:%s:%s:::", + mainkey? "sec":"ssb", + nbits_from_sk( sk ), + sk->pubkey_algo, + (ulong)keyid[0],(ulong)keyid[1], + colon_datestr_from_sk( sk ), + colon_strtime (sk->expiredate) + /* fixme: add LID */ ); + } + else + printf("%s %4u%c/%s %s ", mainkey? "sec":"ssb", + nbits_from_sk( sk ), pubkey_letter( sk->pubkey_algo ), + keystr_from_sk( sk ), datestr_from_sk( sk )); + if( mainkey ) { + /* and now list all userids with their signatures */ + for( node = node->next; node; node = node->next ) { + if( node->pkt->pkttype == PKT_SIGNATURE ) { + if( !any ) { + if( node->pkt->pkt.signature->sig_class == 0x20 ) + puts("[revoked]"); + else + putchar('\n'); + any = 1; + } + list_node(c, node ); + } + else if( node->pkt->pkttype == PKT_USER_ID ) { + if( any ) { + if( opt.with_colons ) + printf("%s:::::::::", + node->pkt->pkt.user_id->attrib_data?"uat":"uid"); + else + printf( "uid%*s", 28, "" ); + } + print_userid( node->pkt ); + if( opt.with_colons ) + putchar(':'); + putchar('\n'); + if( opt.fingerprint && !any ) + print_fingerprint( NULL, sk, 0 ); + any=1; + } + else if( node->pkt->pkttype == PKT_SECRET_SUBKEY ) { + if( !any ) { + putchar('\n'); + any = 1; + } + list_node(c, node ); + } + } + } + if( !any ) + putchar('\n'); + if( !mainkey && opt.fingerprint > 1 ) + print_fingerprint( NULL, sk, 0 ); + } + else if( node->pkt->pkttype == PKT_SIGNATURE ) { + PKT_signature *sig = node->pkt->pkt.signature; + int is_selfsig = 0; + int rc2=0; + size_t n; + char *p; + int sigrc = ' '; + + if( !opt.verbose ) + return; + + if( sig->sig_class == 0x20 || sig->sig_class == 0x30 ) + fputs("rev", stdout); + else + fputs("sig", stdout); + if( opt.check_sigs ) { + fflush(stdout); + switch( (rc2=do_check_sig( c, node, &is_selfsig, NULL, NULL )) ) { + case 0: sigrc = '!'; break; + case G10ERR_BAD_SIGN: sigrc = '-'; break; + case G10ERR_NO_PUBKEY: + case G10ERR_UNU_PUBKEY: sigrc = '?'; break; + default: sigrc = '%'; break; + } + } + else { /* check whether this is a self signature */ + u32 keyid[2]; + + if( c->list->pkt->pkttype == PKT_PUBLIC_KEY + || c->list->pkt->pkttype == PKT_SECRET_KEY ) { + if( c->list->pkt->pkttype == PKT_PUBLIC_KEY ) + keyid_from_pk( c->list->pkt->pkt.public_key, keyid ); + else + keyid_from_sk( c->list->pkt->pkt.secret_key, keyid ); + + if( keyid[0] == sig->keyid[0] && keyid[1] == sig->keyid[1] ) + is_selfsig = 1; + } + } + if( opt.with_colons ) { + putchar(':'); + if( sigrc != ' ' ) + putchar(sigrc); + printf("::%d:%08lX%08lX:%s:%s:", sig->pubkey_algo, + (ulong)sig->keyid[0], (ulong)sig->keyid[1], + colon_datestr_from_sig(sig), + colon_expirestr_from_sig(sig)); + + if(sig->trust_depth || sig->trust_value) + printf("%d %d",sig->trust_depth,sig->trust_value); + printf(":"); + + if(sig->trust_regexp) + print_string(stdout,sig->trust_regexp, + strlen(sig->trust_regexp),':'); + printf(":"); + } + else + printf("%c %s %s ", + sigrc, keystr(sig->keyid), datestr_from_sig(sig)); + if( sigrc == '%' ) + printf("[%s] ", g10_errstr(rc2) ); + else if( sigrc == '?' ) + ; + else if( is_selfsig ) { + if( opt.with_colons ) + putchar(':'); + fputs( sig->sig_class == 0x18? "[keybind]":"[selfsig]", stdout); + if( opt.with_colons ) + putchar(':'); + } + else if( !opt.fast_list_mode ) { + p = get_user_id( sig->keyid, &n ); + print_string( stdout, p, n, opt.with_colons ); + xfree(p); + } + if( opt.with_colons ) + printf(":%02x%c:", sig->sig_class, sig->flags.exportable?'x':'l'); + putchar('\n'); + } + else + log_error("invalid node with packet of type %d\n", node->pkt->pkttype); +} + + + +int +proc_packets( void *anchor, IOBUF a ) +{ + int rc; + CTX c = xmalloc_clear( sizeof *c ); + + c->anchor = anchor; + rc = do_proc_packets( c, a ); + xfree( c ); + return rc; +} + + + +int +proc_signature_packets( void *anchor, IOBUF a, + STRLIST signedfiles, const char *sigfilename ) +{ + CTX c = xmalloc_clear( sizeof *c ); + int rc; + + c->anchor = anchor; + c->sigs_only = 1; + c->signed_data = signedfiles; + c->sigfilename = sigfilename; + rc = do_proc_packets( c, a ); + + /* If we have not encountered any signature we print an error + messages, send a NODATA status back and return an error code. + Using log_error is required because verify_files does not check + error codes for each file but we want to terminate the process + with an error. */ + if (!rc && !c->any_sig_seen) + { + write_status_text (STATUS_NODATA, "4"); + log_error (_("no signature found\n")); + rc = G10ERR_NO_DATA; + } + + /* Propagate the signature seen flag upward. Do this only on + success so that we won't issue the nodata status several + times. */ + if (!rc && c->anchor && c->any_sig_seen) + c->anchor->any_sig_seen = 1; + + xfree( c ); + return rc; +} + +int +proc_encryption_packets( void *anchor, IOBUF a ) +{ + CTX c = xmalloc_clear( sizeof *c ); + int rc; + + c->anchor = anchor; + c->encrypt_only = 1; + rc = do_proc_packets( c, a ); + xfree( c ); + return rc; +} + + +int +do_proc_packets( CTX c, IOBUF a ) +{ + PACKET *pkt = xmalloc( sizeof *pkt ); + int rc=0; + int any_data=0; + int newpkt; + + c->iobuf = a; + init_packet(pkt); + while( (rc=parse_packet(a, pkt)) != -1 ) { + any_data = 1; + if( rc ) { + free_packet(pkt); + /* stop processing when an invalid packet has been encountered + * but don't do so when we are doing a --list-packets. */ + if( rc == G10ERR_INVALID_PACKET && opt.list_packets != 2 ) + break; + continue; + } + newpkt = -1; + if( opt.list_packets ) { + switch( pkt->pkttype ) { + case PKT_PUBKEY_ENC: proc_pubkey_enc( c, pkt ); break; + case PKT_SYMKEY_ENC: proc_symkey_enc( c, pkt ); break; + case PKT_ENCRYPTED: + case PKT_ENCRYPTED_MDC: proc_encrypted( c, pkt ); break; + case PKT_COMPRESSED: proc_compressed( c, pkt ); break; + default: newpkt = 0; break; + } + } + else if( c->sigs_only ) { + switch( pkt->pkttype ) { + case PKT_PUBLIC_KEY: + case PKT_SECRET_KEY: + case PKT_USER_ID: + case PKT_SYMKEY_ENC: + case PKT_PUBKEY_ENC: + case PKT_ENCRYPTED: + case PKT_ENCRYPTED_MDC: + write_status_text( STATUS_UNEXPECTED, "0" ); + rc = G10ERR_UNEXPECTED; + goto leave; + case PKT_SIGNATURE: newpkt = add_signature( c, pkt ); break; + case PKT_PLAINTEXT: proc_plaintext( c, pkt ); break; + case PKT_COMPRESSED: proc_compressed( c, pkt ); break; + case PKT_ONEPASS_SIG: newpkt = add_onepass_sig( c, pkt ); break; + case PKT_GPG_CONTROL: newpkt = add_gpg_control(c, pkt); break; + default: newpkt = 0; break; + } + } + else if( c->encrypt_only ) { + switch( pkt->pkttype ) { + case PKT_PUBLIC_KEY: + case PKT_SECRET_KEY: + case PKT_USER_ID: + write_status_text( STATUS_UNEXPECTED, "0" ); + rc = G10ERR_UNEXPECTED; + goto leave; + case PKT_SIGNATURE: newpkt = add_signature( c, pkt ); break; + case PKT_SYMKEY_ENC: proc_symkey_enc( c, pkt ); break; + case PKT_PUBKEY_ENC: proc_pubkey_enc( c, pkt ); break; + case PKT_ENCRYPTED: + case PKT_ENCRYPTED_MDC: proc_encrypted( c, pkt ); break; + case PKT_PLAINTEXT: proc_plaintext( c, pkt ); break; + case PKT_COMPRESSED: proc_compressed( c, pkt ); break; + case PKT_ONEPASS_SIG: newpkt = add_onepass_sig( c, pkt ); break; + case PKT_GPG_CONTROL: newpkt = add_gpg_control(c, pkt); break; + default: newpkt = 0; break; + } + } + else { + switch( pkt->pkttype ) { + case PKT_PUBLIC_KEY: + case PKT_SECRET_KEY: + release_list( c ); + c->list = new_kbnode( pkt ); + newpkt = 1; + break; + case PKT_PUBLIC_SUBKEY: + case PKT_SECRET_SUBKEY: + newpkt = add_subkey( c, pkt ); + break; + case PKT_USER_ID: newpkt = add_user_id( c, pkt ); break; + case PKT_SIGNATURE: newpkt = add_signature( c, pkt ); break; + case PKT_PUBKEY_ENC: proc_pubkey_enc( c, pkt ); break; + case PKT_SYMKEY_ENC: proc_symkey_enc( c, pkt ); break; + case PKT_ENCRYPTED: + case PKT_ENCRYPTED_MDC: proc_encrypted( c, pkt ); break; + case PKT_PLAINTEXT: proc_plaintext( c, pkt ); break; + case PKT_COMPRESSED: proc_compressed( c, pkt ); break; + case PKT_ONEPASS_SIG: newpkt = add_onepass_sig( c, pkt ); break; + case PKT_GPG_CONTROL: newpkt = add_gpg_control(c, pkt); break; + case PKT_RING_TRUST: newpkt = add_ring_trust( c, pkt ); break; + default: newpkt = 0; break; + } + } + /* This is a very ugly construct and frankly, I don't remember why + * I used it. Adding the MDC check here is a hack. + * The right solution is to initiate another context for encrypted + * packet and not to reuse the current one ... It works right + * when there is a compression packet inbetween which adds just + * an extra layer. + * Hmmm: Rewrite this whole module here?? + */ + if( pkt->pkttype != PKT_SIGNATURE && pkt->pkttype != PKT_MDC ) + c->have_data = pkt->pkttype == PKT_PLAINTEXT; + + if( newpkt == -1 ) + ; + else if( newpkt ) { + pkt = xmalloc( sizeof *pkt ); + init_packet(pkt); + } + else + free_packet(pkt); + if ( c->pipemode.stop_now ) { + /* we won't get an EOF in pipemode, so we have to + * break the loop here */ + rc = -1; + break; + } + } + if( rc == G10ERR_INVALID_PACKET ) + write_status_text( STATUS_NODATA, "3" ); + if( any_data ) + rc = 0; + else if( rc == -1 ) + write_status_text( STATUS_NODATA, "2" ); + + + leave: + release_list( c ); + xfree(c->dek); + free_packet( pkt ); + xfree( pkt ); + free_md_filter_context( &c->mfx ); + return rc; +} + + +/* Helper for pka_uri_from_sig to parse the to-be-verified address out + of the notation data. */ +static pka_info_t * +get_pka_address (PKT_signature *sig) +{ + pka_info_t *pka = NULL; + struct notation *nd,*notation; + + notation=sig_to_notation(sig); + + for(nd=notation;nd;nd=nd->next) + { + if(strcmp(nd->name,"pka-address@gnupg.org")!=0) + continue; /* Not the notation we want. */ + + /* For now we only use the first valid PKA notation. In future + we might want to keep additional PKA notations in a linked + list. */ + if (is_valid_mailbox (nd->value)) + { + pka = xmalloc (sizeof *pka + strlen(nd->value)); + pka->valid = 0; + pka->checked = 0; + pka->uri = NULL; + strcpy (pka->email, nd->value); + break; + } + } + + free_notation(notation); + + return pka; +} + + +/* Return the URI from a DNS PKA record. If this record has already + be retrieved for the signature we merely return it; if not we go + out and try to get that DNS record. */ +static const char * +pka_uri_from_sig (PKT_signature *sig) +{ + if (!sig->flags.pka_tried) + { + assert (!sig->pka_info); + sig->flags.pka_tried = 1; + sig->pka_info = get_pka_address (sig); + if (sig->pka_info) + { + char *uri; + + uri = get_pka_info (sig->pka_info->email, sig->pka_info->fpr); + if (uri) + { + sig->pka_info->valid = 1; + if (!*uri) + xfree (uri); + else + sig->pka_info->uri = uri; + } + } + } + return sig->pka_info? sig->pka_info->uri : NULL; +} + + +static int +check_sig_and_print( CTX c, KBNODE node ) +{ + PKT_signature *sig = node->pkt->pkt.signature; + const char *astr; + int rc, is_expkey=0, is_revkey=0; + + if (opt.skip_verify) + { + log_info(_("signature verification suppressed\n")); + return 0; + } + + /* Check that the message composition is valid. + + Per RFC-2440bis (-15) allowed: + + S{1,n} -- detached signature. + S{1,n} P -- old style PGP2 signature + O{1,n} P S{1,n} -- standard OpenPGP signature. + C P S{1,n} -- cleartext signature. + + + O = One-Pass Signature packet. + S = Signature packet. + P = OpenPGP Message packet (Encrypted | Compressed | Literal) + (Note that the current rfc2440bis draft also allows + for a signed message but that does not work as it + introduces ambiguities.) + We keep track of these packages using the marker packet + CTRLPKT_PLAINTEXT_MARK. + C = Marker packet for cleartext signatures. + + We reject all other messages. + + Actually we are calling this too often, i.e. for verification of + each message but better have some duplicate work than to silently + introduce a bug here. + */ + { + KBNODE n; + int n_onepass, n_sig; + +/* log_debug ("checking signature packet composition\n"); */ +/* dump_kbnode (c->list); */ + + n = c->list; + assert (n); + if ( n->pkt->pkttype == PKT_SIGNATURE ) + { + /* This is either "S{1,n}" case (detached signature) or + "S{1,n} P" (old style PGP2 signature). */ + for (n = n->next; n; n = n->next) + if (n->pkt->pkttype != PKT_SIGNATURE) + break; + if (!n) + ; /* Okay, this is a detached signature. */ + else if (n->pkt->pkttype == PKT_GPG_CONTROL + && (n->pkt->pkt.gpg_control->control + == CTRLPKT_PLAINTEXT_MARK) ) + { + if (n->next) + goto ambiguous; /* We only allow one P packet. */ + } + else + goto ambiguous; + } + else if (n->pkt->pkttype == PKT_ONEPASS_SIG) + { + /* This is the "O{1,n} P S{1,n}" case (standard signature). */ + for (n_onepass=1, n = n->next; + n && n->pkt->pkttype == PKT_ONEPASS_SIG; n = n->next) + n_onepass++; + if (!n || !(n->pkt->pkttype == PKT_GPG_CONTROL + && (n->pkt->pkt.gpg_control->control + == CTRLPKT_PLAINTEXT_MARK))) + goto ambiguous; + for (n_sig=0, n = n->next; + n && n->pkt->pkttype == PKT_SIGNATURE; n = n->next) + n_sig++; + if (!n_sig) + goto ambiguous; + if (n && !opt.allow_multisig_verification) + goto ambiguous; + if (n_onepass != n_sig) + { + log_info ("number of one-pass packets does not match " + "number of signature packets\n"); + goto ambiguous; + } + } + else if (n->pkt->pkttype == PKT_GPG_CONTROL + && n->pkt->pkt.gpg_control->control == CTRLPKT_CLEARSIGN_START ) + { + /* This is the "C P S{1,n}" case (clear text signature). */ + n = n->next; + if (!n || !(n->pkt->pkttype == PKT_GPG_CONTROL + && (n->pkt->pkt.gpg_control->control + == CTRLPKT_PLAINTEXT_MARK))) + goto ambiguous; + for (n_sig=0, n = n->next; + n && n->pkt->pkttype == PKT_SIGNATURE; n = n->next) + n_sig++; + if (n || !n_sig) + goto ambiguous; + } + else + { + ambiguous: + log_error(_("can't handle this ambiguous signature data\n")); + return 0; + } + + } + + /* (Indendation below not yet changed to GNU style.) */ + + astr = pubkey_algo_to_string( sig->pubkey_algo ); + if(keystrlen()>8) + { + log_info(_("Signature made %s\n"),asctimestamp(sig->timestamp)); + log_info(_(" using %s key %s\n"), + astr? astr: "?",keystr(sig->keyid)); + } + else + log_info(_("Signature made %s using %s key ID %s\n"), + asctimestamp(sig->timestamp), astr? astr: "?", + keystr(sig->keyid)); + + rc = do_check_sig(c, node, NULL, &is_expkey, &is_revkey ); + + /* If the key isn't found, check for a preferred keyserver */ + + if(rc==G10ERR_NO_PUBKEY && sig->flags.pref_ks) + { + const byte *p; + int seq=0; + size_t n; + + while((p=enum_sig_subpkt(sig->hashed,SIGSUBPKT_PREF_KS,&n,&seq,NULL))) + { + /* According to my favorite copy editor, in English + grammar, you say "at" if the key is located on a web + page, but "from" if it is located on a keyserver. I'm + not going to even try to make two strings here :) */ + log_info(_("Key available at: ") ); + print_utf8_string( log_stream(), p, n ); + putc( '\n', log_stream() ); + + if(opt.keyserver_options.options&KEYSERVER_AUTO_KEY_RETRIEVE + && opt.keyserver_options.options&KEYSERVER_HONOR_KEYSERVER_URL) + { + struct keyserver_spec *spec; + + spec=parse_preferred_keyserver(sig); + if(spec) + { + int res; + + glo_ctrl.in_auto_key_retrieve++; + res=keyserver_import_keyid(sig->keyid,spec); + glo_ctrl.in_auto_key_retrieve--; + if(!res) + rc=do_check_sig(c, node, NULL, &is_expkey, &is_revkey ); + free_keyserver_spec(spec); + + if(!rc) + break; + } + } + } + } + + /* If the preferred keyserver thing above didn't work, our second + try is to use the URI from a DNS PKA record. */ + if ( rc == G10ERR_NO_PUBKEY + && opt.keyserver_options.options&KEYSERVER_AUTO_KEY_RETRIEVE + && opt.keyserver_options.options&KEYSERVER_HONOR_PKA_RECORD) + { + const char *uri = pka_uri_from_sig (sig); + + if (uri) + { + /* FIXME: We might want to locate the key using the + fingerprint instead of the keyid. */ + int res; + struct keyserver_spec *spec; + + spec = parse_keyserver_uri (uri, 1, NULL, 0); + if (spec) + { + glo_ctrl.in_auto_key_retrieve++; + res = keyserver_import_keyid (sig->keyid, spec); + glo_ctrl.in_auto_key_retrieve--; + free_keyserver_spec (spec); + if (!res) + rc = do_check_sig(c, node, NULL, &is_expkey, &is_revkey ); + } + } + } + + /* If the preferred keyserver thing above didn't work and we got + no information from the DNS PKA, this is a third try. */ + + if( rc == G10ERR_NO_PUBKEY && opt.keyserver + && opt.keyserver_options.options&KEYSERVER_AUTO_KEY_RETRIEVE) + { + int res; + + glo_ctrl.in_auto_key_retrieve++; + res=keyserver_import_keyid ( sig->keyid, opt.keyserver ); + glo_ctrl.in_auto_key_retrieve--; + if(!res) + rc = do_check_sig(c, node, NULL, &is_expkey, &is_revkey ); + } + + if( !rc || rc == G10ERR_BAD_SIGN ) { + KBNODE un, keyblock; + int count=0, statno; + char keyid_str[50]; + PKT_public_key *pk=NULL; + + if(rc) + statno=STATUS_BADSIG; + else if(sig->flags.expired) + statno=STATUS_EXPSIG; + else if(is_expkey) + statno=STATUS_EXPKEYSIG; + else if(is_revkey) + statno=STATUS_REVKEYSIG; + else + statno=STATUS_GOODSIG; + + keyblock = get_pubkeyblock( sig->keyid ); + + sprintf (keyid_str, "%08lX%08lX [uncertain] ", + (ulong)sig->keyid[0], (ulong)sig->keyid[1]); + + /* find and print the primary user ID */ + for( un=keyblock; un; un = un->next ) { + char *p; + int valid; + if(un->pkt->pkttype==PKT_PUBLIC_KEY) + { + pk=un->pkt->pkt.public_key; + continue; + } + if( un->pkt->pkttype != PKT_USER_ID ) + continue; + if ( !un->pkt->pkt.user_id->created ) + continue; + if ( un->pkt->pkt.user_id->is_revoked ) + continue; + if ( un->pkt->pkt.user_id->is_expired ) + continue; + if ( !un->pkt->pkt.user_id->is_primary ) + continue; + /* We want the textual primary user ID here */ + if ( un->pkt->pkt.user_id->attrib_data ) + continue; + + assert(pk); + + /* Get it before we print anything to avoid interrupting + the output with the "please do a --check-trustdb" + line. */ + valid=get_validity(pk,un->pkt->pkt.user_id); + + keyid_str[17] = 0; /* cut off the "[uncertain]" part */ + write_status_text_and_buffer (statno, keyid_str, + un->pkt->pkt.user_id->name, + un->pkt->pkt.user_id->len, + -1 ); + + p=utf8_to_native(un->pkt->pkt.user_id->name, + un->pkt->pkt.user_id->len,0); + + if(rc) + log_info(_("BAD signature from \"%s\""),p); + else if(sig->flags.expired) + log_info(_("Expired signature from \"%s\""),p); + else + log_info(_("Good signature from \"%s\""),p); + + xfree(p); + + if(opt.verify_options&VERIFY_SHOW_UID_VALIDITY) + fprintf(log_stream()," [%s]\n",trust_value_to_string(valid)); + else + fputs("\n", log_stream() ); + count++; + } + if( !count ) { /* just in case that we have no valid textual + userid */ + char *p; + + /* Try for an invalid textual userid */ + for( un=keyblock; un; un = un->next ) { + if( un->pkt->pkttype == PKT_USER_ID && + !un->pkt->pkt.user_id->attrib_data ) + break; + } + + /* Try for any userid at all */ + if(!un) { + for( un=keyblock; un; un = un->next ) { + if( un->pkt->pkttype == PKT_USER_ID ) + break; + } + } + + if (opt.trust_model==TM_ALWAYS || !un) + keyid_str[17] = 0; /* cut off the "[uncertain]" part */ + + write_status_text_and_buffer (statno, keyid_str, + un? un->pkt->pkt.user_id->name:"[?]", + un? un->pkt->pkt.user_id->len:3, + -1 ); + + if(un) + p=utf8_to_native(un->pkt->pkt.user_id->name, + un->pkt->pkt.user_id->len,0); + else + p=xstrdup("[?]"); + + if(rc) + log_info(_("BAD signature from \"%s\""),p); + else if(sig->flags.expired) + log_info(_("Expired signature from \"%s\""),p); + else + log_info(_("Good signature from \"%s\""),p); + if (opt.trust_model!=TM_ALWAYS && un) + { + putc(' ', log_stream() ); + fputs(_("[uncertain]"), log_stream() ); + } + fputs("\n", log_stream() ); + } + + /* If we have a good signature and already printed + * the primary user ID, print all the other user IDs */ + if ( count && !rc ) { + char *p; + for( un=keyblock; un; un = un->next ) { + if( un->pkt->pkttype != PKT_USER_ID ) + continue; + if((un->pkt->pkt.user_id->is_revoked + || un->pkt->pkt.user_id->is_expired) + && !(opt.verify_options&VERIFY_SHOW_UNUSABLE_UIDS)) + continue; + /* Only skip textual primaries */ + if ( un->pkt->pkt.user_id->is_primary && + !un->pkt->pkt.user_id->attrib_data ) + continue; + + if(un->pkt->pkt.user_id->attrib_data) + { + dump_attribs(un->pkt->pkt.user_id,pk,NULL); + + if(opt.verify_options&VERIFY_SHOW_PHOTOS) + show_photos(un->pkt->pkt.user_id->attribs, + un->pkt->pkt.user_id->numattribs,pk,NULL); + } + + p=utf8_to_native(un->pkt->pkt.user_id->name, + un->pkt->pkt.user_id->len,0); + log_info(_(" aka \"%s\""),p); + xfree(p); + + if(opt.verify_options&VERIFY_SHOW_UID_VALIDITY) + { + const char *valid; + if(un->pkt->pkt.user_id->is_revoked) + valid=_("revoked"); + else if(un->pkt->pkt.user_id->is_expired) + valid=_("expired"); + else + valid=trust_value_to_string(get_validity(pk, + un->pkt-> + pkt.user_id)); + fprintf(log_stream()," [%s]\n",valid); + } + else + fputs("\n", log_stream() ); + } + } + release_kbnode( keyblock ); + + if( !rc ) + { + if(opt.verify_options&VERIFY_SHOW_POLICY_URLS) + show_policy_url(sig,0,1); + else + show_policy_url(sig,0,2); + + if(opt.verify_options&VERIFY_SHOW_KEYSERVER_URLS) + show_keyserver_url(sig,0,1); + else + show_keyserver_url(sig,0,2); + + if(opt.verify_options&VERIFY_SHOW_NOTATIONS) + show_notation(sig,0,1, + ((opt.verify_options&VERIFY_SHOW_STD_NOTATIONS)?1:0)+ + ((opt.verify_options&VERIFY_SHOW_USER_NOTATIONS)?2:0)); + else + show_notation(sig,0,2,0); + } + + if( !rc && is_status_enabled() ) { + /* print a status response with the fingerprint */ + PKT_public_key *vpk = xmalloc_clear( sizeof *vpk ); + + if( !get_pubkey( vpk, sig->keyid ) ) { + byte array[MAX_FINGERPRINT_LEN], *p; + char buf[MAX_FINGERPRINT_LEN*4+90], *bufp; + size_t i, n; + + bufp = buf; + fingerprint_from_pk( vpk, array, &n ); + p = array; + for(i=0; i < n ; i++, p++, bufp += 2) + sprintf(bufp, "%02X", *p ); + /* TODO: Replace the reserved '0' in the field below + with bits for status flags (policy url, notation, + etc.). Remember to make the buffer larger to + match! */ + sprintf(bufp, " %s %lu %lu %d 0 %d %d %02X ", + strtimestamp( sig->timestamp ), + (ulong)sig->timestamp,(ulong)sig->expiredate, + sig->version,sig->pubkey_algo,sig->digest_algo, + sig->sig_class); + bufp = bufp + strlen (bufp); + if (!vpk->is_primary) { + u32 akid[2]; + + akid[0] = vpk->main_keyid[0]; + akid[1] = vpk->main_keyid[1]; + free_public_key (vpk); + vpk = xmalloc_clear( sizeof *vpk ); + if (get_pubkey (vpk, akid)) { + /* impossible error, we simply return a zeroed out fpr */ + n = MAX_FINGERPRINT_LEN < 20? MAX_FINGERPRINT_LEN : 20; + memset (array, 0, n); + } + else + fingerprint_from_pk( vpk, array, &n ); + } + p = array; + for(i=0; i < n ; i++, p++, bufp += 2) + sprintf(bufp, "%02X", *p ); + write_status_text( STATUS_VALIDSIG, buf ); + } + free_public_key( vpk ); + } + + if (!rc) + { + if(opt.verify_options&VERIFY_PKA_LOOKUPS) + pka_uri_from_sig (sig); /* Make sure PKA info is available. */ + rc = check_signatures_trust( sig ); + } + + if(sig->flags.expired) + { + log_info(_("Signature expired %s\n"), + asctimestamp(sig->expiredate)); + rc=G10ERR_GENERAL; /* need a better error here? */ + } + else if(sig->expiredate) + log_info(_("Signature expires %s\n"),asctimestamp(sig->expiredate)); + + if(opt.verbose) + log_info(_("%s signature, digest algorithm %s\n"), + sig->sig_class==0x00?_("binary"): + sig->sig_class==0x01?_("textmode"):_("unknown"), + digest_algo_to_string(sig->digest_algo)); + + if( rc ) + g10_errors_seen = 1; + if( opt.batch && rc ) + g10_exit(1); + } + else { + char buf[50]; + sprintf(buf, "%08lX%08lX %d %d %02x %lu %d", + (ulong)sig->keyid[0], (ulong)sig->keyid[1], + sig->pubkey_algo, sig->digest_algo, + sig->sig_class, (ulong)sig->timestamp, rc ); + write_status_text( STATUS_ERRSIG, buf ); + if( rc == G10ERR_NO_PUBKEY ) { + buf[16] = 0; + write_status_text( STATUS_NO_PUBKEY, buf ); + } + if( rc != G10ERR_NOT_PROCESSED ) + log_error(_("Can't check signature: %s\n"), g10_errstr(rc) ); + } + return rc; +} + + +/**************** + * Process the tree which starts at node + */ +static void +proc_tree( CTX c, KBNODE node ) +{ + KBNODE n1; + int rc; + + if( opt.list_packets || opt.list_only ) + return; + + /* we must skip our special plaintext marker packets here becuase + they may be the root packet. These packets are only used in + addionla checks and skipping them here doesn't matter */ + while ( node + && node->pkt->pkttype == PKT_GPG_CONTROL + && node->pkt->pkt.gpg_control->control + == CTRLPKT_PLAINTEXT_MARK ) { + node = node->next; + } + if (!node) + return; + + c->trustletter = ' '; + if( node->pkt->pkttype == PKT_PUBLIC_KEY + || node->pkt->pkttype == PKT_PUBLIC_SUBKEY ) { + merge_keys_and_selfsig( node ); + list_node( c, node ); + } + else if( node->pkt->pkttype == PKT_SECRET_KEY ) { + merge_keys_and_selfsig( node ); + list_node( c, node ); + } + else if( node->pkt->pkttype == PKT_ONEPASS_SIG ) { + /* check all signatures */ + if( !c->have_data ) { + free_md_filter_context( &c->mfx ); + /* prepare to create all requested message digests */ + c->mfx.md = md_open(0, 0); + + /* fixme: why looking for the signature packet and not the + one-pass packet? */ + for( n1 = node; (n1 = find_next_kbnode(n1, PKT_SIGNATURE )); ) { + md_enable( c->mfx.md, n1->pkt->pkt.signature->digest_algo); + } + /* ask for file and hash it */ + if( c->sigs_only ) { + rc = hash_datafiles( c->mfx.md, NULL, + c->signed_data, c->sigfilename, + n1? (n1->pkt->pkt.onepass_sig->sig_class == 0x01):0 ); + } + else { + rc = ask_for_detached_datafile( c->mfx.md, c->mfx.md2, + iobuf_get_real_fname(c->iobuf), + n1? (n1->pkt->pkt.onepass_sig->sig_class == 0x01):0 ); + } + if( rc ) { + log_error("can't hash datafile: %s\n", g10_errstr(rc)); + return; + } + } + else if ( c->signed_data ) { + log_error (_("not a detached signature\n") ); + return; + } + + for( n1 = node; (n1 = find_next_kbnode(n1, PKT_SIGNATURE )); ) + check_sig_and_print( c, n1 ); + } + else if( node->pkt->pkttype == PKT_GPG_CONTROL + && node->pkt->pkt.gpg_control->control + == CTRLPKT_CLEARSIGN_START ) { + /* clear text signed message */ + if( !c->have_data ) { + log_error("cleartext signature without data\n" ); + return; + } + else if ( c->signed_data ) { + log_error (_("not a detached signature\n") ); + return; + } + + for( n1 = node; (n1 = find_next_kbnode(n1, PKT_SIGNATURE )); ) + check_sig_and_print( c, n1 ); + } + else if( node->pkt->pkttype == PKT_SIGNATURE ) { + PKT_signature *sig = node->pkt->pkt.signature; + int multiple_ok=1; + + n1=find_next_kbnode(node, PKT_SIGNATURE); + if(n1) + { + byte class=sig->sig_class; + byte hash=sig->digest_algo; + + for(; n1; (n1 = find_next_kbnode(n1, PKT_SIGNATURE))) + { + /* We can't currently handle multiple signatures of + different classes or digests (we'd pretty much have + to run a different hash context for each), but if + they are all the same, make an exception. */ + if(n1->pkt->pkt.signature->sig_class!=class + || n1->pkt->pkt.signature->digest_algo!=hash) + { + multiple_ok=0; + log_info(_("WARNING: multiple signatures detected. " + "Only the first will be checked.\n")); + break; + } + } + } + + if( sig->sig_class != 0x00 && sig->sig_class != 0x01 ) + log_info(_("standalone signature of class 0x%02x\n"), + sig->sig_class); + else if( !c->have_data ) { + /* detached signature */ + free_md_filter_context( &c->mfx ); + c->mfx.md = md_open(sig->digest_algo, 0); + if( !opt.pgp2_workarounds ) + ; + else if( sig->digest_algo == DIGEST_ALGO_MD5 + && is_RSA( sig->pubkey_algo ) ) { + /* enable a workaround for a pgp2 bug */ + c->mfx.md2 = md_open( DIGEST_ALGO_MD5, 0 ); + } + else if( sig->digest_algo == DIGEST_ALGO_SHA1 + && sig->pubkey_algo == PUBKEY_ALGO_DSA + && sig->sig_class == 0x01 ) { + /* enable the workaround also for pgp5 when the detached + * signature has been created in textmode */ + c->mfx.md2 = md_open( sig->digest_algo, 0 ); + } +#if 0 /* workaround disabled */ + /* Here we have another hack to work around a pgp 2 bug + * It works by not using the textmode for detached signatures; + * this will let the first signature check (on md) fail + * but the second one (on md2) which adds an extra CR should + * then produce the "correct" hash. This is very, very ugly + * hack but it may help in some cases (and break others) + */ + /* c->mfx.md2? 0 :(sig->sig_class == 0x01) */ +#endif + if ( DBG_HASHING ) { + md_start_debug( c->mfx.md, "verify" ); + if ( c->mfx.md2 ) + md_start_debug( c->mfx.md2, "verify2" ); + } + if( c->sigs_only ) { + rc = hash_datafiles( c->mfx.md, c->mfx.md2, + c->signed_data, c->sigfilename, + (sig->sig_class == 0x01) ); + } + else { + rc = ask_for_detached_datafile( c->mfx.md, c->mfx.md2, + iobuf_get_real_fname(c->iobuf), + (sig->sig_class == 0x01) ); + } + if( rc ) { + log_error("can't hash datafile: %s\n", g10_errstr(rc)); + return; + } + } + else if ( c->signed_data ) { + log_error (_("not a detached signature\n") ); + return; + } + else if ( c->pipemode.op == 'B' ) + ; /* this is a detached signature trough the pipemode handler */ + else if (!opt.quiet) + log_info(_("old style (PGP 2.x) signature\n")); + + if(multiple_ok) + for( n1 = node; n1; (n1 = find_next_kbnode(n1, PKT_SIGNATURE )) ) + check_sig_and_print( c, n1 ); + else + check_sig_and_print( c, node ); + } + else { + dump_kbnode (c->list); + log_error(_("invalid root packet detected in proc_tree()\n")); + dump_kbnode (node); + } +} diff --git a/g10/mdfilter.c b/g10/mdfilter.c new file mode 100644 index 0000000..3605db8 --- /dev/null +++ b/g10/mdfilter.c @@ -0,0 +1,77 @@ +/* mdfilter.c - filter data and calculate a message digest + * Copyright (C) 1998, 1999, 2000, 2001 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <assert.h> + +#include "errors.h" +#include "iobuf.h" +#include "memory.h" +#include "util.h" +#include "filter.h" + + + +/**************** + * This filter is used to collect a message digest + */ +int +md_filter( void *opaque, int control, + IOBUF a, byte *buf, size_t *ret_len) +{ + size_t size = *ret_len; + md_filter_context_t *mfx = opaque; + int i, rc=0; + + if( control == IOBUFCTRL_UNDERFLOW ) { + if( mfx->maxbuf_size && size > mfx->maxbuf_size ) + size = mfx->maxbuf_size; + i = iobuf_read( a, buf, size ); + if( i == -1 ) i = 0; + if( i ) { + md_write(mfx->md, buf, i ); + if( mfx->md2 ) + md_write(mfx->md2, buf, i ); + } + else + rc = -1; /* eof */ + *ret_len = i; + } + else if( control == IOBUFCTRL_DESC ) + *(char**)buf = "md_filter"; + return rc; +} + + +void +free_md_filter_context( md_filter_context_t *mfx ) +{ + md_close(mfx->md); + md_close(mfx->md2); + mfx->md = NULL; + mfx->md2 = NULL; + mfx->maxbuf_size = 0; +} + diff --git a/g10/misc.c b/g10/misc.c new file mode 100644 index 0000000..fbe4492 --- /dev/null +++ b/g10/misc.c @@ -0,0 +1,1284 @@ +/* misc.c - miscellaneous functions + * Copyright (C) 1998, 1999, 2000, 2001, 2002, 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 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> +#if defined(__linux__) && defined(__alpha__) && __GLIBC__ < 2 +#include <asm/sysinfo.h> +#include <asm/unistd.h> +#endif +#ifdef HAVE_SETRLIMIT +#include <time.h> +#include <sys/time.h> +#include <sys/resource.h> +#endif +#ifdef ENABLE_SELINUX_HACKS +#include <sys/stat.h> +#endif +#ifdef _WIN32 +#include <time.h> +#include <process.h> +#include <windows.h> +#include <shlobj.h> +#ifndef CSIDL_APPDATA +#define CSIDL_APPDATA 0x001a +#endif +#ifndef CSIDL_LOCAL_APPDATA +#define CSIDL_LOCAL_APPDATA 0x001c +#endif +#ifndef CSIDL_FLAG_CREATE +#define CSIDL_FLAG_CREATE 0x8000 +#endif +#include "errors.h" +#include "dynload.h" +#endif /*_WIN32*/ + +#include "util.h" +#include "main.h" +#include "photoid.h" +#include "options.h" +#include "i18n.h" +#include "cardglue.h" + + + + +#ifdef ENABLE_SELINUX_HACKS +/* A object and a global variable to keep track of files marked as + secured. */ +struct secured_file_item +{ + struct secured_file_item *next; + ino_t ino; + dev_t dev; +}; +static struct secured_file_item *secured_files; +#endif /*ENABLE_SELINUX_HACKS*/ + + + +#if defined(__linux__) && defined(__alpha__) && __GLIBC__ < 2 +static int +setsysinfo(unsigned long op, void *buffer, unsigned long size, + int *start, void *arg, unsigned long flag) +{ + return syscall(__NR_osf_setsysinfo, op, buffer, size, start, arg, flag); +} + +void +trap_unaligned(void) +{ + unsigned int buf[2]; + + buf[0] = SSIN_UACPROC; + buf[1] = UAC_SIGBUS | UAC_NOPRINT; + setsysinfo(SSI_NVPAIRS, buf, 1, 0, 0, 0); +} +#else +void +trap_unaligned(void) +{ /* dummy */ +} +#endif + + +int +disable_core_dumps() +{ +#ifdef HAVE_DOSISH_SYSTEM + return 0; +#else +#ifdef HAVE_SETRLIMIT + struct rlimit limit; + + limit.rlim_cur = 0; + limit.rlim_max = 0; + if( !setrlimit( RLIMIT_CORE, &limit ) ) + return 0; + if( errno != EINVAL && errno != ENOSYS ) + log_fatal(_("can't disable core dumps: %s\n"), strerror(errno) ); +#endif + return 1; +#endif +} + + +/* For the sake of SELinux we want to restrict access through gpg to + certain files we keep under our own control. This function + registers such a file and is_secured_file may then be used to + check whether a file has ben registered as secured. */ +void +register_secured_file (const char *fname) +{ +#ifdef ENABLE_SELINUX_HACKS + struct stat buf; + struct secured_file_item *sf; + + /* Note that we stop immediatley if something goes wrong here. */ + if (stat (fname, &buf)) + log_fatal (_("fstat of `%s' failed in %s: %s\n"), fname, + "register_secured_file", strerror (errno)); +/* log_debug ("registering `%s' i=%lu.%lu\n", fname, */ +/* (unsigned long)buf.st_dev, (unsigned long)buf.st_ino); */ + for (sf=secured_files; sf; sf = sf->next) + { + if (sf->ino == buf.st_ino && sf->dev == buf.st_dev) + return; /* Already registered. */ + } + + sf = xmalloc (sizeof *sf); + sf->ino = buf.st_ino; + sf->dev = buf.st_dev; + sf->next = secured_files; + secured_files = sf; +#endif /*ENABLE_SELINUX_HACKS*/ +} + +/* Remove a file registerd as secure. */ +void +unregister_secured_file (const char *fname) +{ +#ifdef ENABLE_SELINUX_HACKS + struct stat buf; + struct secured_file_item *sf, *sfprev; + + if (stat (fname, &buf)) + { + log_error (_("fstat of `%s' failed in %s: %s\n"), fname, + "unregister_secured_file", strerror (errno)); + return; + } +/* log_debug ("unregistering `%s' i=%lu.%lu\n", fname, */ +/* (unsigned long)buf.st_dev, (unsigned long)buf.st_ino); */ + for (sfprev=NULL,sf=secured_files; sf; sfprev=sf, sf = sf->next) + { + if (sf->ino == buf.st_ino && sf->dev == buf.st_dev) + { + if (sfprev) + sfprev->next = sf->next; + else + secured_files = sf->next; + xfree (sf); + return; + } + } +#endif /*ENABLE_SELINUX_HACKS*/ +} + +/* Return true if FD is corresponds to a secured file. Using -1 for + FS is allowed and will return false. */ +int +is_secured_file (int fd) +{ +#ifdef ENABLE_SELINUX_HACKS + struct stat buf; + struct secured_file_item *sf; + + if (fd == -1) + return 0; /* No file descriptor so it can't be secured either. */ + + /* Note that we print out a error here and claim that a file is + secure if something went wrong. */ + if (fstat (fd, &buf)) + { + log_error (_("fstat(%d) failed in %s: %s\n"), fd, + "is_secured_file", strerror (errno)); + return 1; + } +/* log_debug ("is_secured_file (%d) i=%lu.%lu\n", fd, */ +/* (unsigned long)buf.st_dev, (unsigned long)buf.st_ino); */ + for (sf=secured_files; sf; sf = sf->next) + { + if (sf->ino == buf.st_ino && sf->dev == buf.st_dev) + return 1; /* Yes. */ + } +#endif /*ENABLE_SELINUX_HACKS*/ + return 0; /* No. */ +} + +/* Return true if FNAME is corresponds to a secured file. Using NULL, + "" or "-" for FS is allowed and will return false. This function is + used before creating a file, thus it won't fail if the file does + not exist. */ +int +is_secured_filename (const char *fname) +{ +#ifdef ENABLE_SELINUX_HACKS + struct stat buf; + struct secured_file_item *sf; + + if (iobuf_is_pipe_filename (fname) || !*fname) + return 0; + + /* Note that we print out a error here and claim that a file is + secure if something went wrong. */ + if (stat (fname, &buf)) + { + if (errno == ENOENT || errno == EPERM || errno == EACCES) + return 0; + log_error (_("fstat of `%s' failed in %s: %s\n"), fname, + "is_secured_filename", strerror (errno)); + return 1; + } +/* log_debug ("is_secured_filename (%s) i=%lu.%lu\n", fname, */ +/* (unsigned long)buf.st_dev, (unsigned long)buf.st_ino); */ + for (sf=secured_files; sf; sf = sf->next) + { + if (sf->ino == buf.st_ino && sf->dev == buf.st_dev) + return 1; /* Yes. */ + } +#endif /*ENABLE_SELINUX_HACKS*/ + return 0; /* No. */ +} + + + +u16 +checksum_u16( unsigned n ) +{ + u16 a; + + a = (n >> 8) & 0xff; + a += n & 0xff; + return a; +} + + +u16 +checksum( byte *p, unsigned n ) +{ + u16 a; + + for(a=0; n; n-- ) + a += *p++; + return a; +} + +u16 +checksum_mpi( MPI a ) +{ + u16 csum; + byte *buffer; + unsigned nbytes; + unsigned nbits; + + buffer = mpi_get_buffer( a, &nbytes, NULL ); + nbits = mpi_get_nbits(a); + csum = checksum_u16( nbits ); + csum += checksum( buffer, nbytes ); + xfree( buffer ); + return csum; +} + +u32 +buffer_to_u32( const byte *buffer ) +{ + unsigned long a; + a = *buffer << 24; + a |= buffer[1] << 16; + a |= buffer[2] << 8; + a |= buffer[3]; + return a; +} + +void +print_pubkey_algo_note( int algo ) +{ + if(algo >= 100 && algo <= 110) + { + static int warn=0; + if(!warn) + { + warn=1; + log_info(_("WARNING: using experimental public key algorithm %s\n"), + pubkey_algo_to_string(algo)); + } + } +} + +void +print_cipher_algo_note( int algo ) +{ + if(algo >= 100 && algo <= 110) + { + static int warn=0; + if(!warn) + { + warn=1; + log_info(_("WARNING: using experimental cipher algorithm %s\n"), + cipher_algo_to_string(algo)); + } + } +} + +void +print_digest_algo_note( int algo ) +{ + if(algo >= 100 && algo <= 110) + { + static int warn=0; + if(!warn) + { + warn=1; + log_info(_("WARNING: using experimental digest algorithm %s\n"), + digest_algo_to_string(algo)); + } + } + else if(algo==DIGEST_ALGO_MD5) + log_info(_("WARNING: digest algorithm %s is deprecated\n"), + digest_algo_to_string(algo)); +} + +/* Return a string which is used as a kind of process ID */ +const byte * +get_session_marker( size_t *rlen ) +{ + static byte marker[SIZEOF_UNSIGNED_LONG*2]; + static int initialized; + + if ( !initialized ) { + volatile ulong aa, bb; /* we really want the uninitialized value */ + ulong a, b; + + initialized = 1; + /* also this marker is guessable it is not easy to use this + * for a faked control packet because an attacker does not + * have enough control about the time the verification does + * take place. Of course, we can add just more random but + * than we need the random generator even for verification + * tasks - which does not make sense. */ + a = aa ^ (ulong)getpid(); + b = bb ^ (ulong)time(NULL); + memcpy( marker, &a, SIZEOF_UNSIGNED_LONG ); + memcpy( marker+SIZEOF_UNSIGNED_LONG, &b, SIZEOF_UNSIGNED_LONG ); + } + *rlen = sizeof(marker); + return marker; +} + +/**************** + * Wrapper around the libgcrypt function with addional checks on + * openPGP contraints for the algo ID. + */ +int +openpgp_cipher_test_algo( int algo ) +{ + if( algo < 0 || algo > 110 ) + return G10ERR_CIPHER_ALGO; + return check_cipher_algo(algo); +} + +int +openpgp_pk_test_algo( int algo, unsigned int usage_flags ) +{ + if( algo < 0 || algo > 110 ) + return G10ERR_PUBKEY_ALGO; + return check_pubkey_algo2( algo, usage_flags ); +} + +int +openpgp_pk_algo_usage ( int algo ) +{ + int use = 0; + + /* they are hardwired in gpg 1.0 */ + switch ( algo ) { + case PUBKEY_ALGO_RSA: + use = PUBKEY_USAGE_CERT | PUBKEY_USAGE_SIG | PUBKEY_USAGE_ENC | PUBKEY_USAGE_AUTH; + break; + case PUBKEY_ALGO_RSA_E: + use = PUBKEY_USAGE_ENC; + break; + case PUBKEY_ALGO_RSA_S: + use = PUBKEY_USAGE_CERT | PUBKEY_USAGE_SIG; + break; + case PUBKEY_ALGO_ELGAMAL_E: + use = PUBKEY_USAGE_ENC; + break; + case PUBKEY_ALGO_DSA: + use = PUBKEY_USAGE_CERT | PUBKEY_USAGE_SIG | PUBKEY_USAGE_AUTH; + break; + default: + break; + } + return use; +} + +int +openpgp_md_test_algo( int algo ) +{ + if( algo < 0 || algo > 110 ) + return G10ERR_DIGEST_ALGO; + return check_digest_algo(algo); +} + +#ifdef USE_IDEA +/* Special warning for the IDEA cipher */ +void +idea_cipher_warn(int show) +{ + static int warned=0; + + if(!warned || show) + { + log_info(_("the IDEA cipher plugin is not present\n")); + log_info(_("please see %s for more information\n"), + "http://www.gnupg.org/faq/why-not-idea.html"); + warned=1; + } +} +#endif + +static unsigned long get_signature_count(PKT_secret_key *sk) +{ +#ifdef ENABLE_CARD_SUPPORT + if(sk && sk->is_protected && sk->protect.s2k.mode==1002) + { + struct agent_card_info_s info; + if(agent_scd_getattr("SIG-COUNTER",&info)==0) + return info.sig_counter; + } +#endif + + /* How to do this without a card? */ + + return 0; +} + +/* Expand %-strings. Returns a string which must be xfreed. Returns + NULL if the string cannot be expanded (too large). */ +char * +pct_expando(const char *string,struct expando_args *args) +{ + const char *ch=string; + int idx=0,maxlen=0,done=0; + u32 pk_keyid[2]={0,0},sk_keyid[2]={0,0}; + char *ret=NULL; + + if(args->pk) + keyid_from_pk(args->pk,pk_keyid); + + if(args->sk) + keyid_from_sk(args->sk,sk_keyid); + + /* This is used so that %k works in photoid command strings in + --list-secret-keys (which of course has a sk, but no pk). */ + if(!args->pk && args->sk) + keyid_from_sk(args->sk,pk_keyid); + + while(*ch!='\0') + { + char *str=NULL; + + if(!done) + { + /* 8192 is way bigger than we'll need here */ + if(maxlen>=8192) + goto fail; + + maxlen+=1024; + ret=xrealloc(ret,maxlen); + } + + done=0; + + if(*ch=='%') + { + switch(*(ch+1)) + { + case 's': /* short key id */ + if(idx+8<maxlen) + { + sprintf(&ret[idx],"%08lX",(ulong)sk_keyid[1]); + idx+=8; + done=1; + } + break; + + case 'S': /* long key id */ + if(idx+16<maxlen) + { + sprintf(&ret[idx],"%08lX%08lX", + (ulong)sk_keyid[0],(ulong)sk_keyid[1]); + idx+=16; + done=1; + } + break; + + case 'k': /* short key id */ + if(idx+8<maxlen) + { + sprintf(&ret[idx],"%08lX",(ulong)pk_keyid[1]); + idx+=8; + done=1; + } + break; + + case 'K': /* long key id */ + if(idx+16<maxlen) + { + sprintf(&ret[idx],"%08lX%08lX", + (ulong)pk_keyid[0],(ulong)pk_keyid[1]); + idx+=16; + done=1; + } + break; + + case 'c': /* signature count from card, if any. */ + if(idx+10<maxlen) + { + sprintf(&ret[idx],"%lu",get_signature_count(args->sk)); + idx+=strlen(&ret[idx]); + done=1; + } + break; + + case 'p': /* primary pk fingerprint of a sk */ + case 'f': /* pk fingerprint */ + case 'g': /* sk fingerprint */ + { + byte array[MAX_FINGERPRINT_LEN]; + size_t len; + int i; + + if((*(ch+1))=='p' && args->sk) + { + if(args->sk->is_primary) + fingerprint_from_sk(args->sk,array,&len); + else if(args->sk->main_keyid[0] || args->sk->main_keyid[1]) + { + PKT_public_key *pk= + xmalloc_clear(sizeof(PKT_public_key)); + + if(get_pubkey_fast(pk,args->sk->main_keyid)==0) + fingerprint_from_pk(pk,array,&len); + else + memset(array,0,(len=MAX_FINGERPRINT_LEN)); + free_public_key(pk); + } + else + memset(array,0,(len=MAX_FINGERPRINT_LEN)); + } + else if((*(ch+1))=='f' && args->pk) + fingerprint_from_pk(args->pk,array,&len); + else if((*(ch+1))=='g' && args->sk) + fingerprint_from_sk(args->sk,array,&len); + else + memset(array,0,(len=MAX_FINGERPRINT_LEN)); + + if(idx+(len*2)<maxlen) + { + for(i=0;i<len;i++) + { + sprintf(&ret[idx],"%02X",array[i]); + idx+=2; + } + done=1; + } + } + break; + + case 't': /* e.g. "jpg" */ + str=image_type_to_string(args->imagetype,0); + /* fall through */ + + case 'T': /* e.g. "image/jpeg" */ + if(str==NULL) + str=image_type_to_string(args->imagetype,2); + + if(idx+strlen(str)<maxlen) + { + strcpy(&ret[idx],str); + idx+=strlen(str); + done=1; + } + break; + + case '%': + if(idx+1<maxlen) + { + ret[idx++]='%'; + ret[idx]='\0'; + done=1; + } + break; + + /* Any unknown %-keys (like %i, %o, %I, and %O) are + passed through for later expansion. Note this also + handles the case where the last character in the + string is a '%' - the terminating \0 will end up here + and properly terminate the string. */ + default: + if(idx+2<maxlen) + { + ret[idx++]='%'; + ret[idx++]=*(ch+1); + ret[idx]='\0'; + done=1; + } + break; + } + + if(done) + ch++; + } + else + { + if(idx+1<maxlen) + { + ret[idx++]=*ch; + ret[idx]='\0'; + done=1; + } + } + + if(done) + ch++; + } + + return ret; + + fail: + xfree(ret); + return NULL; +} + +void +deprecated_warning(const char *configname,unsigned int configlineno, + const char *option,const char *repl1,const char *repl2) +{ + if(configname) + { + if(strncmp("--",option,2)==0) + option+=2; + + if(strncmp("--",repl1,2)==0) + repl1+=2; + + log_info(_("%s:%d: deprecated option \"%s\"\n"), + configname,configlineno,option); + } + else + log_info(_("WARNING: \"%s\" is a deprecated option\n"),option); + + log_info(_("please use \"%s%s\" instead\n"),repl1,repl2); +} + + +void +deprecated_command (const char *name) +{ + log_info(_("WARNING: \"%s\" is a deprecated command - do not use it\n"), + name); +} + + +const char * +compress_algo_to_string(int algo) +{ + const char *s=NULL; + + switch(algo) + { + case COMPRESS_ALGO_NONE: + s=_("Uncompressed"); + break; + + case COMPRESS_ALGO_ZIP: + s="ZIP"; + break; + + case COMPRESS_ALGO_ZLIB: + s="ZLIB"; + break; + +#ifdef HAVE_BZIP2 + case COMPRESS_ALGO_BZIP2: + s="BZIP2"; + break; +#endif + } + + return s; +} + +int +string_to_compress_algo(const char *string) +{ + /* NOTE TO TRANSLATOR: See doc/TRANSLATE about this string. */ + if(match_multistr(_("uncompressed|none"),string)) + return 0; + else if(ascii_strcasecmp(string,"uncompressed")==0) + return 0; + else if(ascii_strcasecmp(string,"none")==0) + return 0; + else if(ascii_strcasecmp(string,"zip")==0) + return 1; + else if(ascii_strcasecmp(string,"zlib")==0) + return 2; +#ifdef HAVE_BZIP2 + else if(ascii_strcasecmp(string,"bzip2")==0) + return 3; +#endif + else if(ascii_strcasecmp(string,"z0")==0) + return 0; + else if(ascii_strcasecmp(string,"z1")==0) + return 1; + else if(ascii_strcasecmp(string,"z2")==0) + return 2; +#ifdef HAVE_BZIP2 + else if(ascii_strcasecmp(string,"z3")==0) + return 3; +#endif + else + return -1; +} + +int +check_compress_algo(int algo) +{ +#ifdef HAVE_BZIP2 + if(algo>=0 && algo<=3) + return 0; +#else + if(algo>=0 && algo<=2) + return 0; +#endif + + return G10ERR_COMPR_ALGO; +} + +int +default_cipher_algo(void) +{ + if(opt.def_cipher_algo) + return opt.def_cipher_algo; + else if(opt.personal_cipher_prefs) + return opt.personal_cipher_prefs[0].value; + else + return opt.s2k_cipher_algo; +} + +/* There is no default_digest_algo function, but see + sign.c:hash_for() */ + +int +default_compress_algo(void) +{ + if(opt.compress_algo!=-1) + return opt.compress_algo; + else if(opt.personal_compress_prefs) + return opt.personal_compress_prefs[0].value; + else + return DEFAULT_COMPRESS_ALGO; +} + +const char * +compliance_option_string(void) +{ + switch(opt.compliance) + { + case CO_RFC2440: + return "--openpgp"; + case CO_PGP2: + return "--pgp2"; + case CO_PGP6: + return "--pgp6"; + case CO_PGP7: + return "--pgp7"; + case CO_PGP8: + return "--pgp8"; + default: + return "???"; + } +} + +static const char * +compliance_string(void) +{ + switch(opt.compliance) + { + case CO_RFC2440: + return "OpenPGP"; + case CO_PGP2: + return "PGP 2.x"; + case CO_PGP6: + return "PGP 6.x"; + case CO_PGP7: + return "PGP 7.x"; + case CO_PGP8: + return "PGP 8.x"; + default: + return "???"; + } +} + +void +compliance_failure(void) +{ + log_info(_("this message may not be usable by %s\n"),compliance_string()); + opt.compliance=CO_GNUPG; +} + +/* Break a string into successive option pieces. Accepts single word + options and key=value argument options. */ +char * +optsep(char **stringp) +{ + char *tok,*end; + + tok=*stringp; + if(tok) + { + end=strpbrk(tok," ,="); + if(end) + { + int sawequals=0; + char *ptr=end; + + /* what we need to do now is scan along starting with *end, + If the next character we see (ignoring spaces) is an = + sign, then there is an argument. */ + + while(*ptr) + { + if(*ptr=='=') + sawequals=1; + else if(*ptr!=' ') + break; + ptr++; + } + + /* There is an argument, so grab that too. At this point, + ptr points to the first character of the argument. */ + if(sawequals) + { + /* Is it a quoted argument? */ + if(*ptr=='"') + { + ptr++; + end=strchr(ptr,'"'); + if(end) + end++; + } + else + end=strpbrk(ptr," ,"); + } + + if(end && *end) + { + *end='\0'; + *stringp=end+1; + } + else + *stringp=NULL; + } + else + *stringp=NULL; + } + + return tok; +} + +/* Breaks an option value into key and value. Returns NULL if there + is no value. Note that "string" is modified to remove the =value + part. */ +char * +argsplit(char *string) +{ + char *equals,*arg=NULL; + + equals=strchr(string,'='); + if(equals) + { + char *quote,*space; + + *equals='\0'; + arg=equals+1; + + /* Quoted arg? */ + quote=strchr(arg,'"'); + if(quote) + { + arg=quote+1; + + quote=strchr(arg,'"'); + if(quote) + *quote='\0'; + } + else + { + size_t spaces; + + /* Trim leading spaces off of the arg */ + spaces=strspn(arg," "); + arg+=spaces; + } + + /* Trim tailing spaces off of the tag */ + space=strchr(string,' '); + if(space) + *space='\0'; + } + + return arg; +} + +/* Return the length of the initial token, leaving off any + argument. */ +static size_t +optlen(const char *s) +{ + char *end=strpbrk(s," ="); + + if(end) + return end-s; + else + return strlen(s); +} + +int +parse_options(char *str,unsigned int *options, + struct parse_options *opts,int noisy) +{ + char *tok; + + if (str && !strcmp (str, "help")) + { + int i,maxlen=0; + + /* Figure out the longest option name so we can line these up + neatly. */ + for(i=0;opts[i].name;i++) + if(opts[i].help && maxlen<strlen(opts[i].name)) + maxlen=strlen(opts[i].name); + + for(i=0;opts[i].name;i++) + if(opts[i].help) + printf("%s%*s%s\n",opts[i].name, + maxlen+2-(int)strlen(opts[i].name),"",_(opts[i].help)); + + g10_exit(0); + } + + while((tok=optsep(&str))) + { + int i,rev=0; + char *otok=tok; + + if(tok[0]=='\0') + continue; + + if(ascii_strncasecmp("no-",tok,3)==0) + { + rev=1; + tok+=3; + } + + for(i=0;opts[i].name;i++) + { + size_t toklen=optlen(tok); + + if(ascii_strncasecmp(opts[i].name,tok,toklen)==0) + { + /* We have a match, but it might be incomplete */ + if(toklen!=strlen(opts[i].name)) + { + int j; + + for(j=i+1;opts[j].name;j++) + { + if(ascii_strncasecmp(opts[j].name,tok,toklen)==0) + { + if(noisy) + log_info(_("ambiguous option `%s'\n"),otok); + return 0; + } + } + } + + if(rev) + { + *options&=~opts[i].bit; + if(opts[i].value) + *opts[i].value=NULL; + } + else + { + *options|=opts[i].bit; + if(opts[i].value) + *opts[i].value=argsplit(tok); + } + break; + } + } + + if(!opts[i].name) + { + if(noisy) + log_info(_("unknown option `%s'\n"),otok); + return 0; + } + } + + return 1; +} + + +/* Return a new malloced string by unescaping the string S. Escaping + is percent escaping and '+'/space mapping. A binary nul will + silently be replaced by a 0xFF. */ +char * +unescape_percent_string (const unsigned char *s) +{ + char *buffer, *d; + + buffer = d = xmalloc (strlen (s)+1); + while (*s) + { + if (*s == '%' && s[1] && s[2]) + { + s++; + *d = xtoi_2 (s); + if (!*d) + *d = '\xff'; + d++; + s += 2; + } + else if (*s == '+') + { + *d++ = ' '; + s++; + } + else + *d++ = *s++; + } + *d = 0; + return buffer; +} + + +int +has_invalid_email_chars (const char *s) +{ + int at_seen=0; + const char *valid_chars= + "01234567890_-.abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; + + for ( ; *s; s++ ) + { + if ( *s & 0x80 ) + return 1; + if ( *s == '@' ) + at_seen=1; + else if ( !at_seen && !( !!strchr( valid_chars, *s ) || *s == '+' ) ) + return 1; + else if ( at_seen && !strchr( valid_chars, *s ) ) + return 1; + } + return 0; +} + + +/* Check whether NAME represents a valid mailbox according to + RFC822. Returns true if so. */ +int +is_valid_mailbox (const char *name) +{ + return !( !name + || !*name + || has_invalid_email_chars (name) + || string_count_chr (name,'@') != 1 + || *name == '@' + || name[strlen(name)-1] == '@' + || name[strlen(name)-1] == '.' + || strstr (name, "..") ); +} + + +/* This is a helper function to load a Windows function from either of + one DLLs. */ +#ifdef HAVE_W32_SYSTEM +static HRESULT +w32_shgetfolderpath (HWND a, int b, HANDLE c, DWORD d, LPSTR e) +{ + static int initialized; + static HRESULT (WINAPI * func)(HWND,int,HANDLE,DWORD,LPSTR); + + if (!initialized) + { + static char *dllnames[] = { "shell32.dll", "shfolder.dll", NULL }; + void *handle; + int i; + + initialized = 1; + + for (i=0, handle = NULL; !handle && dllnames[i]; i++) + { + handle = dlopen (dllnames[i], RTLD_LAZY); + if (handle) + { + func = dlsym (handle, "SHGetFolderPathA"); + if (!func) + { + dlclose (handle); + handle = NULL; + } + } + } + } + + if (func) + return func (a,b,c,d,e); + else + return -1; +} +#endif /*HAVE_W32_SYSTEM*/ + + +/* Set up the default home directory. The usual --homedir option + should be parsed later. */ +char * +default_homedir (void) +{ + char *dir; + + dir = getenv("GNUPGHOME"); +#ifdef HAVE_W32_SYSTEM + if (!dir || !*dir) + dir = read_w32_registry_string (NULL, "Software\\GNU\\GnuPG", "HomeDir"); + if (!dir || !*dir) + { + char path[MAX_PATH]; + + /* It might be better to use LOCAL_APPDATA because this is + defined as "non roaming" and thus more likely to be kept + locally. For private keys this is desired. However, given + that many users copy private keys anyway forth and back, + using a system roaming serives might be better than to let + them do it manually. A security conscious user will anyway + use the registry entry to have better control. */ + if (w32_shgetfolderpath (NULL, CSIDL_APPDATA|CSIDL_FLAG_CREATE, + NULL, 0, path) >= 0) + { + char *tmp = xmalloc (strlen (path) + 6 +1); + strcpy (stpcpy (tmp, path), "\\gnupg"); + dir = tmp; + + /* Try to create the directory if it does not yet + exists. */ + if (access (dir, F_OK)) + CreateDirectory (dir, NULL); + } + } +#endif /*HAVE_W32_SYSTEM*/ + if (!dir || !*dir) + dir = GNUPG_HOMEDIR; + + return dir; +} + + +/* Return the name of the libexec directory. The name is allocated in + a static area on the first use. This function won't fail. */ +const char * +get_libexecdir (void) +{ +#ifdef HAVE_W32_SYSTEM + static int got_dir; + static char dir[MAX_PATH+5]; + + if (!got_dir) + { + char *p; + + if ( !GetModuleFileName ( NULL, dir, MAX_PATH) ) + { + log_debug ("GetModuleFileName failed: %s\n", w32_strerror (0)); + *dir = 0; + } + got_dir = 1; + p = strrchr (dir, DIRSEP_C); + if (p) + *p = 0; + else + { + log_debug ("bad filename `%s' returned for this process\n", dir); + *dir = 0; + } + } + + if (*dir) + return dir; + /* Fallback to the hardwired value. */ +#endif /*HAVE_W32_SYSTEM*/ + + return GNUPG_LIBEXECDIR; +} + +/* Similar to access(2), but uses PATH to find the file. */ +int +path_access(const char *file,int mode) +{ + char *envpath; + int ret=-1; + + envpath=getenv("PATH"); + + if(!envpath +#ifdef HAVE_DRIVE_LETTERS + || (((file[0]>='A' && file[0]<='Z') + || (file[0]>='a' && file[0]<='z')) + && file[1]==':') +#else + || file[0]=='/' +#endif + ) + return access(file,mode); + else + { + /* At least as large as, but most often larger than we need. */ + char *buffer=xmalloc(strlen(envpath)+1+strlen(file)+1); + char *split,*item,*path=xstrdup(envpath); + + split=path; + + while((item=strsep(&split,PATHSEP_S))) + { + strcpy(buffer,item); + strcat(buffer,"/"); + strcat(buffer,file); + ret=access(buffer,mode); + if(ret==0) + break; + } + + xfree(path); + xfree(buffer); + } + + return ret; +} diff --git a/g10/openfile.c b/g10/openfile.c new file mode 100644 index 0000000..c106cdb --- /dev/null +++ b/g10/openfile.c @@ -0,0 +1,430 @@ +/* openfile.c + * Copyright (C) 1998, 1999, 2000, 2001, 2002, 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 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> +#include "util.h" +#include "memory.h" +#include "ttyio.h" +#include "options.h" +#include "main.h" +#include "status.h" +#include "i18n.h" + +#ifdef USE_ONLY_8DOT3 +#define SKELEXT ".skl" +#else +#define SKELEXT EXTSEP_S "skel" +#endif + +#if defined (HAVE_DRIVE_LETTERS) || defined (__riscos__) +#define CMP_FILENAME(a,b) ascii_strcasecmp( (a), (b) ) +#else +#define CMP_FILENAME(a,b) strcmp( (a), (b) ) +#endif + +#ifdef MKDIR_TAKES_ONE_ARG +#undef mkdir +#define mkdir(a,b) mkdir(a) +#endif + +/* FIXME: Implement opt.interactive. */ + +/**************** + * Check whether FNAME exists and ask if it's okay to overwrite an + * existing one. + * Returns: True: it's okay to overwrite or the file does not exist + * False: Do not overwrite + */ +int +overwrite_filep( const char *fname ) +{ + if( iobuf_is_pipe_filename (fname) ) + return 1; /* Writing to stdout is always okay */ + + if( access( fname, F_OK ) ) + return 1; /* does not exist */ + +#ifndef HAVE_DOSISH_SYSTEM + if ( !strcmp ( fname, "/dev/null" ) ) + return 1; /* does not do any harm */ +#endif + + /* fixme: add some backup stuff in case of overwrite */ + if( opt.answer_yes ) + return 1; + if( opt.answer_no || opt.batch ) + return 0; /* do not overwrite */ + + tty_printf(_("File `%s' exists. "), fname); + if( cpr_enabled () ) + tty_printf ("\n"); + if( cpr_get_answer_is_yes("openfile.overwrite.okay", + _("Overwrite? (y/N) ")) ) + return 1; + return 0; +} + + +/**************** + * Strip know extensions from iname and return a newly allocated + * filename. Return NULL if we can't do that. + */ +char * +make_outfile_name( const char *iname ) +{ + size_t n; + + if ( iobuf_is_pipe_filename (iname) ) + return xstrdup("-"); + + n = strlen(iname); + if( n > 4 && ( !CMP_FILENAME(iname+n-4, EXTSEP_S "gpg") + || !CMP_FILENAME(iname+n-4, EXTSEP_S "pgp") + || !CMP_FILENAME(iname+n-4, EXTSEP_S "sig") + || !CMP_FILENAME(iname+n-4, EXTSEP_S "asc") ) ) { + char *buf = xstrdup( iname ); + buf[n-4] = 0; + return buf; + } + else if( n > 5 && !CMP_FILENAME(iname+n-5, EXTSEP_S "sign") ) { + char *buf = xstrdup( iname ); + buf[n-5] = 0; + return buf; + } + + log_info(_("%s: unknown suffix\n"), iname ); + return NULL; +} + + +/**************** + * Ask for a outputfilename and use the given one as default. + * Return NULL if no file has been given or it is not possible to + * ask the user. + */ +char * +ask_outfile_name( const char *name, size_t namelen ) +{ + size_t n; + const char *s; + char *prompt; + char *fname; + char *defname; + + if( opt.batch ) + return NULL; + + s = _("Enter new filename"); + + defname = name && namelen? make_printable_string( name, namelen, 0): NULL; + n = strlen(s) + (defname?strlen (defname):0) + 10; + prompt = xmalloc(n); + if( defname ) + sprintf(prompt, "%s [%s]: ", s, defname ); + else + sprintf(prompt, "%s: ", s ); + tty_enable_completion(NULL); + fname = cpr_get("openfile.askoutname", prompt ); + cpr_kill_prompt(); + tty_disable_completion(); + xfree(prompt); + if( !*fname ) { + xfree( fname ); fname = NULL; + fname = defname; defname = NULL; + } + xfree(defname); + if (fname) + trim_spaces (fname); + return fname; +} + + +/**************** + * Make an output filename for the inputfile INAME. + * Returns an IOBUF and an errorcode + * Mode 0 = use ".gpg" + * 1 = use ".asc" + * 2 = use ".sig" + */ +int +open_outfile( const char *iname, int mode, IOBUF *a ) +{ + int rc = 0; + + *a = NULL; + if( iobuf_is_pipe_filename (iname) && !opt.outfile ) { + *a = iobuf_create(NULL); + if( !*a ) { + log_error(_("can't open `%s': %s\n"), "[stdout]", strerror(errno) ); + rc = G10ERR_CREATE_FILE; + } + else if( opt.verbose ) + log_info(_("writing to stdout\n")); + } + else { + char *buf = NULL; + const char *name; + + if( opt.dry_run ) + name = "/dev/null"; + else if( opt.outfile ) + name = opt.outfile; + else { +#ifdef USE_ONLY_8DOT3 + if (opt.mangle_dos_filenames) + { + /* It is quite common for DOS system to have only one dot in a + * a filename So if we have something like this, we simple + * replace the suffix except in cases where the suffix is + * larger than 3 characters and not identlically to the new one. + * We should really map the filenames to 8.3 but this tends to + * be more complicated and is probaly a duty of the filesystem + */ + char *dot; + const char *newsfx = mode==1 ? ".asc" : + mode==2 ? ".sig" : ".gpg"; + + buf = xmalloc(strlen(iname)+4+1); + strcpy(buf,iname); + dot = strrchr(buf, '.' ); + if ( dot && dot > buf && dot[1] && strlen(dot) <= 4 + && CMP_FILENAME(newsfx, dot) + && !(strchr (dot, '/') || strchr (dot, '\\'))) + { + /* There is a dot, the dot is not the first character, + the suffix is not longer than 3, the suffix is not + equal to the new suffix and tehre is no path delimter + after the dot (e.g. foo.1/bar): Replace the + suffix. */ + strcpy (dot, newsfx ); + } + else if ( dot && !dot[1] ) /* Don't duplicate a trailing dot. */ + strcpy ( dot, newsfx+1 ); + else + strcat ( buf, newsfx ); /* Just append the new suffix. */ + } + if (!buf) +#endif /* USE_ONLY_8DOT3 */ + { + buf = xmalloc(strlen(iname)+4+1); + strcpy(stpcpy(buf,iname), mode==1 ? EXTSEP_S "asc" : + mode==2 ? EXTSEP_S "sig" : EXTSEP_S "gpg"); + } + name = buf; + } + + rc = 0; + while( !overwrite_filep (name) ) + { + char *tmp = ask_outfile_name (NULL, 0); + if ( !tmp || !*tmp ) + { + xfree (tmp); + rc = G10ERR_FILE_EXISTS; + break; + } + xfree (buf); + name = buf = tmp; + } + + if( !rc ) + { + if (is_secured_filename (name) ) + { + *a = NULL; + errno = EPERM; + } + else + *a = iobuf_create( name ); + if( !*a ) + { + log_error(_("can't create `%s': %s\n"), name, strerror(errno) ); + rc = G10ERR_CREATE_FILE; + } + else if( opt.verbose ) + log_info(_("writing to `%s'\n"), name ); + } + xfree(buf); + } + + if (*a) + iobuf_ioctl (*a,3,1,NULL); /* disable fd caching */ + + return rc; +} + + +/**************** + * Try to open a file without the extension ".sig" or ".asc" + * Return NULL if such a file is not available. + */ +IOBUF +open_sigfile( const char *iname, progress_filter_context_t *pfx ) +{ + IOBUF a = NULL; + size_t len; + + if( !iobuf_is_pipe_filename (iname) ) { + len = strlen(iname); + if( len > 4 && ( !strcmp(iname + len - 4, EXTSEP_S "sig") + || ( len > 5 && !strcmp(iname + len - 5, EXTSEP_S "sign") ) + || !strcmp(iname + len - 4, EXTSEP_S "asc")) ) { + char *buf; + buf = xstrdup(iname); + buf[len-(buf[len-1]=='n'?5:4)] = 0 ; + a = iobuf_open( buf ); + if (a && is_secured_file (iobuf_get_fd (a))) + { + iobuf_close (a); + a = NULL; + errno = EPERM; + } + if( a && opt.verbose ) + log_info(_("assuming signed data in `%s'\n"), buf ); + if (a && pfx) + handle_progress (pfx, a, buf); + xfree(buf); + } + } + return a; +} + +/**************** + * Copy the option file skeleton to the given directory. + */ +static void +copy_options_file( const char *destdir ) +{ + const char *datadir = GNUPG_DATADIR; + char *fname; + FILE *src, *dst; + int linefeeds=0; + int c; + mode_t oldmask; + int esc = 0; + int any_option = 0; + + if( opt.dry_run ) + return; + + fname = xmalloc( strlen(datadir) + strlen(destdir) + 15 ); + strcpy(stpcpy(fname, datadir), DIRSEP_S "options" SKELEXT ); + src = fopen( fname, "r" ); + if (src && is_secured_file (fileno (src))) + { + fclose (src); + src = NULL; + errno = EPERM; + } + if( !src ) { + log_error(_("can't open `%s': %s\n"), fname, strerror(errno) ); + xfree(fname); + return; + } + strcpy(stpcpy(fname, destdir), DIRSEP_S "gpg" EXTSEP_S "conf" ); + oldmask=umask(077); + if ( is_secured_filename (fname) ) + { + dst = NULL; + errno = EPERM; + } + else + dst = fopen( fname, "w" ); + umask(oldmask); + if( !dst ) { + log_error(_("can't create `%s': %s\n"), fname, strerror(errno) ); + fclose( src ); + xfree(fname); + return; + } + + while( (c=getc(src)) != EOF ) { + if( linefeeds < 3 ) { + if( c == '\n' ) + linefeeds++; + } + else { + putc( c, dst ); + if (c== '\n') + esc = 1; + else if (esc == 1) { + if (c == ' ' || c == '\t') + ; + else if (c == '#') + esc = 2; + else + any_option = 1; + } + } + } + fclose( dst ); + fclose( src ); + log_info(_("new configuration file `%s' created\n"), fname ); + if (any_option) + log_info (_("WARNING: options in `%s'" + " are not yet active during this run\n"), + fname); + xfree(fname); +} + + +void +try_make_homedir( const char *fname ) +{ + const char *defhome = GNUPG_HOMEDIR; + + /* Create the directory only if the supplied directory name + * is the same as the default one. This way we avoid to create + * arbitrary directories when a non-default homedirectory is used. + * To cope with HOME, we do compare only the suffix if we see that + * the default homedir does start with a tilde. + */ + if( opt.dry_run || opt.no_homedir_creation ) + return; + + if ( ( *defhome == '~' + && ( strlen(fname) >= strlen (defhome+1) + && !strcmp(fname+strlen(fname)-strlen(defhome+1), + defhome+1 ) )) + || ( *defhome != '~' + && !compare_filenames( fname, defhome ) ) + ) { + if( mkdir( fname, S_IRUSR|S_IWUSR|S_IXUSR ) ) + log_fatal( _("can't create directory `%s': %s\n"), + fname, strerror(errno) ); + else if( !opt.quiet ) + log_info( _("directory `%s' created\n"), fname ); + copy_options_file( fname ); +/* log_info(_("you have to start GnuPG again, " */ +/* "so it can read the new configuration file\n") ); */ +/* g10_exit(1); */ + } +} diff --git a/g10/options.h b/g10/options.h new file mode 100644 index 0000000..8f866e2 --- /dev/null +++ b/g10/options.h @@ -0,0 +1,335 @@ +/* options.h + * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, + * 2006 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ +#ifndef G10_OPTIONS_H +#define G10_OPTIONS_H + +#include <sys/types.h> +#include <types.h> +#include "main.h" +#include "packet.h" + +#ifndef EXTERN_UNLESS_MAIN_MODULE +/* Norcraft can't cope with common symbols */ +#if defined (__riscos__) && !defined (INCLUDED_BY_MAIN_MODULE) +#define EXTERN_UNLESS_MAIN_MODULE extern +#else +#define EXTERN_UNLESS_MAIN_MODULE +#endif +#endif + +EXTERN_UNLESS_MAIN_MODULE +struct +{ + int verbose; + int quiet; + unsigned debug; + int armor; + char *outfile; + off_t max_output; + int dry_run; + int list_only; + int textmode; + int expert; + const char *def_sig_expire; + int ask_sig_expire; + const char *def_cert_expire; + int ask_cert_expire; + int batch; /* run in batch mode */ + int answer_yes; /* answer yes on most questions */ + int answer_no; /* answer no on most questions */ + int check_sigs; /* check key signatures */ + int with_colons; + int with_key_data; + int with_fingerprint; /* opt --with-fingerprint active */ + int fingerprint; /* list fingerprints */ + int list_sigs; /* list signatures */ + int no_armor; + int list_packets; /* list-packets mode: 1=normal, 2=invoked by command*/ + int def_cipher_algo; + int force_v3_sigs; + int force_v4_certs; + int force_mdc; + int disable_mdc; + int def_digest_algo; + int cert_digest_algo; + int compress_algo; + int compress_level; + int bz2_compress_level; + int bz2_decompress_lowmem; + const char *def_secret_key; + char *def_recipient; + int def_recipient_self; + int def_cert_level; + int min_cert_level; + int ask_cert_level; + int no_version; + int marginals_needed; + int completes_needed; + int max_cert_depth; + const char *homedir; + + char *display; /* 5 options to be passed to the gpg-agent */ + char *ttyname; + char *ttytype; + char *lc_ctype; + char *lc_messages; + + int skip_verify; + int compress_keys; + int compress_sigs; + /* TM_CLASSIC must be zero to accomodate trustdbs generated before + we started storing the trust model inside the trustdb. */ + enum + { + TM_CLASSIC=0, TM_PGP=1, TM_EXTERNAL=2, TM_ALWAYS, TM_DIRECT, TM_AUTO + } trust_model; + int force_ownertrust; + enum + { + CO_GNUPG=0, CO_RFC2440, CO_RFC1991, CO_PGP2, CO_PGP6, CO_PGP7, CO_PGP8 + } compliance; + enum + { + KF_SHORT, KF_LONG, KF_0xSHORT, KF_0xLONG + } keyid_format; + int pgp2_workarounds; + int shm_coprocess; + const char *set_filename; + STRLIST comments; + int throw_keyid; + const char *photo_viewer; + int s2k_mode; + int s2k_digest_algo; + int s2k_cipher_algo; + unsigned char s2k_count; /* This is the encoded form, not the raw + count */ + int simple_sk_checksum; /* create the deprecated rfc2440 secret key + protection */ + int not_dash_escaped; + int escape_from; + int lock_once; + struct keyserver_spec + { + char *uri; + char *scheme; + char *auth; + char *host; + char *port; + char *path; + char *opaque; + STRLIST options; + struct + { + unsigned int direct_uri:1; + } flags; + struct keyserver_spec *next; + } *keyserver; + struct + { + unsigned int options; + unsigned int import_options; + unsigned int export_options; + STRLIST other; + } keyserver_options; + int exec_disable; + int exec_path_set; + unsigned int import_options; + unsigned int export_options; + unsigned int list_options; + unsigned int verify_options; + const char *def_preference_list; + const char *def_keyserver_url; + prefitem_t *personal_cipher_prefs; + prefitem_t *personal_digest_prefs; + prefitem_t *personal_compress_prefs; + int no_perm_warn; + int no_mdc_warn; + char *temp_dir; + int no_encrypt_to; + int interactive; + struct notation *sig_notations; + struct notation *cert_notations; + STRLIST sig_policy_url; + STRLIST cert_policy_url; + STRLIST sig_keyserver_url; + STRLIST cert_subpackets; + STRLIST sig_subpackets; + int allow_non_selfsigned_uid; + int allow_freeform_uid; + int no_literal; + ulong set_filesize; + int fast_list_mode; + int fixed_list_mode; + int ignore_time_conflict; + int ignore_valid_from; + int ignore_crc_error; + int ignore_mdc_error; + int command_fd; + const char *override_session_key; + int show_session_key; + int use_agent; + const char *gpg_agent_info; + int try_all_secrets; + int no_expensive_trust_checks; + int no_sig_cache; + int no_sig_create_check; + int no_auto_check_trustdb; + int preserve_permissions; + int no_homedir_creation; + struct groupitem *grouplist; + int strict; + int mangle_dos_filenames; + int enable_progress_filter; + unsigned int screen_columns; + unsigned int screen_lines; + byte *show_subpackets; + int rfc2440_text; + + /* If true, let write failures on the status-fd exit the process. */ + int exit_on_status_write_error; + + /* If > 0, limit the number of card insertion prompts to this + value. */ + int limit_card_insert_tries; + +#ifdef ENABLE_CARD_SUPPORT + const char *ctapi_driver; /* Library to access the ctAPI. */ + const char *pcsc_driver; /* Library to access the PC/SC system. */ + int disable_ccid; /* Disable the use of the internal CCID driver. */ +#endif /*ENABLE_CARD_SUPPORT*/ + + struct + { + /* If set, require an 0x19 backsig to be present on signatures + made by signing subkeys. If not set, a missing backsig is not + an error (but an invalid backsig still is). */ + unsigned int require_cross_cert:1; + unsigned int use_embedded_filename:1; + unsigned int utf8_filename:1; + unsigned int dsa2:1; + } flags; + + /* Linked list of ways to find a key if the key isn't on the local + keyring. */ + struct akl + { + enum {AKL_CERT, AKL_PKA, AKL_LDAP, AKL_KEYSERVER, AKL_SPEC} type; + struct keyserver_spec *spec; + struct akl *next; + } *auto_key_locate; + + /* True if multiple concatenated signatures may be verified. */ + int allow_multisig_verification; + int passwd_repeat; +} opt; + +/* CTRL is used to keep some global variables we currently can't + avoid. Future concurrent versions of gpg will put it into a per + request structure CTRL. */ +EXTERN_UNLESS_MAIN_MODULE +struct { + int in_auto_key_retrieve; /* True if we are doing an + auto_key_retrieve. */ +} glo_ctrl; + +#define DBG_PACKET_VALUE 1 /* debug packet reading/writing */ +#define DBG_MPI_VALUE 2 /* debug mpi details */ +#define DBG_CIPHER_VALUE 4 /* debug cipher handling */ + /* (may reveal sensitive data) */ +#define DBG_FILTER_VALUE 8 /* debug internal filter handling */ +#define DBG_IOBUF_VALUE 16 /* debug iobuf stuff */ +#define DBG_MEMORY_VALUE 32 /* debug memory allocation stuff */ +#define DBG_CACHE_VALUE 64 /* debug the cacheing */ +#define DBG_MEMSTAT_VALUE 128 /* show memory statistics */ +#define DBG_TRUST_VALUE 256 /* debug the trustdb */ +#define DBG_HASHING_VALUE 512 /* debug hashing operations */ +#define DBG_EXTPROG_VALUE 1024 /* debug external program calls */ +#define DBG_CARD_IO_VALUE 2048 /* debug smart card I/O. */ + +#define DBG_PACKET (opt.debug & DBG_PACKET_VALUE) +#define DBG_FILTER (opt.debug & DBG_FILTER_VALUE) +#define DBG_CACHE (opt.debug & DBG_CACHE_VALUE) +#define DBG_TRUST (opt.debug & DBG_TRUST_VALUE) +#define DBG_HASHING (opt.debug & DBG_HASHING_VALUE) +#define DBG_EXTPROG (opt.debug & DBG_EXTPROG_VALUE) +#define DBG_CARD_IO (opt.debug & DBG_CARD_IO_VALUE) + + +#define GNUPG (opt.compliance==CO_GNUPG) +#define RFC1991 (opt.compliance==CO_RFC1991 || opt.compliance==CO_PGP2) +#define RFC2440 (opt.compliance==CO_RFC2440) +#define PGP2 (opt.compliance==CO_PGP2) +#define PGP6 (opt.compliance==CO_PGP6) +#define PGP7 (opt.compliance==CO_PGP7) +#define PGP8 (opt.compliance==CO_PGP8) + +/* Various option flags. Note that there should be no common string + names between the IMPORT_ and EXPORT_ flags as they can be mixed in + the keyserver-options option. */ + +#define IMPORT_LOCAL_SIGS (1<<0) +#define IMPORT_REPAIR_PKS_SUBKEY_BUG (1<<1) +#define IMPORT_FAST (1<<2) +#define IMPORT_SK2PK (1<<3) +#define IMPORT_MERGE_ONLY (1<<4) +#define IMPORT_MINIMAL (1<<5) +#define IMPORT_CLEAN (1<<6) + +#define EXPORT_LOCAL_SIGS (1<<0) +#define EXPORT_ATTRIBUTES (1<<1) +#define EXPORT_SENSITIVE_REVKEYS (1<<2) +#define EXPORT_RESET_SUBKEY_PASSWD (1<<3) +#define EXPORT_MINIMAL (1<<4) +#define EXPORT_CLEAN (1<<5) + +#define LIST_SHOW_PHOTOS (1<<0) +#define LIST_SHOW_POLICY_URLS (1<<1) +#define LIST_SHOW_STD_NOTATIONS (1<<2) +#define LIST_SHOW_USER_NOTATIONS (1<<3) +#define LIST_SHOW_NOTATIONS (LIST_SHOW_STD_NOTATIONS|LIST_SHOW_USER_NOTATIONS) +#define LIST_SHOW_KEYSERVER_URLS (1<<4) +#define LIST_SHOW_UID_VALIDITY (1<<5) +#define LIST_SHOW_UNUSABLE_UIDS (1<<6) +#define LIST_SHOW_UNUSABLE_SUBKEYS (1<<7) +#define LIST_SHOW_KEYRING (1<<8) +#define LIST_SHOW_SIG_EXPIRE (1<<9) +#define LIST_SHOW_SIG_SUBPACKETS (1<<10) + +#define VERIFY_SHOW_PHOTOS (1<<0) +#define VERIFY_SHOW_POLICY_URLS (1<<1) +#define VERIFY_SHOW_STD_NOTATIONS (1<<2) +#define VERIFY_SHOW_USER_NOTATIONS (1<<3) +#define VERIFY_SHOW_NOTATIONS (VERIFY_SHOW_STD_NOTATIONS|VERIFY_SHOW_USER_NOTATIONS) +#define VERIFY_SHOW_KEYSERVER_URLS (1<<4) +#define VERIFY_SHOW_UID_VALIDITY (1<<5) +#define VERIFY_SHOW_UNUSABLE_UIDS (1<<6) +#define VERIFY_PKA_LOOKUPS (1<<7) +#define VERIFY_PKA_TRUST_INCREASE (1<<8) + +#define KEYSERVER_USE_TEMP_FILES (1<<0) +#define KEYSERVER_KEEP_TEMP_FILES (1<<1) +#define KEYSERVER_ADD_FAKE_V3 (1<<2) +#define KEYSERVER_AUTO_KEY_RETRIEVE (1<<3) +#define KEYSERVER_HONOR_KEYSERVER_URL (1<<4) +#define KEYSERVER_HONOR_PKA_RECORD (1<<5) + +#endif /*G10_OPTIONS_H*/ diff --git a/g10/options.skel b/g10/options.skel new file mode 100644 index 0000000..83c0949 --- /dev/null +++ b/g10/options.skel @@ -0,0 +1,241 @@ +# These first three lines are not copied to the gpg.conf file in +# the users home directory. +# $Id: options.skel 4106 2006-04-11 19:20:08Z dshaw $ +# Options for GnuPG +# Copyright 1998, 1999, 2000, 2001, 2002, 2003 Free Software Foundation, Inc. +# +# This file is free software; as a special exception the author gives +# unlimited permission to copy and/or distribute it, with or without +# modifications, as long as this notice is preserved. +# +# This file is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY, to the extent permitted by law; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# +# Unless you specify which option file to use (with the command line +# option "--options filename"), GnuPG uses the file ~/.gnupg/gpg.conf +# by default. +# +# An options file can contain any long options which are available in +# GnuPG. If the first non white space character of a line is a '#', +# this line is ignored. Empty lines are also ignored. +# +# See the man page for a list of options. + +# Uncomment the following option to get rid of the copyright notice + +#no-greeting + +# If you have more than 1 secret key in your keyring, you may want to +# uncomment the following option and set your preferred keyid. + +#default-key 621CC013 + +# If you do not pass a recipient to gpg, it will ask for one. Using +# this option you can encrypt to a default key. Key validation will +# not be done in this case. The second form uses the default key as +# default recipient. + +#default-recipient some-user-id +#default-recipient-self + +# Use --encrypt-to to add the specified key as a recipient to all +# messages. This is useful, for example, when sending mail through a +# mail client that does not automatically encrypt mail to your key. +# In the example, this option allows you to read your local copy of +# encrypted mail that you've sent to others. + +#encrypt-to some-key-id + +# By default GnuPG creates version 3 signatures for data files. This +# is not strictly OpenPGP compliant but PGP 6 and most versions of PGP +# 7 require them. To disable this behavior, you may use this option +# or --openpgp. + +#no-force-v3-sigs + +# Because some mailers change lines starting with "From " to ">From " +# it is good to handle such lines in a special way when creating +# cleartext signatures; all other PGP versions do it this way too. +# To enable full OpenPGP compliance you may want to use this option. + +#no-escape-from-lines + +# If you do not use the Latin-1 (ISO-8859-1) charset, you should tell +# GnuPG which is the native character set. Please check the man page +# for supported character sets. This character set is only used for +# metadata and not for the actual message which does not undergo any +# translation. Note that future version of GnuPG will change to UTF-8 +# as default character set. In most cases this option is not required +# GnuPG is able to figure out the correct charset and use that. + +#charset utf-8 + +# Group names may be defined like this: +# group mynames = paige 0x12345678 joe patti +# +# Any time "mynames" is a recipient (-r or --recipient), it will be +# expanded to the names "paige", "joe", and "patti", and the key ID +# "0x12345678". Note there is only one level of expansion - you +# cannot make an group that points to another group. Note also that +# if there are spaces in the recipient name, this will appear as two +# recipients. In these cases it is better to use the key ID. + +#group mynames = paige 0x12345678 joe patti + +# Lock the file only once for the lifetime of a process. If you do +# not define this, the lock will be obtained and released every time +# it is needed, which is usually preferable. + +#lock-once + +# GnuPG can send and receive keys to and from a keyserver. These +# servers can be HKP, email, or LDAP (if GnuPG is built with LDAP +# support). +# +# Example HKP keyserver: +# hkp://subkeys.pgp.net +# +# Example email keyserver: +# mailto:pgp-public-keys@keys.pgp.net +# +# Example LDAP keyservers: +# ldap://keyserver.pgp.com +# +# Regular URL syntax applies, and you can set an alternate port +# through the usual method: +# hkp://keyserver.example.net:22742 +# +# If you have problems connecting to a HKP server through a buggy http +# proxy, you can use keyserver option broken-http-proxy (see below), +# but first you should make sure that you have read the man page +# regarding proxies (keyserver option honor-http-proxy) +# +# Most users just set the name and type of their preferred keyserver. +# Note that most servers (with the notable exception of +# ldap://keyserver.pgp.com) synchronize changes with each other. Note +# also that a single server name may actually point to multiple +# servers via DNS round-robin. hkp://subkeys.pgp.net is an example of +# such a "server", which spreads the load over a number of physical +# servers. + +keyserver hkp://subkeys.pgp.net +#keyserver mailto:pgp-public-keys@keys.nl.pgp.net +#keyserver ldap://keyserver.pgp.com + +# Common options for keyserver functions: +# +# include-disabled = when searching, include keys marked as "disabled" +# on the keyserver (not all keyservers support this). +# +# no-include-revoked = when searching, do not include keys marked as +# "revoked" on the keyserver. +# +# verbose = show more information as the keys are fetched. +# Can be used more than once to increase the amount +# of information shown. +# +# use-temp-files = use temporary files instead of a pipe to talk to the +# keyserver. Some platforms (Win32 for one) always +# have this on. +# +# keep-temp-files = do not delete temporary files after using them +# (really only useful for debugging) +# +# honor-http-proxy = if the keyserver uses HTTP, honor the http_proxy +# environment variable +# +# broken-http-proxy = try to work around a buggy HTTP proxy +# +# auto-key-retrieve = automatically fetch keys as needed from the keyserver +# when verifying signatures or when importing keys that +# have been revoked by a revocation key that is not +# present on the keyring. +# +# no-include-attributes = do not include attribute IDs (aka "photo IDs") +# when sending keys to the keyserver. + +#keyserver-options auto-key-retrieve + +# Display photo user IDs in key listings + +# list-options show-photos + +# Display photo user IDs when a signature from a key with a photo is +# verified + +# verify-options show-photos + +# Use this program to display photo user IDs +# +# %i is expanded to a temporary file that contains the photo. +# %I is the same as %i, but the file isn't deleted afterwards by GnuPG. +# %k is expanded to the key ID of the key. +# %K is expanded to the long OpenPGP key ID of the key. +# %t is expanded to the extension of the image (e.g. "jpg"). +# %T is expanded to the MIME type of the image (e.g. "image/jpeg"). +# %f is expanded to the fingerprint of the key. +# %% is %, of course. +# +# If %i or %I are not present, then the photo is supplied to the +# viewer on standard input. If your platform supports it, standard +# input is the best way to do this as it avoids the time and effort in +# generating and then cleaning up a secure temp file. +# +# If no photo-viewer is provided, GnuPG will look for xloadimage, eog, +# or display (ImageMagick). On Mac OS X and Windows, the default is +# to use your regular JPEG image viewer. +# +# Some other viewers: +# photo-viewer "qiv %i" +# photo-viewer "ee %i" +# +# This one saves a copy of the photo ID in your home directory: +# photo-viewer "cat > ~/photoid-for-key-%k.%t" +# +# Use your MIME handler to view photos: +# photo-viewer "metamail -q -d -b -c %T -s 'KeyID 0x%k' -f GnuPG" + +# Passphrase agent +# +# We support the old experimental passphrase agent protocol as well as +# the new Assuan based one (currently available in the "newpg" package +# at ftp.gnupg.org/gcrypt/alpha/aegypten/). To make use of the agent, +# you have to run an agent as daemon and use the option +# +# use-agent +# +# which tries to use the agent but will fallback to the regular mode +# if there is a problem connecting to the agent. The normal way to +# locate the agent is by looking at the environment variable +# GPG_AGENT_INFO which should have been set during gpg-agent startup. +# In certain situations the use of this variable is not possible, thus +# the option +# +# --gpg-agent-info=<path>:<pid>:1 +# +# may be used to override it. + +# Automatic key location +# +# GnuPG can automatically locate and retrieve keys as needed using the +# auto-key-locate option. This happens when encrypting to an email +# address (in the "user@example.com" form), and there are no +# user@example.com keys on the local keyring. This option takes the +# following arguments, in the order they are to be tried: +# +# cert = locate a key using DNS CERT, as specified in 2538bis +# (currently in draft): http://www.josefsson.org/rfc2538bis/ +# +# pka = locate a key using DNS PKA. +# +# ldap = locate a key using the PGP Universal method of checking +# "ldap://keys.(thedomain)". +# +# keyserver = locate a key using whatever keyserver is defined using +# the keyserver option. +# +# You may also list arbitrary keyservers here by URL. +# +# Try CERT, then PKA, then LDAP, then hkp://subkeys.net: +#auto-key-locate cert pka ldap hkp://subkeys.pgp.net diff --git a/g10/packet.h b/g10/packet.h new file mode 100644 index 0000000..f318133 --- /dev/null +++ b/g10/packet.h @@ -0,0 +1,569 @@ +/* packet.h - packet definitions + * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, + * 2006 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#ifndef G10_PACKET_H +#define G10_PACKET_H + +#include "types.h" +#include "iobuf.h" +#include "mpi.h" +#include "cipher.h" +#include "filter.h" +#include "global.h" + +#define DEBUG_PARSE_PACKET 1 + +typedef enum { + PKT_NONE =0, + PKT_PUBKEY_ENC =1, /* public key encrypted packet */ + PKT_SIGNATURE =2, /* secret key encrypted packet */ + PKT_SYMKEY_ENC =3, /* session key packet (OpenPGP)*/ + PKT_ONEPASS_SIG =4, /* one pass sig packet (OpenPGP)*/ + PKT_SECRET_KEY =5, /* secret key */ + PKT_PUBLIC_KEY =6, /* public key */ + PKT_SECRET_SUBKEY =7, /* secret subkey (OpenPGP) */ + PKT_COMPRESSED =8, /* compressed data packet */ + PKT_ENCRYPTED =9, /* conventional encrypted data */ + PKT_MARKER =10, /* marker packet (OpenPGP) */ + PKT_PLAINTEXT =11, /* plaintext data with filename and mode */ + PKT_RING_TRUST =12, /* keyring trust packet */ + PKT_USER_ID =13, /* user id packet */ + PKT_PUBLIC_SUBKEY =14, /* public subkey (OpenPGP) */ + PKT_OLD_COMMENT =16, /* comment packet from an OpenPGP draft */ + PKT_ATTRIBUTE =17, /* PGP's attribute packet */ + PKT_ENCRYPTED_MDC =18, /* integrity protected encrypted data */ + PKT_MDC =19, /* manipulation detection code packet */ + PKT_COMMENT =61, /* new comment packet (private) */ + PKT_GPG_CONTROL =63 /* internal control packet */ +} pkttype_t; + +typedef struct packet_struct PACKET; + +/* PKT_GPG_CONTROL types */ +typedef enum { + CTRLPKT_CLEARSIGN_START = 1, + CTRLPKT_PIPEMODE = 2, + CTRLPKT_PLAINTEXT_MARK =3 +} ctrlpkttype_t; + +typedef enum { + PREFTYPE_NONE = 0, + PREFTYPE_SYM = 1, + PREFTYPE_HASH = 2, + PREFTYPE_ZIP = 3 +} preftype_t; + +typedef struct { + byte type; + byte value; +} prefitem_t; + +typedef struct { + int mode; + byte hash_algo; + byte salt[8]; + u32 count; +} STRING2KEY; + +typedef struct { + byte version; + byte cipher_algo; /* cipher algorithm used */ + STRING2KEY s2k; + byte seskeylen; /* keylength in byte or 0 for no seskey */ + byte seskey[1]; +} PKT_symkey_enc; + +typedef struct { + u32 keyid[2]; /* 64 bit keyid */ + byte version; + byte pubkey_algo; /* algorithm used for public key scheme */ + byte throw_keyid; + MPI data[PUBKEY_MAX_NENC]; +} PKT_pubkey_enc; + + +typedef struct { + u32 keyid[2]; /* 64 bit keyid */ + byte sig_class; /* sig classification */ + byte digest_algo; /* algorithm used for digest */ + byte pubkey_algo; /* algorithm used for public key scheme */ + byte last; /* a stupid flag */ +} PKT_onepass_sig; + + +typedef struct { + size_t size; /* allocated */ + size_t len; /* used */ + byte data[1]; +} subpktarea_t; + +struct revocation_key { + byte class; + byte algid; + byte fpr[MAX_FINGERPRINT_LEN]; +}; + + +/* Object to keep information about a PKA DNS record. */ +typedef struct +{ + int valid; /* An actual PKA record exists for EMAIL. */ + int checked; /* Set to true if the FPR has been checked against the + actual key. */ + char *uri; /* Malloced string with the URI. NULL if the URI is + not available.*/ + unsigned char fpr[20]; /* The fingerprint as stored in the PKA RR. */ + char email[1];/* The email address from the notation data. */ +} pka_info_t; + + +/* Object to keep information pertaining to a signature. */ +typedef struct +{ + struct + { + unsigned checked:1; /* Signature has been checked. */ + unsigned valid:1; /* Signature is good (if checked is set). */ + unsigned chosen_selfsig:1; /* A selfsig that is the chosen one. */ + unsigned unknown_critical:1; + unsigned exportable:1; + unsigned revocable:1; + unsigned policy_url:1; /* At least one policy URL is present */ + unsigned notation:1; /* At least one notation is present */ + unsigned pref_ks:1; /* At least one preferred keyserver is present */ + unsigned expired:1; + unsigned pka_tried:1; /* Set if we tried to retrieve the PKA record. */ + } flags; + u32 keyid[2]; /* 64 bit keyid */ + u32 timestamp; /* Signature made (seconds since Epoch). */ + u32 expiredate; /* Expires at this date or 0 if not at all. */ + byte version; + byte sig_class; /* Sig classification, append for MD calculation. */ + byte pubkey_algo; /* Algorithm used for public key scheme */ + /* (PUBKEY_ALGO_xxx) */ + byte digest_algo; /* Algorithm used for digest (DIGEST_ALGO_xxxx). */ + byte trust_depth; + byte trust_value; + const byte *trust_regexp; + struct revocation_key **revkey; + int numrevkeys; + pka_info_t *pka_info; /* Malloced PKA data or NULL if not + available. See also flags.pka_tried. */ + subpktarea_t *hashed; /* All subpackets with hashed data (v4 only). */ + subpktarea_t *unhashed; /* Ditto for unhashed data. */ + byte digest_start[2]; /* First 2 bytes of the digest. */ + MPI data[PUBKEY_MAX_NSIG]; +} PKT_signature; + +#define ATTRIB_IMAGE 1 + +/* This is the cooked form of attributes */ +struct user_attribute { + byte type; + const byte *data; + u32 len; +}; + +typedef struct +{ + int ref; /* reference counter */ + int len; /* length of the name */ + struct user_attribute *attribs; + int numattribs; + byte *attrib_data; /* if this is not NULL, the packet is an attribute */ + unsigned long attrib_len; + byte *namehash; + int help_key_usage; + u32 help_key_expire; + int help_full_count; + int help_marginal_count; + int is_primary; /* 2 if set via the primary flag, 1 if calculated */ + int is_revoked; + int is_expired; + u32 expiredate; /* expires at this date or 0 if not at all */ + prefitem_t *prefs; /* list of preferences (may be NULL)*/ + u32 created; /* according to the self-signature */ + byte selfsigversion; + struct + { + /* TODO: Move more flags here */ + unsigned mdc:1; + unsigned ks_modify:1; + unsigned compacted:1; + } flags; + char name[1]; +} PKT_user_id; + +struct revoke_info +{ + /* revoked at this date */ + u32 date; + /* the keyid of the revoking key (selfsig or designated revoker) */ + u32 keyid[2]; + /* the algo of the revoking key */ + byte algo; +}; + +/**************** + * Note about the pkey/skey elements: We assume that the secret keys + * has the same elemts as the public key at the begin of the array, so + * that npkey < nskey and it is possible to compare the secret and + * public keys by comparing the first npkey elements of pkey againts skey. + */ +typedef struct { + u32 timestamp; /* key made */ + u32 expiredate; /* expires at this date or 0 if not at all */ + u32 max_expiredate; /* must not expire past this date */ + struct revoke_info revoked; + byte hdrbytes; /* number of header bytes */ + byte version; + byte selfsigversion; /* highest version of all of the self-sigs */ + byte pubkey_algo; /* algorithm used for public key scheme */ + byte pubkey_usage; /* for now only used to pass it to getkey() */ + byte req_usage; /* hack to pass a request to getkey() */ + byte req_algo; /* Ditto */ + u32 has_expired; /* set to the expiration date if expired */ + int is_revoked; /* key has been revoked, 1 if by the + owner, 2 if by a designated revoker */ + int maybe_revoked; /* a designated revocation is present, but + without the key to check it */ + int is_valid; /* key (especially subkey) is valid */ + int dont_cache; /* do not cache this */ + byte backsig; /* 0=none, 1=bad, 2=good */ + u32 main_keyid[2]; /* keyid of the primary key */ + u32 keyid[2]; /* calculated by keyid_from_pk() */ + byte is_primary; + byte is_disabled; /* 0 for unset, 1 for enabled, 2 for disabled. */ + prefitem_t *prefs; /* list of preferences (may be NULL) */ + int mdc_feature; /* mdc feature set */ + PKT_user_id *user_id; /* if != NULL: found by that uid */ + struct revocation_key *revkey; + int numrevkeys; + u32 trust_timestamp; + byte trust_depth; + byte trust_value; + const byte *trust_regexp; + MPI pkey[PUBKEY_MAX_NPKEY]; +} PKT_public_key; + +/* Evaluates as true if the pk is disabled, and false if it isn't. If + there is no disable value cached, fill one in. */ +#define pk_is_disabled(a) (((a)->is_disabled)?((a)->is_disabled==2):(cache_disabled_value((a)))) + +typedef struct { + u32 timestamp; /* key made */ + u32 expiredate; /* expires at this date or 0 if not at all */ + u32 max_expiredate; /* must not expire past this date */ + byte hdrbytes; /* number of header bytes */ + byte version; + byte pubkey_algo; /* algorithm used for public key scheme */ + byte pubkey_usage; + byte req_usage; + byte req_algo; + u32 has_expired; /* set to the expiration date if expired */ + int is_revoked; /* key has been revoked */ + int is_valid; /* key (especially subkey) is valid */ + u32 main_keyid[2]; /* keyid of the primary key */ + u32 keyid[2]; + byte is_primary; + byte is_protected; /* The secret info is protected and must */ + /* be decrypted before use, the protected */ + /* MPIs are simply (void*) pointers to memory */ + /* and should never be passed to a mpi_xxx() */ + struct { + byte algo; /* cipher used to protect the secret information*/ + byte sha1chk; /* SHA1 is used instead of a 16 bit checksum */ + STRING2KEY s2k; + byte ivlen; /* used length of the iv */ + byte iv[16]; /* initialization vector for CFB mode */ + } protect; + MPI skey[PUBKEY_MAX_NSKEY]; + u16 csum; /* checksum */ +} PKT_secret_key; + + +typedef struct { + int len; /* length of data */ + char data[1]; +} PKT_comment; + +typedef struct { + u32 len; /* reserved */ + byte new_ctb; + byte algorithm; + IOBUF buf; /* IOBUF reference */ +} PKT_compressed; + +typedef struct { + u32 len; /* length of encrypted data */ + int extralen; /* this is (blocksize+2) */ + byte new_ctb; /* uses a new CTB */ + byte is_partial; /* partial length encoded */ + byte mdc_method; /* > 0: integrity protected encrypted data packet */ + IOBUF buf; /* IOBUF reference */ +} PKT_encrypted; + +typedef struct { + byte hash[20]; +} PKT_mdc; + +typedef struct { + unsigned int trustval; + unsigned int sigcache; +} PKT_ring_trust; + +typedef struct { + u32 len; /* length of encrypted data */ + IOBUF buf; /* IOBUF reference */ + byte new_ctb; + byte is_partial; /* partial length encoded */ + int mode; + u32 timestamp; + int namelen; + char name[1]; +} PKT_plaintext; + +typedef struct { + int control; + size_t datalen; + char data[1]; +} PKT_gpg_control; + +/* combine all packets into a union */ +struct packet_struct { + pkttype_t pkttype; + union { + void *generic; + PKT_symkey_enc *symkey_enc; /* PKT_SYMKEY_ENC */ + PKT_pubkey_enc *pubkey_enc; /* PKT_PUBKEY_ENC */ + PKT_onepass_sig *onepass_sig; /* PKT_ONEPASS_SIG */ + PKT_signature *signature; /* PKT_SIGNATURE */ + PKT_public_key *public_key; /* PKT_PUBLIC_[SUB)KEY */ + PKT_secret_key *secret_key; /* PKT_SECRET_[SUB]KEY */ + PKT_comment *comment; /* PKT_COMMENT */ + PKT_user_id *user_id; /* PKT_USER_ID */ + PKT_compressed *compressed; /* PKT_COMPRESSED */ + PKT_encrypted *encrypted; /* PKT_ENCRYPTED[_MDC] */ + PKT_mdc *mdc; /* PKT_MDC */ + PKT_ring_trust *ring_trust; /* PKT_RING_TRUST */ + PKT_plaintext *plaintext; /* PKT_PLAINTEXT */ + PKT_gpg_control *gpg_control; /* PKT_GPG_CONTROL */ + } pkt; +}; + +#define init_packet(a) do { (a)->pkttype = 0; \ + (a)->pkt.generic = NULL; \ + } while(0) + +typedef enum { + SIGSUBPKT_TEST_CRITICAL=-3, + SIGSUBPKT_LIST_UNHASHED=-2, + SIGSUBPKT_LIST_HASHED =-1, + SIGSUBPKT_NONE = 0, + SIGSUBPKT_SIG_CREATED = 2, /* signature creation time */ + SIGSUBPKT_SIG_EXPIRE = 3, /* signature expiration time */ + SIGSUBPKT_EXPORTABLE = 4, /* exportable */ + SIGSUBPKT_TRUST = 5, /* trust signature */ + SIGSUBPKT_REGEXP = 6, /* regular expression */ + SIGSUBPKT_REVOCABLE = 7, /* revocable */ + SIGSUBPKT_KEY_EXPIRE = 9, /* key expiration time */ + SIGSUBPKT_ARR =10, /* additional recipient request */ + SIGSUBPKT_PREF_SYM =11, /* preferred symmetric algorithms */ + SIGSUBPKT_REV_KEY =12, /* revocation key */ + SIGSUBPKT_ISSUER =16, /* issuer key ID */ + SIGSUBPKT_NOTATION =20, /* notation data */ + SIGSUBPKT_PREF_HASH =21, /* preferred hash algorithms */ + SIGSUBPKT_PREF_COMPR =22, /* preferred compression algorithms */ + SIGSUBPKT_KS_FLAGS =23, /* key server preferences */ + SIGSUBPKT_PREF_KS =24, /* preferred key server */ + SIGSUBPKT_PRIMARY_UID =25, /* primary user id */ + SIGSUBPKT_POLICY =26, /* policy URL */ + SIGSUBPKT_KEY_FLAGS =27, /* key flags */ + SIGSUBPKT_SIGNERS_UID =28, /* signer's user id */ + SIGSUBPKT_REVOC_REASON =29, /* reason for revocation */ + SIGSUBPKT_FEATURES =30, /* feature flags */ + + SIGSUBPKT_SIGNATURE =32, /* embedded signature */ + + SIGSUBPKT_FLAG_CRITICAL=128 +} sigsubpkttype_t; + +struct notation +{ + char *name; + char *value; + char *altvalue; + unsigned char *bdat; + size_t blen; + struct + { + unsigned int critical:1; + unsigned int ignore:1; + } flags; + struct notation *next; +}; + +/*-- mainproc.c --*/ +int proc_packets( void *ctx, IOBUF a ); +int proc_signature_packets( void *ctx, IOBUF a, + STRLIST signedfiles, const char *sigfile ); +int proc_encryption_packets( void *ctx, IOBUF a ); +int list_packets( IOBUF a ); + +/*-- parse-packet.c --*/ +int set_packet_list_mode( int mode ); + +#if DEBUG_PARSE_PACKET +int dbg_search_packet( IOBUF inp, PACKET *pkt, off_t *retpos, int with_uid, + const char* file, int lineno ); +int dbg_parse_packet( IOBUF inp, PACKET *ret_pkt, + const char* file, int lineno ); +int dbg_copy_all_packets( IOBUF inp, IOBUF out, + const char* file, int lineno ); +int dbg_copy_some_packets( IOBUF inp, IOBUF out, off_t stopoff, + const char* file, int lineno ); +int dbg_skip_some_packets( IOBUF inp, unsigned n, + const char* file, int lineno ); +#define search_packet( a,b,c,d ) \ + dbg_search_packet( (a), (b), (c), (d), __FILE__, __LINE__ ) +#define parse_packet( a, b ) \ + dbg_parse_packet( (a), (b), __FILE__, __LINE__ ) +#define copy_all_packets( a,b ) \ + dbg_copy_all_packets((a),(b), __FILE__, __LINE__ ) +#define copy_some_packets( a,b,c ) \ + dbg_copy_some_packets((a),(b),(c), __FILE__, __LINE__ ) +#define skip_some_packets( a,b ) \ + dbg_skip_some_packets((a),(b), __FILE__, __LINE__ ) +#else +int search_packet( IOBUF inp, PACKET *pkt, off_t *retpos, int with_uid ); +int parse_packet( IOBUF inp, PACKET *ret_pkt); +int copy_all_packets( IOBUF inp, IOBUF out ); +int copy_some_packets( IOBUF inp, IOBUF out, off_t stopoff ); +int skip_some_packets( IOBUF inp, unsigned n ); +#endif + +int parse_signature( IOBUF inp, int pkttype, unsigned long pktlen, + PKT_signature *sig ); +const byte *enum_sig_subpkt ( const subpktarea_t *subpkts, + sigsubpkttype_t reqtype, + size_t *ret_n, int *start, int *critical ); +const byte *parse_sig_subpkt ( const subpktarea_t *buffer, + sigsubpkttype_t reqtype, + size_t *ret_n ); +const byte *parse_sig_subpkt2 ( PKT_signature *sig, + sigsubpkttype_t reqtype, + size_t *ret_n ); +int parse_one_sig_subpkt( const byte *buffer, size_t n, int type ); +void parse_revkeys(PKT_signature *sig); +int parse_attribute_subpkts(PKT_user_id *uid); +void make_attribute_uidname(PKT_user_id *uid, size_t max_namelen); +PACKET *create_gpg_control ( ctrlpkttype_t type, + const byte *data, + size_t datalen ); + +/*-- build-packet.c --*/ +int build_packet( IOBUF inp, PACKET *pkt ); +u32 calc_packet_length( PACKET *pkt ); +void build_sig_subpkt( PKT_signature *sig, sigsubpkttype_t type, + const byte *buffer, size_t buflen ); +void build_sig_subpkt_from_sig( PKT_signature *sig ); +int delete_sig_subpkt(subpktarea_t *buffer, sigsubpkttype_t type ); +void build_attribute_subpkt(PKT_user_id *uid,byte type, + const void *buf,u32 buflen, + const void *header,u32 headerlen); +struct notation *string_to_notation(const char *string,int is_utf8); +struct notation *sig_to_notation(PKT_signature *sig); +void free_notation(struct notation *notation); + +/*-- free-packet.c --*/ +void free_symkey_enc( PKT_symkey_enc *enc ); +void free_pubkey_enc( PKT_pubkey_enc *enc ); +void free_seckey_enc( PKT_signature *enc ); +int digest_algo_from_sig( PKT_signature *sig ); +void release_public_key_parts( PKT_public_key *pk ); +void free_public_key( PKT_public_key *key ); +void release_secret_key_parts( PKT_secret_key *sk ); +void free_secret_key( PKT_secret_key *sk ); +void free_attributes(PKT_user_id *uid); +void free_user_id( PKT_user_id *uid ); +void free_comment( PKT_comment *rem ); +void free_packet( PACKET *pkt ); +prefitem_t *copy_prefs (const prefitem_t *prefs); +PKT_public_key *copy_public_key( PKT_public_key *d, PKT_public_key *s ); +void copy_public_parts_to_secret_key( PKT_public_key *pk, PKT_secret_key *sk ); +PKT_secret_key *copy_secret_key( PKT_secret_key *d, PKT_secret_key *s ); +PKT_signature *copy_signature( PKT_signature *d, PKT_signature *s ); +PKT_user_id *scopy_user_id (PKT_user_id *sd ); +int cmp_public_keys( PKT_public_key *a, PKT_public_key *b ); +int cmp_secret_keys( PKT_secret_key *a, PKT_secret_key *b ); +int cmp_signatures( PKT_signature *a, PKT_signature *b ); +int cmp_public_secret_key( PKT_public_key *pk, PKT_secret_key *sk ); +int cmp_user_ids( PKT_user_id *a, PKT_user_id *b ); + + +/*-- sig-check.c --*/ +int signature_check( PKT_signature *sig, MD_HANDLE digest ); +int signature_check2( PKT_signature *sig, MD_HANDLE digest, u32 *r_expiredate, + int *r_expired, int *r_revoked, PKT_public_key *ret_pk ); + +/*-- seckey-cert.c --*/ +int is_secret_key_protected( PKT_secret_key *sk ); +int check_secret_key( PKT_secret_key *sk, int retries ); +int protect_secret_key( PKT_secret_key *sk, DEK *dek ); + +/*-- pubkey-enc.c --*/ +int get_session_key( PKT_pubkey_enc *k, DEK *dek ); +int get_override_session_key( DEK *dek, const char *string ); + +/*-- compress.c --*/ +int handle_compressed( void *ctx, PKT_compressed *cd, + int (*callback)(IOBUF, void *), void *passthru ); + +/*-- encr-data.c --*/ +int decrypt_data( void *ctx, PKT_encrypted *ed, DEK *dek ); + +/*-- plaintext.c --*/ +int handle_plaintext( PKT_plaintext *pt, md_filter_context_t *mfx, + int nooutput, int clearsig ); +int ask_for_detached_datafile( MD_HANDLE md, MD_HANDLE md2, + const char *inname, int textmode ); + +/*-- sign.c --*/ +int make_keysig_packet( PKT_signature **ret_sig, PKT_public_key *pk, + PKT_user_id *uid, PKT_public_key *subpk, + PKT_secret_key *sk, int sigclass, int digest_algo, + int sigversion, u32 timestamp, u32 duration, + int (*mksubpkt)(PKT_signature *, void *), + void *opaque ); +int update_keysig_packet( PKT_signature **ret_sig, + PKT_signature *orig_sig, + PKT_public_key *pk, + PKT_user_id *uid, + PKT_public_key *subpk, + PKT_secret_key *sk, + int (*mksubpkt)(PKT_signature *, void *), + void *opaque ); + +/*-- keygen.c --*/ +PKT_user_id *generate_user_id(void); + +#endif /*G10_PACKET_H*/ diff --git a/g10/parse-packet.c b/g10/parse-packet.c new file mode 100644 index 0000000..31f21bc --- /dev/null +++ b/g10/parse-packet.c @@ -0,0 +1,2411 @@ +/* parse-packet.c - read packets + * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, + * 2006 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> + +#include "packet.h" +#include "iobuf.h" +#include "mpi.h" +#include "util.h" +#include "cipher.h" +#include "memory.h" +#include "filter.h" +#include "photoid.h" +#include "options.h" +#include "main.h" +#include "i18n.h" + +static int mpi_print_mode; +static int list_mode; +static FILE *listfp; + +static int parse( IOBUF inp, PACKET *pkt, int onlykeypkts, + off_t *retpos, int *skip, IOBUF out, int do_skip +#ifdef DEBUG_PARSE_PACKET + ,const char *dbg_w, const char *dbg_f, int dbg_l +#endif + ); +static int copy_packet( IOBUF inp, IOBUF out, int pkttype, + unsigned long pktlen, int partial ); +static void skip_packet( IOBUF inp, int pkttype, + unsigned long pktlen, int partial ); +static void *read_rest( IOBUF inp, size_t pktlen, int partial ); +static int parse_symkeyenc( IOBUF inp, int pkttype, unsigned long pktlen, + PACKET *packet ); +static int parse_pubkeyenc( IOBUF inp, int pkttype, unsigned long pktlen, + PACKET *packet ); +static int parse_onepass_sig( IOBUF inp, int pkttype, unsigned long pktlen, + PKT_onepass_sig *ops ); +static int parse_key( IOBUF inp, int pkttype, unsigned long pktlen, + byte *hdr, int hdrlen, PACKET *packet ); +static int parse_user_id( IOBUF inp, int pkttype, unsigned long pktlen, + PACKET *packet ); +static int parse_attribute( IOBUF inp, int pkttype, unsigned long pktlen, + PACKET *packet ); +static int parse_comment( IOBUF inp, int pkttype, unsigned long pktlen, + PACKET *packet ); +static void parse_trust( IOBUF inp, int pkttype, unsigned long pktlen, + PACKET *packet ); +static int parse_plaintext( IOBUF inp, int pkttype, unsigned long pktlen, + PACKET *packet, int new_ctb, int partial); +static int parse_compressed( IOBUF inp, int pkttype, unsigned long pktlen, + PACKET *packet, int new_ctb ); +static int parse_encrypted( IOBUF inp, int pkttype, unsigned long pktlen, + PACKET *packet, int new_ctb, int partial); +static int parse_mdc( IOBUF inp, int pkttype, unsigned long pktlen, + PACKET *packet, int new_ctb); +static int parse_gpg_control( IOBUF inp, int pkttype, unsigned long pktlen, + PACKET *packet, int partial ); + +static unsigned short +read_16(IOBUF inp) +{ + unsigned short a; + a = iobuf_get_noeof(inp) << 8; + a |= iobuf_get_noeof(inp); + return a; +} + +static unsigned long +read_32(IOBUF inp) +{ + unsigned long a; + a = iobuf_get_noeof(inp) << 24; + a |= iobuf_get_noeof(inp) << 16; + a |= iobuf_get_noeof(inp) << 8; + a |= iobuf_get_noeof(inp); + return a; +} + + +int +set_packet_list_mode( int mode ) +{ + int old = list_mode; + list_mode = mode; + mpi_print_mode = DBG_MPI; + /* We use stdout print only if invoked by the --list-packets + command but switch to stderr in all otehr cases. This breaks + the previous behaviour but that seems to be more of a bug than + intentional. I don't believe that any application makes use of + this long standing annoying way of printing to stdout except + when doing a --list-packets. If this assumption fails, it will + be easy to add an option for the listing stream. Note that we + initialize it only once; mainly because some code may switch + the option value later back to 1 and we want to have all output + to the same stream. + + Using stderr is not actually very clean because it bypasses the + logging code but it is a special thing anyay. I am not sure + whether using log_stream() would be better. Perhaps we should + enable the list mdoe only with a special option. */ + if (!listfp) + listfp = opt.list_packets == 2 ? stdout : stderr; + return old; +} + +static void +unknown_pubkey_warning( int algo ) +{ + static byte unknown_pubkey_algos[256]; + + algo &= 0xff; + if( !unknown_pubkey_algos[algo] ) { + if( opt.verbose ) + log_info(_("can't handle public key algorithm %d\n"), algo ); + unknown_pubkey_algos[algo] = 1; + } +} + +/**************** + * Parse a Packet and return it in packet + * Returns: 0 := valid packet in pkt + * -1 := no more packets + * >0 := error + * Note: The function may return an error and a partly valid packet; + * caller must free this packet. + */ +#ifdef DEBUG_PARSE_PACKET +int +dbg_parse_packet( IOBUF inp, PACKET *pkt, const char *dbg_f, int dbg_l ) +{ + int skip, rc; + + do { + rc = parse( inp, pkt, 0, NULL, &skip, NULL, 0, "parse", dbg_f, dbg_l ); + } while( skip ); + return rc; +} +#else +int +parse_packet( IOBUF inp, PACKET *pkt ) +{ + int skip, rc; + + do { + rc = parse( inp, pkt, 0, NULL, &skip, NULL, 0 ); + } while( skip ); + return rc; +} +#endif + +/**************** + * Like parse packet, but only return secret or public (sub)key packets. + */ +#ifdef DEBUG_PARSE_PACKET +int +dbg_search_packet( IOBUF inp, PACKET *pkt, off_t *retpos, int with_uid, + const char *dbg_f, int dbg_l ) +{ + int skip, rc; + + do { + rc = parse( inp, pkt, with_uid?2:1, retpos, &skip, NULL, 0, "search", dbg_f, dbg_l ); + } while( skip ); + return rc; +} +#else +int +search_packet( IOBUF inp, PACKET *pkt, off_t *retpos, int with_uid ) +{ + int skip, rc; + + do { + rc = parse( inp, pkt, with_uid?2:1, retpos, &skip, NULL, 0 ); + } while( skip ); + return rc; +} +#endif + +/**************** + * Copy all packets from INP to OUT, thereby removing unused spaces. + */ +#ifdef DEBUG_PARSE_PACKET +int +dbg_copy_all_packets( IOBUF inp, IOBUF out, + const char *dbg_f, int dbg_l ) +{ + PACKET pkt; + int skip, rc=0; + do { + init_packet(&pkt); + } while( !(rc = parse( inp, &pkt, 0, NULL, &skip, out, 0, "copy", dbg_f, dbg_l ))); + return rc; +} +#else +int +copy_all_packets( IOBUF inp, IOBUF out ) +{ + PACKET pkt; + int skip, rc=0; + do { + init_packet(&pkt); + } while( !(rc = parse( inp, &pkt, 0, NULL, &skip, out, 0 ))); + return rc; +} +#endif + +/**************** + * Copy some packets from INP to OUT, thereby removing unused spaces. + * Stop at offset STOPoff (i.e. don't copy packets at this or later offsets) + */ +#ifdef DEBUG_PARSE_PACKET +int +dbg_copy_some_packets( IOBUF inp, IOBUF out, off_t stopoff, + const char *dbg_f, int dbg_l ) +{ + PACKET pkt; + int skip, rc=0; + do { + if( iobuf_tell(inp) >= stopoff ) + return 0; + init_packet(&pkt); + } while( !(rc = parse( inp, &pkt, 0, NULL, &skip, out, 0, + "some", dbg_f, dbg_l )) ); + return rc; +} +#else +int +copy_some_packets( IOBUF inp, IOBUF out, off_t stopoff ) +{ + PACKET pkt; + int skip, rc=0; + do { + if( iobuf_tell(inp) >= stopoff ) + return 0; + init_packet(&pkt); + } while( !(rc = parse( inp, &pkt, 0, NULL, &skip, out, 0 )) ); + return rc; +} +#endif + +/**************** + * Skip over N packets + */ +#ifdef DEBUG_PARSE_PACKET +int +dbg_skip_some_packets( IOBUF inp, unsigned n, + const char *dbg_f, int dbg_l ) +{ + int skip, rc=0; + PACKET pkt; + + for( ;n && !rc; n--) { + init_packet(&pkt); + rc = parse( inp, &pkt, 0, NULL, &skip, NULL, 1, "skip", dbg_f, dbg_l ); + } + return rc; +} +#else +int +skip_some_packets( IOBUF inp, unsigned n ) +{ + int skip, rc=0; + PACKET pkt; + + for( ;n && !rc; n--) { + init_packet(&pkt); + rc = parse( inp, &pkt, 0, NULL, &skip, NULL, 1 ); + } + return rc; +} +#endif + + +/**************** + * Parse packet. Set the variable skip points to 1 if the packet + * should be skipped; this is the case if either ONLYKEYPKTS is set + * and the parsed packet isn't one or the + * packet-type is 0, indicating deleted stuff. + * if OUT is not NULL, a special copymode is used. + */ +static int +parse( IOBUF inp, PACKET *pkt, int onlykeypkts, off_t *retpos, + int *skip, IOBUF out, int do_skip +#ifdef DEBUG_PARSE_PACKET + ,const char *dbg_w, const char *dbg_f, int dbg_l +#endif + ) +{ + int rc=0, c, ctb, pkttype, lenbytes; + unsigned long pktlen; + byte hdr[8]; + int hdrlen; + int new_ctb = 0, partial=0; + int with_uid = (onlykeypkts == 2); + + *skip = 0; + assert( !pkt->pkt.generic ); + if( retpos ) + *retpos = iobuf_tell(inp); + + if( (ctb = iobuf_get(inp)) == -1 ) { + rc = -1; + goto leave; + } + hdrlen=0; + hdr[hdrlen++] = ctb; + if( !(ctb & 0x80) ) { + log_error("%s: invalid packet (ctb=%02x)\n", iobuf_where(inp), ctb ); + rc = G10ERR_INVALID_PACKET; + goto leave; + } + pktlen = 0; + new_ctb = !!(ctb & 0x40); + if( new_ctb ) { + pkttype = ctb & 0x3f; + if( (c = iobuf_get(inp)) == -1 ) { + log_error("%s: 1st length byte missing\n", iobuf_where(inp) ); + rc = G10ERR_INVALID_PACKET; + goto leave; + } + if (pkttype == PKT_COMPRESSED) { + iobuf_set_partial_block_mode(inp, c & 0xff); + pktlen = 0;/* to indicate partial length */ + partial=1; + } + else { + hdr[hdrlen++] = c; + if( c < 192 ) + pktlen = c; + else if( c < 224 ) + { + pktlen = (c - 192) * 256; + if( (c = iobuf_get(inp)) == -1 ) + { + log_error("%s: 2nd length byte missing\n", + iobuf_where(inp) ); + rc = G10ERR_INVALID_PACKET; + goto leave; + } + hdr[hdrlen++] = c; + pktlen += c + 192; + } + else if( c == 255 ) + { + pktlen = (hdr[hdrlen++] = iobuf_get_noeof(inp)) << 24; + pktlen |= (hdr[hdrlen++] = iobuf_get_noeof(inp)) << 16; + pktlen |= (hdr[hdrlen++] = iobuf_get_noeof(inp)) << 8; + if( (c = iobuf_get(inp)) == -1 ) + { + log_error("%s: 4 byte length invalid\n", + iobuf_where(inp) ); + rc = G10ERR_INVALID_PACKET; + goto leave; + } + pktlen |= (hdr[hdrlen++] = c ); + } + else + { + /* Partial body length. Note that we handled + PKT_COMPRESSED earlier. */ + if(pkttype==PKT_PLAINTEXT || pkttype==PKT_ENCRYPTED + || pkttype==PKT_ENCRYPTED_MDC) + { + iobuf_set_partial_block_mode(inp, c & 0xff); + pktlen = 0;/* to indicate partial length */ + partial=1; + } + else + { + log_error("%s: partial length for invalid" + " packet type %d\n",iobuf_where(inp),pkttype); + rc=G10ERR_INVALID_PACKET; + goto leave; + } + } + } + } + else + { + pkttype = (ctb>>2)&0xf; + lenbytes = ((ctb&3)==3)? 0 : (1<<(ctb & 3)); + if( !lenbytes ) + { + pktlen = 0; /* don't know the value */ + /* This isn't really partial, but we can treat it the same + in a "read until the end" sort of way. */ + partial=1; + if(pkttype!=PKT_ENCRYPTED && pkttype!=PKT_PLAINTEXT + && pkttype!=PKT_COMPRESSED) + { + log_error ("%s: indeterminate length for invalid" + " packet type %d\n", iobuf_where(inp), pkttype ); + rc = G10ERR_INVALID_PACKET; + goto leave; + } + } + else + { + for( ; lenbytes; lenbytes-- ) + { + pktlen <<= 8; + pktlen |= hdr[hdrlen++] = iobuf_get_noeof(inp); + } + } + } + + if (pktlen == 0xffffffff) { + /* with a some probability this is caused by a problem in the + * the uncompressing layer - in some error cases it just loops + * and spits out 0xff bytes. */ + log_error ("%s: garbled packet detected\n", iobuf_where(inp) ); + g10_exit (2); + } + + if( out && pkttype ) { + if( iobuf_write( out, hdr, hdrlen ) == -1 ) + rc = G10ERR_WRITE_FILE; + else + rc = copy_packet(inp, out, pkttype, pktlen, partial ); + goto leave; + } + + if (with_uid && pkttype == PKT_USER_ID) + ; + else if( do_skip + || !pkttype + || (onlykeypkts && pkttype != PKT_PUBLIC_SUBKEY + && pkttype != PKT_PUBLIC_KEY + && pkttype != PKT_SECRET_SUBKEY + && pkttype != PKT_SECRET_KEY ) ) { + iobuf_skip_rest(inp, pktlen, partial); + *skip = 1; + rc = 0; + goto leave; + } + + if( DBG_PACKET ) { +#ifdef DEBUG_PARSE_PACKET + log_debug("parse_packet(iob=%d): type=%d length=%lu%s (%s.%s.%d)\n", + iobuf_id(inp), pkttype, pktlen, new_ctb?" (new_ctb)":"", + dbg_w, dbg_f, dbg_l ); +#else + log_debug("parse_packet(iob=%d): type=%d length=%lu%s\n", + iobuf_id(inp), pkttype, pktlen, new_ctb?" (new_ctb)":"" ); +#endif + } + pkt->pkttype = pkttype; + rc = G10ERR_UNKNOWN_PACKET; /* default error */ + switch( pkttype ) { + case PKT_PUBLIC_KEY: + case PKT_PUBLIC_SUBKEY: + pkt->pkt.public_key = xmalloc_clear(sizeof *pkt->pkt.public_key ); + rc = parse_key(inp, pkttype, pktlen, hdr, hdrlen, pkt ); + break; + case PKT_SECRET_KEY: + case PKT_SECRET_SUBKEY: + pkt->pkt.secret_key = xmalloc_clear(sizeof *pkt->pkt.secret_key ); + rc = parse_key(inp, pkttype, pktlen, hdr, hdrlen, pkt ); + break; + case PKT_SYMKEY_ENC: + rc = parse_symkeyenc( inp, pkttype, pktlen, pkt ); + break; + case PKT_PUBKEY_ENC: + rc = parse_pubkeyenc(inp, pkttype, pktlen, pkt ); + break; + case PKT_SIGNATURE: + pkt->pkt.signature = xmalloc_clear(sizeof *pkt->pkt.signature ); + rc = parse_signature(inp, pkttype, pktlen, pkt->pkt.signature ); + break; + case PKT_ONEPASS_SIG: + pkt->pkt.onepass_sig = xmalloc_clear(sizeof *pkt->pkt.onepass_sig ); + rc = parse_onepass_sig(inp, pkttype, pktlen, pkt->pkt.onepass_sig ); + break; + case PKT_USER_ID: + rc = parse_user_id(inp, pkttype, pktlen, pkt ); + break; + case PKT_ATTRIBUTE: + pkt->pkttype = pkttype = PKT_USER_ID; /* we store it in the userID */ + rc = parse_attribute(inp, pkttype, pktlen, pkt); + break; + case PKT_OLD_COMMENT: + case PKT_COMMENT: + rc = parse_comment(inp, pkttype, pktlen, pkt); + break; + case PKT_RING_TRUST: + parse_trust(inp, pkttype, pktlen, pkt); + rc = 0; + break; + case PKT_PLAINTEXT: + rc = parse_plaintext(inp, pkttype, pktlen, pkt, new_ctb, partial ); + break; + case PKT_COMPRESSED: + rc = parse_compressed(inp, pkttype, pktlen, pkt, new_ctb ); + break; + case PKT_ENCRYPTED: + case PKT_ENCRYPTED_MDC: + rc = parse_encrypted(inp, pkttype, pktlen, pkt, new_ctb, partial ); + break; + case PKT_MDC: + rc = parse_mdc(inp, pkttype, pktlen, pkt, new_ctb ); + break; + case PKT_GPG_CONTROL: + rc = parse_gpg_control(inp, pkttype, pktlen, pkt, partial ); + break; + default: + skip_packet(inp, pkttype, pktlen, partial); + break; + } + + leave: + if( !rc && iobuf_error(inp) ) + rc = G10ERR_INV_KEYRING; + return rc; +} + +static void +dump_hex_line( int c, int *i ) +{ + if( *i && !(*i%8) ) { + if( *i && !(*i%24) ) + fprintf (listfp, "\n%4d:", *i ); + else + putc (' ', listfp); + } + if( c == -1 ) + fprintf (listfp, " EOF" ); + else + fprintf (listfp, " %02x", c ); + ++*i; +} + + +static int +copy_packet( IOBUF inp, IOBUF out, int pkttype, + unsigned long pktlen, int partial ) +{ + int n; + char buf[100]; + + if( partial ) { + while( (n = iobuf_read( inp, buf, 100 )) != -1 ) + if( iobuf_write(out, buf, n ) ) + return G10ERR_WRITE_FILE; /* write error */ + } + else if( !pktlen && pkttype == PKT_COMPRESSED ) { + log_debug("copy_packet: compressed!\n"); + /* compressed packet, copy till EOF */ + while( (n = iobuf_read( inp, buf, 100 )) != -1 ) + if( iobuf_write(out, buf, n ) ) + return G10ERR_WRITE_FILE; /* write error */ + } + else { + for( ; pktlen; pktlen -= n ) { + n = pktlen > 100 ? 100 : pktlen; + n = iobuf_read( inp, buf, n ); + if( n == -1 ) + return G10ERR_READ_FILE; + if( iobuf_write(out, buf, n ) ) + return G10ERR_WRITE_FILE; /* write error */ + } + } + return 0; +} + + +static void +skip_packet( IOBUF inp, int pkttype, unsigned long pktlen, int partial ) +{ + if( list_mode ) { + if( pkttype == PKT_MARKER ) + fputs(":marker packet:\n", listfp ); + else + fprintf (listfp, ":unknown packet: type %2d, length %lu\n", + pkttype, pktlen); + if( pkttype ) { + int c, i=0 ; + if( pkttype != PKT_MARKER ) + fputs("dump:", listfp ); + if( partial ) { + while( (c=iobuf_get(inp)) != -1 ) + dump_hex_line(c, &i); + } + else { + for( ; pktlen; pktlen-- ) + dump_hex_line(iobuf_get(inp), &i); + } + putc ('\n', listfp); + return; + } + } + iobuf_skip_rest(inp,pktlen,partial); +} + + +static void * +read_rest( IOBUF inp, size_t pktlen, int partial ) +{ + byte *p; + int i; + + if( partial ) { + log_error("read_rest: can't store stream data\n"); + p = NULL; + } + else { + p = xmalloc( pktlen ); + for(i=0; pktlen; pktlen--, i++ ) + p[i] = iobuf_get(inp); + } + return p; +} + + + +static int +parse_symkeyenc( IOBUF inp, int pkttype, unsigned long pktlen, PACKET *packet ) +{ + PKT_symkey_enc *k; + int rc = 0; + int i, version, s2kmode, cipher_algo, hash_algo, seskeylen, minlen; + + if( pktlen < 4 ) { + log_error("packet(%d) too short\n", pkttype); + rc = G10ERR_INVALID_PACKET; + goto leave; + } + version = iobuf_get_noeof(inp); pktlen--; + if( version != 4 ) { + log_error("packet(%d) with unknown version %d\n", pkttype, version); + rc = G10ERR_INVALID_PACKET; + goto leave; + } + if( pktlen > 200 ) { /* (we encode the seskeylen in a byte) */ + log_error("packet(%d) too large\n", pkttype); + rc = G10ERR_INVALID_PACKET; + goto leave; + } + cipher_algo = iobuf_get_noeof(inp); pktlen--; + s2kmode = iobuf_get_noeof(inp); pktlen--; + hash_algo = iobuf_get_noeof(inp); pktlen--; + switch( s2kmode ) { + case 0: /* simple s2k */ + minlen = 0; + break; + case 1: /* salted s2k */ + minlen = 8; + break; + case 3: /* iterated+salted s2k */ + minlen = 9; + break; + default: + log_error("unknown S2K %d\n", s2kmode ); + goto leave; + } + if( minlen > pktlen ) { + log_error("packet with S2K %d too short\n", s2kmode ); + rc = G10ERR_INVALID_PACKET; + goto leave; + } + seskeylen = pktlen - minlen; + k = packet->pkt.symkey_enc = xmalloc_clear( sizeof *packet->pkt.symkey_enc + + seskeylen - 1 ); + k->version = version; + k->cipher_algo = cipher_algo; + k->s2k.mode = s2kmode; + k->s2k.hash_algo = hash_algo; + if( s2kmode == 1 || s2kmode == 3 ) { + for(i=0; i < 8 && pktlen; i++, pktlen-- ) + k->s2k.salt[i] = iobuf_get_noeof(inp); + } + if( s2kmode == 3 ) { + k->s2k.count = iobuf_get(inp); pktlen--; + } + k->seskeylen = seskeylen; + if(k->seskeylen) + { + for(i=0; i < seskeylen && pktlen; i++, pktlen-- ) + k->seskey[i] = iobuf_get_noeof(inp); + + /* What we're watching out for here is a session key decryptor + with no salt. The RFC says that using salt for this is a + MUST. */ + if(s2kmode!=1 && s2kmode!=3) + log_info(_("WARNING: potentially insecure symmetrically" + " encrypted session key\n")); + } + assert( !pktlen ); + + if( list_mode ) { + fprintf (listfp, ":symkey enc packet: version %d, cipher %d, s2k %d, hash %d", + version, cipher_algo, s2kmode, hash_algo); + if(seskeylen) + fprintf (listfp, ", seskey %d bits",(seskeylen-1)*8); + fprintf (listfp, "\n"); + if( s2kmode == 1 || s2kmode == 3 ) { + fprintf (listfp, "\tsalt "); + for(i=0; i < 8; i++ ) + fprintf (listfp, "%02x", k->s2k.salt[i]); + if( s2kmode == 3 ) + fprintf (listfp, ", count %lu (%lu)", + S2K_DECODE_COUNT((ulong)k->s2k.count), + (ulong)k->s2k.count ); + fprintf (listfp, "\n"); + } + } + + leave: + iobuf_skip_rest(inp, pktlen, 0); + return rc; +} + +static int +parse_pubkeyenc( IOBUF inp, int pkttype, unsigned long pktlen, PACKET *packet ) +{ + unsigned int n; + int rc = 0; + int i, ndata; + PKT_pubkey_enc *k; + + k = packet->pkt.pubkey_enc = xmalloc_clear(sizeof *packet->pkt.pubkey_enc); + if( pktlen < 12 ) { + log_error("packet(%d) too short\n", pkttype); + rc = G10ERR_INVALID_PACKET; + goto leave; + } + k->version = iobuf_get_noeof(inp); pktlen--; + if( k->version != 2 && k->version != 3 ) { + log_error("packet(%d) with unknown version %d\n", pkttype, k->version); + rc = G10ERR_INVALID_PACKET; + goto leave; + } + k->keyid[0] = read_32(inp); pktlen -= 4; + k->keyid[1] = read_32(inp); pktlen -= 4; + k->pubkey_algo = iobuf_get_noeof(inp); pktlen--; + k->throw_keyid = 0; /* only used as flag for build_packet */ + if( list_mode ) + fprintf (listfp, ":pubkey enc packet: version %d, algo %d, keyid %08lX%08lX\n", + k->version, k->pubkey_algo, (ulong)k->keyid[0], (ulong)k->keyid[1]); + + ndata = pubkey_get_nenc(k->pubkey_algo); + if( !ndata ) { + if( list_mode ) + fprintf (listfp, "\tunsupported algorithm %d\n", k->pubkey_algo ); + unknown_pubkey_warning( k->pubkey_algo ); + k->data[0] = NULL; /* no need to store the encrypted data */ + } + else { + for( i=0; i < ndata; i++ ) { + n = pktlen; + k->data[i] = mpi_read(inp, &n, 0); pktlen -=n; + if( list_mode ) { + fprintf (listfp, "\tdata: "); + mpi_print(listfp, k->data[i], mpi_print_mode ); + putc ('\n', listfp); + } + if (!k->data[i]) + rc = G10ERR_INVALID_PACKET; + } + } + + leave: + iobuf_skip_rest(inp, pktlen, 0); + return rc; +} + + +static void +dump_sig_subpkt( int hashed, int type, int critical, + const byte *buffer, size_t buflen, size_t length ) +{ + const char *p=NULL; + int i; + + /* The CERT has warning out with explains how to use GNUPG to + * detect the ARRs - we print our old message here when it is a faked + * ARR and add an additional notice */ + if ( type == SIGSUBPKT_ARR && !hashed ) { + fprintf (listfp, + "\tsubpkt %d len %u (additional recipient request)\n" + "WARNING: PGP versions > 5.0 and < 6.5.8 will automagically " + "encrypt to this key and thereby reveal the plaintext to " + "the owner of this ARR key. Detailed info follows:\n", + type, (unsigned)length ); + } + + buffer++; + length--; + + fprintf (listfp, "\t%s%ssubpkt %d len %u (", /*)*/ + critical ? "critical ":"", + hashed ? "hashed ":"", type, (unsigned)length ); + if( length > buflen ) { + fprintf (listfp, "too short: buffer is only %u)\n", (unsigned)buflen ); + return; + } + switch( type ) { + case SIGSUBPKT_SIG_CREATED: + if( length >= 4 ) + fprintf (listfp, "sig created %s", strtimestamp( buffer_to_u32(buffer) ) ); + break; + case SIGSUBPKT_SIG_EXPIRE: + if( length >= 4 ) + { + if(buffer_to_u32(buffer)) + fprintf (listfp, "sig expires after %s", + strtimevalue( buffer_to_u32(buffer) ) ); + else + fprintf (listfp, "sig does not expire"); + } + break; + case SIGSUBPKT_EXPORTABLE: + if( length ) + fprintf (listfp, "%sexportable", *buffer? "":"not "); + break; + case SIGSUBPKT_TRUST: + if(length!=2) + p="[invalid trust subpacket]"; + else + fprintf (listfp, "trust signature of depth %d, value %d",buffer[0],buffer[1]); + break; + case SIGSUBPKT_REGEXP: + if(!length) + p="[invalid regexp subpacket]"; + else + fprintf (listfp, "regular expression: \"%s\"",buffer); + break; + case SIGSUBPKT_REVOCABLE: + if( length ) + fprintf (listfp, "%srevocable", *buffer? "":"not "); + break; + case SIGSUBPKT_KEY_EXPIRE: + if( length >= 4 ) + { + if(buffer_to_u32(buffer)) + fprintf (listfp, "key expires after %s", + strtimevalue( buffer_to_u32(buffer) ) ); + else + fprintf (listfp, "key does not expire"); + } + break; + case SIGSUBPKT_PREF_SYM: + fputs("pref-sym-algos:", listfp ); + for( i=0; i < length; i++ ) + fprintf (listfp, " %d", buffer[i] ); + break; + case SIGSUBPKT_REV_KEY: + fputs("revocation key: ", listfp ); + if( length < 22 ) + p = "[too short]"; + else { + fprintf (listfp, "c=%02x a=%d f=", buffer[0], buffer[1] ); + for( i=2; i < length; i++ ) + fprintf (listfp, "%02X", buffer[i] ); + } + break; + case SIGSUBPKT_ISSUER: + if( length >= 8 ) + fprintf (listfp, "issuer key ID %08lX%08lX", + (ulong)buffer_to_u32(buffer), + (ulong)buffer_to_u32(buffer+4) ); + break; + case SIGSUBPKT_NOTATION: + { + fputs("notation: ", listfp ); + if( length < 8 ) + p = "[too short]"; + else { + const byte *s = buffer; + size_t n1, n2; + + n1 = (s[4] << 8) | s[5]; + n2 = (s[6] << 8) | s[7]; + s += 8; + if( 8+n1+n2 != length ) + p = "[error]"; + else { + print_string( listfp, s, n1, ')' ); + putc( '=', listfp ); + + if( *buffer & 0x80 ) + print_string( listfp, s+n1, n2, ')' ); + else + p = "[not human readable]"; + } + } + } + break; + case SIGSUBPKT_PREF_HASH: + fputs("pref-hash-algos:", listfp ); + for( i=0; i < length; i++ ) + fprintf (listfp, " %d", buffer[i] ); + break; + case SIGSUBPKT_PREF_COMPR: + fputs("pref-zip-algos:", listfp ); + for( i=0; i < length; i++ ) + fprintf (listfp, " %d", buffer[i] ); + break; + case SIGSUBPKT_KS_FLAGS: + fputs("key server preferences:",listfp); + for(i=0;i<length;i++) + fprintf (listfp, " %02X", buffer[i]); + break; + case SIGSUBPKT_PREF_KS: + fputs("preferred key server: ", listfp ); + print_string( listfp, buffer, length, ')' ); + break; + case SIGSUBPKT_PRIMARY_UID: + p = "primary user ID"; + break; + case SIGSUBPKT_POLICY: + fputs("policy: ", listfp ); + print_string( listfp, buffer, length, ')' ); + break; + case SIGSUBPKT_KEY_FLAGS: + fputs ( "key flags:", listfp ); + for( i=0; i < length; i++ ) + fprintf (listfp, " %02X", buffer[i] ); + break; + case SIGSUBPKT_SIGNERS_UID: + p = "signer's user ID"; + break; + case SIGSUBPKT_REVOC_REASON: + if( length ) { + fprintf (listfp, "revocation reason 0x%02x (", *buffer ); + print_string( listfp, buffer+1, length-1, ')' ); + p = ")"; + } + break; + case SIGSUBPKT_ARR: + fputs("Big Brother's key (ignored): ", listfp ); + if( length < 22 ) + p = "[too short]"; + else { + fprintf (listfp, "c=%02x a=%d f=", buffer[0], buffer[1] ); + for( i=2; i < length; i++ ) + fprintf (listfp, "%02X", buffer[i] ); + } + break; + case SIGSUBPKT_FEATURES: + fputs ( "features:", listfp ); + for( i=0; i < length; i++ ) + fprintf (listfp, " %02x", buffer[i] ); + break; + case SIGSUBPKT_SIGNATURE: + fputs("signature: ",listfp); + if(length<17) + p="[too short]"; + else + fprintf (listfp, "v%d, class 0x%02X, algo %d, digest algo %d", + buffer[0], + buffer[0]==3?buffer[2]:buffer[1], + buffer[0]==3?buffer[15]:buffer[2], + buffer[0]==3?buffer[16]:buffer[3]); + break; + default: + if(type>=100 && type<=110) + p="experimental / private subpacket"; + else + p = "?"; + break; + } + + fprintf (listfp, "%s)\n", p? p: ""); +} + +/**************** + * Returns: >= 0 use this offset into buffer + * -1 explicitly reject returning this type + * -2 subpacket too short + */ +int +parse_one_sig_subpkt( const byte *buffer, size_t n, int type ) +{ + switch( type ) + { + case SIGSUBPKT_REV_KEY: + if(n < 22) + break; + return 0; + case SIGSUBPKT_SIG_CREATED: + case SIGSUBPKT_SIG_EXPIRE: + case SIGSUBPKT_KEY_EXPIRE: + if( n < 4 ) + break; + return 0; + case SIGSUBPKT_KEY_FLAGS: + case SIGSUBPKT_KS_FLAGS: + case SIGSUBPKT_PREF_SYM: + case SIGSUBPKT_PREF_HASH: + case SIGSUBPKT_PREF_COMPR: + case SIGSUBPKT_POLICY: + case SIGSUBPKT_PREF_KS: + case SIGSUBPKT_FEATURES: + case SIGSUBPKT_REGEXP: + return 0; + case SIGSUBPKT_SIGNATURE: + case SIGSUBPKT_EXPORTABLE: + case SIGSUBPKT_REVOCABLE: + case SIGSUBPKT_REVOC_REASON: + if( !n ) + break; + return 0; + case SIGSUBPKT_ISSUER: /* issuer key ID */ + if( n < 8 ) + break; + return 0; + case SIGSUBPKT_NOTATION: + /* minimum length needed, and the subpacket must be well-formed + where the name length and value length all fit inside the + packet. */ + if(n<8 || 8+((buffer[4]<<8)|buffer[5])+((buffer[6]<<8)|buffer[7]) != n) + break; + return 0; + case SIGSUBPKT_PRIMARY_UID: + if ( n != 1 ) + break; + return 0; + case SIGSUBPKT_TRUST: + if ( n != 2 ) + break; + return 0; + default: return 0; + } + return -2; +} + +/* Not many critical notations we understand yet... */ +static int +can_handle_critical_notation(const byte *name,size_t len) +{ + if(len==32 && memcmp(name,"preferred-email-encoding@pgp.com",32)==0) + return 1; + if(len==21 && memcmp(name,"pka-address@gnupg.org",21)==0) + return 1; + + return 0; +} + +static int +can_handle_critical( const byte *buffer, size_t n, int type ) +{ + switch( type ) + { + case SIGSUBPKT_NOTATION: + if(n>=8) + return can_handle_critical_notation(buffer+8,(buffer[4]<<8)|buffer[5]); + else + return 0; + case SIGSUBPKT_SIGNATURE: + case SIGSUBPKT_SIG_CREATED: + case SIGSUBPKT_SIG_EXPIRE: + case SIGSUBPKT_KEY_EXPIRE: + case SIGSUBPKT_EXPORTABLE: + case SIGSUBPKT_REVOCABLE: + case SIGSUBPKT_REV_KEY: + case SIGSUBPKT_ISSUER:/* issuer key ID */ + case SIGSUBPKT_PREF_SYM: + case SIGSUBPKT_PREF_HASH: + case SIGSUBPKT_PREF_COMPR: + case SIGSUBPKT_KEY_FLAGS: + case SIGSUBPKT_PRIMARY_UID: + case SIGSUBPKT_FEATURES: + case SIGSUBPKT_TRUST: + case SIGSUBPKT_REGEXP: + /* Is it enough to show the policy or keyserver? */ + case SIGSUBPKT_POLICY: + case SIGSUBPKT_PREF_KS: + return 1; + + default: + return 0; + } +} + + +const byte * +enum_sig_subpkt( const subpktarea_t *pktbuf, sigsubpkttype_t reqtype, + size_t *ret_n, int *start, int *critical ) +{ + const byte *buffer; + int buflen; + int type; + int critical_dummy; + int offset; + size_t n; + int seq = 0; + int reqseq = start? *start: 0; + + if(!critical) + critical=&critical_dummy; + + if( !pktbuf || reqseq == -1 ) { + /* return some value different from NULL to indicate that + * there is no critical bit we do not understand. The caller + * will never use the value. Yes I know, it is an ugly hack */ + return reqtype == SIGSUBPKT_TEST_CRITICAL? (const byte*)&pktbuf : NULL; + } + buffer = pktbuf->data; + buflen = pktbuf->len; + while( buflen ) { + n = *buffer++; buflen--; + if( n == 255 ) { /* 4 byte length header */ + if( buflen < 4 ) + goto too_short; + n = (buffer[0] << 24) | (buffer[1] << 16) + | (buffer[2] << 8) | buffer[3]; + buffer += 4; + buflen -= 4; + } + else if( n >= 192 ) { /* 2 byte special encoded length header */ + if( buflen < 2 ) + goto too_short; + n = (( n - 192 ) << 8) + *buffer + 192; + buffer++; + buflen--; + } + if( buflen < n ) + goto too_short; + type = *buffer; + if( type & 0x80 ) { + type &= 0x7f; + *critical = 1; + } + else + *critical = 0; + if( !(++seq > reqseq) ) + ; + else if( reqtype == SIGSUBPKT_TEST_CRITICAL ) { + if( *critical ) { + if( n-1 > buflen+1 ) + goto too_short; + if( !can_handle_critical(buffer+1, n-1, type ) ) + { + if(opt.verbose) + log_info(_("subpacket of type %d has " + "critical bit set\n"),type); + if( start ) + *start = seq; + return NULL; /* this is an error */ + } + } + } + else if( reqtype < 0 ) /* list packets */ + dump_sig_subpkt( reqtype == SIGSUBPKT_LIST_HASHED, + type, *critical, buffer, buflen, n ); + else if( type == reqtype ) { /* found */ + buffer++; + n--; + if( n > buflen ) + goto too_short; + if( ret_n ) + *ret_n = n; + offset = parse_one_sig_subpkt(buffer, n, type ); + switch( offset ) { + case -2: + log_error("subpacket of type %d too short\n", type); + return NULL; + case -1: + return NULL; + default: + break; + } + if( start ) + *start = seq; + return buffer+offset; + } + buffer += n; buflen -=n; + } + if( reqtype == SIGSUBPKT_TEST_CRITICAL ) + return buffer; /* as value true to indicate that there is no */ + /* critical bit we don't understand */ + if( start ) + *start = -1; + return NULL; /* end of packets; not found */ + + too_short: + if(opt.verbose) + log_info("buffer shorter than subpacket\n"); + if( start ) + *start = -1; + return NULL; +} + + +const byte * +parse_sig_subpkt (const subpktarea_t *buffer, sigsubpkttype_t reqtype, + size_t *ret_n) +{ + return enum_sig_subpkt( buffer, reqtype, ret_n, NULL, NULL ); +} + +const byte * +parse_sig_subpkt2 (PKT_signature *sig, sigsubpkttype_t reqtype, + size_t *ret_n ) +{ + const byte *p; + + p = parse_sig_subpkt (sig->hashed, reqtype, ret_n ); + if( !p ) + p = parse_sig_subpkt (sig->unhashed, reqtype, ret_n ); + return p; +} + +/* Find all revocation keys. Look in hashed area only. */ +void parse_revkeys(PKT_signature *sig) +{ + struct revocation_key *revkey; + int seq=0; + size_t len; + + if(sig->sig_class!=0x1F) + return; + + while((revkey= + (struct revocation_key *)enum_sig_subpkt(sig->hashed, + SIGSUBPKT_REV_KEY, + &len,&seq,NULL))) + { + if(len==sizeof(struct revocation_key) && + (revkey->class&0x80)) /* 0x80 bit must be set */ + { + sig->revkey=xrealloc(sig->revkey, + sizeof(struct revocation_key *)*(sig->numrevkeys+1)); + sig->revkey[sig->numrevkeys]=revkey; + sig->numrevkeys++; + } + } +} + +int +parse_signature( IOBUF inp, int pkttype, unsigned long pktlen, + PKT_signature *sig ) +{ + int md5_len=0; + unsigned n; + int is_v4=0; + int rc=0; + int i, ndata; + + if( pktlen < 16 ) { + log_error("packet(%d) too short\n", pkttype); + goto leave; + } + sig->version = iobuf_get_noeof(inp); pktlen--; + if( sig->version == 4 ) + is_v4=1; + else if( sig->version != 2 && sig->version != 3 ) { + log_error("packet(%d) with unknown version %d\n", pkttype, sig->version); + rc = G10ERR_INVALID_PACKET; + goto leave; + } + + if( !is_v4 ) { + md5_len = iobuf_get_noeof(inp); pktlen--; + } + sig->sig_class = iobuf_get_noeof(inp); pktlen--; + if( !is_v4 ) { + sig->timestamp = read_32(inp); pktlen -= 4; + sig->keyid[0] = read_32(inp); pktlen -= 4; + sig->keyid[1] = read_32(inp); pktlen -= 4; + } + sig->pubkey_algo = iobuf_get_noeof(inp); pktlen--; + sig->digest_algo = iobuf_get_noeof(inp); pktlen--; + sig->flags.exportable=1; + sig->flags.revocable=1; + if( is_v4 ) { /* read subpackets */ + n = read_16(inp); pktlen -= 2; /* length of hashed data */ + if( n > 10000 ) { + log_error("signature packet: hashed data too long\n"); + rc = G10ERR_INVALID_PACKET; + goto leave; + } + if( n ) { + sig->hashed = xmalloc (sizeof (*sig->hashed) + n - 1 ); + sig->hashed->size = n; + sig->hashed->len = n; + if( iobuf_read (inp, sig->hashed->data, n ) != n ) { + log_error ("premature eof while reading " + "hashed signature data\n"); + rc = -1; + goto leave; + } + pktlen -= n; + } + n = read_16(inp); pktlen -= 2; /* length of unhashed data */ + if( n > 10000 ) { + log_error("signature packet: unhashed data too long\n"); + rc = G10ERR_INVALID_PACKET; + goto leave; + } + if( n ) { + sig->unhashed = xmalloc (sizeof(*sig->unhashed) + n - 1 ); + sig->unhashed->size = n; + sig->unhashed->len = n; + if( iobuf_read(inp, sig->unhashed->data, n ) != n ) { + log_error("premature eof while reading " + "unhashed signature data\n"); + rc = -1; + goto leave; + } + pktlen -= n; + } + } + + if( pktlen < 5 ) { /* sanity check */ + log_error("packet(%d) too short\n", pkttype); + rc = G10ERR_INVALID_PACKET; + goto leave; + } + + sig->digest_start[0] = iobuf_get_noeof(inp); pktlen--; + sig->digest_start[1] = iobuf_get_noeof(inp); pktlen--; + + if( is_v4 && sig->pubkey_algo ) + { /*extract required information */ + const byte *p; + size_t len; + + /* set sig->flags.unknown_critical if there is a + * critical bit set for packets which we do not understand */ + if( !parse_sig_subpkt (sig->hashed, SIGSUBPKT_TEST_CRITICAL, NULL) + || !parse_sig_subpkt (sig->unhashed, SIGSUBPKT_TEST_CRITICAL, + NULL) ) + sig->flags.unknown_critical = 1; + + p = parse_sig_subpkt (sig->hashed, SIGSUBPKT_SIG_CREATED, NULL ); + if(p) + sig->timestamp = buffer_to_u32(p); + else if(!(sig->pubkey_algo>=100 && sig->pubkey_algo<=110) + && opt.verbose) + log_info ("signature packet without timestamp\n"); + + p = parse_sig_subpkt2( sig, SIGSUBPKT_ISSUER, NULL ); + if(p) + { + sig->keyid[0] = buffer_to_u32(p); + sig->keyid[1] = buffer_to_u32(p+4); + } + else if(!(sig->pubkey_algo>=100 && sig->pubkey_algo<=110) + && opt.verbose) + log_info ("signature packet without keyid\n"); + + p=parse_sig_subpkt(sig->hashed,SIGSUBPKT_SIG_EXPIRE,NULL); + if(p && buffer_to_u32(p)) + sig->expiredate=sig->timestamp+buffer_to_u32(p); + if(sig->expiredate && sig->expiredate<=make_timestamp()) + sig->flags.expired=1; + + p=parse_sig_subpkt(sig->hashed,SIGSUBPKT_POLICY,NULL); + if(p) + sig->flags.policy_url=1; + + p=parse_sig_subpkt(sig->hashed,SIGSUBPKT_PREF_KS,NULL); + if(p) + sig->flags.pref_ks=1; + + p=parse_sig_subpkt(sig->hashed,SIGSUBPKT_NOTATION,NULL); + if(p) + sig->flags.notation=1; + + p=parse_sig_subpkt(sig->hashed,SIGSUBPKT_REVOCABLE,NULL); + if(p && *p==0) + sig->flags.revocable=0; + + p=parse_sig_subpkt(sig->hashed,SIGSUBPKT_TRUST,&len); + if(p && len==2) + { + sig->trust_depth=p[0]; + sig->trust_value=p[1]; + + /* Only look for a regexp if there is also a trust + subpacket. */ + sig->trust_regexp= + parse_sig_subpkt(sig->hashed,SIGSUBPKT_REGEXP,&len); + + /* If the regular expression is of 0 length, there is no + regular expression. */ + if(len==0) + sig->trust_regexp=NULL; + } + + /* We accept the exportable subpacket from either the hashed + or unhashed areas as older versions of gpg put it in the + unhashed area. In theory, anyway, we should never see this + packet off of a local keyring. */ + + p=parse_sig_subpkt2(sig,SIGSUBPKT_EXPORTABLE,NULL); + if(p && *p==0) + sig->flags.exportable=0; + + /* Find all revocation keys. */ + if(sig->sig_class==0x1F) + parse_revkeys(sig); + } + + if( list_mode ) { + fprintf (listfp, ":signature packet: algo %d, keyid %08lX%08lX\n" + "\tversion %d, created %lu, md5len %d, sigclass %02x\n" + "\tdigest algo %d, begin of digest %02x %02x\n", + sig->pubkey_algo, + (ulong)sig->keyid[0], (ulong)sig->keyid[1], + sig->version, (ulong)sig->timestamp, md5_len, sig->sig_class, + sig->digest_algo, + sig->digest_start[0], sig->digest_start[1] ); + if( is_v4 ) { + parse_sig_subpkt (sig->hashed, SIGSUBPKT_LIST_HASHED, NULL ); + parse_sig_subpkt (sig->unhashed, SIGSUBPKT_LIST_UNHASHED, NULL); + } + } + + ndata = pubkey_get_nsig(sig->pubkey_algo); + if( !ndata ) { + if( list_mode ) + fprintf (listfp, "\tunknown algorithm %d\n", sig->pubkey_algo ); + unknown_pubkey_warning( sig->pubkey_algo ); + /* we store the plain material in data[0], so that we are able + * to write it back with build_packet() */ + sig->data[0]= mpi_set_opaque(NULL, read_rest(inp, pktlen, 0), pktlen ); + pktlen = 0; + } + else { + for( i=0; i < ndata; i++ ) { + n = pktlen; + sig->data[i] = mpi_read(inp, &n, 0 ); + pktlen -=n; + if( list_mode ) { + fprintf (listfp, "\tdata: "); + mpi_print(listfp, sig->data[i], mpi_print_mode ); + putc ('\n', listfp); + } + if (!sig->data[i]) + rc = G10ERR_INVALID_PACKET; + } + } + + leave: + iobuf_skip_rest(inp, pktlen, 0); + return rc; +} + + +static int +parse_onepass_sig( IOBUF inp, int pkttype, unsigned long pktlen, + PKT_onepass_sig *ops ) +{ + int version; + int rc = 0; + + if( pktlen < 13 ) { + log_error("packet(%d) too short\n", pkttype); + rc = G10ERR_INVALID_PACKET; + goto leave; + } + version = iobuf_get_noeof(inp); pktlen--; + if( version != 3 ) { + log_error("onepass_sig with unknown version %d\n", version); + rc = G10ERR_INVALID_PACKET; + goto leave; + } + ops->sig_class = iobuf_get_noeof(inp); pktlen--; + ops->digest_algo = iobuf_get_noeof(inp); pktlen--; + ops->pubkey_algo = iobuf_get_noeof(inp); pktlen--; + ops->keyid[0] = read_32(inp); pktlen -= 4; + ops->keyid[1] = read_32(inp); pktlen -= 4; + ops->last = iobuf_get_noeof(inp); pktlen--; + if( list_mode ) + fprintf (listfp, ":onepass_sig packet: keyid %08lX%08lX\n" + "\tversion %d, sigclass %02x, digest %d, pubkey %d, last=%d\n", + (ulong)ops->keyid[0], (ulong)ops->keyid[1], + version, ops->sig_class, + ops->digest_algo, ops->pubkey_algo, ops->last ); + + + leave: + iobuf_skip_rest(inp, pktlen, 0); + return rc; +} + + +static MPI +read_protected_v3_mpi (IOBUF inp, unsigned long *length) +{ + int c; + unsigned int nbits, nbytes; + unsigned char *buf, *p; + MPI val; + + if (*length < 2) + { + log_error ("mpi too small\n"); + return NULL; + } + + if ((c=iobuf_get (inp)) == -1) + return NULL; + --*length; + nbits = c << 8; + if ((c=iobuf_get(inp)) == -1) + return NULL; + --*length; + nbits |= c; + + if (nbits > 16384) + { + log_error ("mpi too large (%u bits)\n", nbits); + return NULL; + } + nbytes = (nbits+7) / 8; + buf = p = xmalloc (2 + nbytes); + *p++ = nbits >> 8; + *p++ = nbits; + for (; nbytes && length; nbytes--, --*length) + *p++ = iobuf_get (inp); + if (nbytes) + { + log_error ("packet shorter tham mpi\n"); + xfree (buf); + return NULL; + } + + /* convert buffer into an opaque MPI */ + val = mpi_set_opaque (NULL, buf, p-buf); + return val; +} + + +static int +parse_key( IOBUF inp, int pkttype, unsigned long pktlen, + byte *hdr, int hdrlen, PACKET *pkt ) +{ + int i, version, algorithm; + unsigned n; + unsigned long timestamp, expiredate, max_expiredate; + int npkey, nskey; + int is_v4=0; + int rc=0; + + version = iobuf_get_noeof(inp); pktlen--; + if( pkttype == PKT_PUBLIC_SUBKEY && version == '#' ) { + /* early versions of G10 use old PGP comments packets; + * luckily all those comments are started by a hash */ + if( list_mode ) { + fprintf (listfp, ":rfc1991 comment packet: \"" ); + for( ; pktlen; pktlen-- ) { + int c; + c = iobuf_get_noeof(inp); + if( c >= ' ' && c <= 'z' ) + putc (c, listfp); + else + fprintf (listfp, "\\x%02x", c ); + } + fprintf (listfp, "\"\n"); + } + iobuf_skip_rest(inp, pktlen, 0); + return 0; + } + else if( version == 4 ) + is_v4=1; + else if( version != 2 && version != 3 ) { + log_error("packet(%d) with unknown version %d\n", pkttype, version); + rc = G10ERR_INVALID_PACKET; + goto leave; + } + + if( pktlen < 11 ) { + log_error("packet(%d) too short\n", pkttype); + rc = G10ERR_INVALID_PACKET; + goto leave; + } + + timestamp = read_32(inp); pktlen -= 4; + if( is_v4 ) { + expiredate = 0; /* have to get it from the selfsignature */ + max_expiredate = 0; + } + else { + unsigned short ndays; + ndays = read_16(inp); pktlen -= 2; + if( ndays ) + expiredate = timestamp + ndays * 86400L; + else + expiredate = 0; + + max_expiredate=expiredate; + } + algorithm = iobuf_get_noeof(inp); pktlen--; + if( list_mode ) + fprintf (listfp, ":%s key packet:\n" + "\tversion %d, algo %d, created %lu, expires %lu\n", + pkttype == PKT_PUBLIC_KEY? "public" : + pkttype == PKT_SECRET_KEY? "secret" : + pkttype == PKT_PUBLIC_SUBKEY? "public sub" : + pkttype == PKT_SECRET_SUBKEY? "secret sub" : "??", + version, algorithm, timestamp, expiredate ); + + if( pkttype == PKT_SECRET_KEY || pkttype == PKT_SECRET_SUBKEY ) { + PKT_secret_key *sk = pkt->pkt.secret_key; + + sk->timestamp = timestamp; + sk->expiredate = expiredate; + sk->max_expiredate = max_expiredate; + sk->hdrbytes = hdrlen; + sk->version = version; + sk->is_primary = pkttype == PKT_SECRET_KEY; + sk->pubkey_algo = algorithm; + sk->req_usage = 0; + sk->pubkey_usage = 0; /* not yet used */ + } + else { + PKT_public_key *pk = pkt->pkt.public_key; + + pk->timestamp = timestamp; + pk->expiredate = expiredate; + pk->max_expiredate = max_expiredate; + pk->hdrbytes = hdrlen; + pk->version = version; + pk->is_primary = pkttype == PKT_PUBLIC_KEY; + pk->pubkey_algo = algorithm; + pk->req_usage = 0; + pk->pubkey_usage = 0; /* not yet used */ + pk->is_revoked = 0; + pk->is_disabled = 0; + pk->keyid[0] = 0; + pk->keyid[1] = 0; + } + nskey = pubkey_get_nskey( algorithm ); + npkey = pubkey_get_npkey( algorithm ); + if( !npkey ) { + if( list_mode ) + fprintf (listfp, "\tunknown algorithm %d\n", algorithm ); + unknown_pubkey_warning( algorithm ); + } + + + if( pkttype == PKT_SECRET_KEY || pkttype == PKT_SECRET_SUBKEY ) { + PKT_secret_key *sk = pkt->pkt.secret_key; + byte temp[16]; + size_t snlen = 0; + + if( !npkey ) { + sk->skey[0] = mpi_set_opaque( NULL, + read_rest(inp, pktlen, 0), pktlen ); + pktlen = 0; + goto leave; + } + + for(i=0; i < npkey; i++ ) { + n = pktlen; sk->skey[i] = mpi_read(inp, &n, 0 ); pktlen -=n; + if( list_mode ) { + fprintf (listfp, "\tskey[%d]: ", i); + mpi_print(listfp, sk->skey[i], mpi_print_mode ); + putc ('\n', listfp); + } + if (!sk->skey[i]) + rc = G10ERR_INVALID_PACKET; + } + if (rc) /* one of the MPIs were bad */ + goto leave; + sk->protect.algo = iobuf_get_noeof(inp); pktlen--; + sk->protect.sha1chk = 0; + if( sk->protect.algo ) { + sk->is_protected = 1; + sk->protect.s2k.count = 0; + if( sk->protect.algo == 254 || sk->protect.algo == 255 ) { + if( pktlen < 3 ) { + rc = G10ERR_INVALID_PACKET; + goto leave; + } + sk->protect.sha1chk = (sk->protect.algo == 254); + sk->protect.algo = iobuf_get_noeof(inp); pktlen--; + /* Note that a sk->protect.algo > 110 is illegal, but + I'm not erroring on it here as otherwise there + would be no way to delete such a key. */ + sk->protect.s2k.mode = iobuf_get_noeof(inp); pktlen--; + sk->protect.s2k.hash_algo = iobuf_get_noeof(inp); pktlen--; + /* check for the special GNU extension */ + if( is_v4 && sk->protect.s2k.mode == 101 ) { + for(i=0; i < 4 && pktlen; i++, pktlen-- ) + temp[i] = iobuf_get_noeof(inp); + if( i < 4 || memcmp( temp, "GNU", 3 ) ) { + if( list_mode ) + fprintf (listfp, "\tunknown S2K %d\n", + sk->protect.s2k.mode ); + rc = G10ERR_INVALID_PACKET; + goto leave; + } + /* here we know that it is a gnu extension + * What follows is the GNU protection mode: + * All values have special meanings + * and they are mapped in the mode with a base of 1000. + */ + sk->protect.s2k.mode = 1000 + temp[3]; + } + switch( sk->protect.s2k.mode ) { + case 1: + case 3: + for(i=0; i < 8 && pktlen; i++, pktlen-- ) + temp[i] = iobuf_get_noeof(inp); + memcpy(sk->protect.s2k.salt, temp, 8 ); + break; + } + switch( sk->protect.s2k.mode ) { + case 0: if( list_mode ) fprintf (listfp, "\tsimple S2K" ); + break; + case 1: if( list_mode ) fprintf (listfp, "\tsalted S2K" ); + break; + case 3: if( list_mode ) fprintf (listfp, "\titer+salt S2K" ); + break; + case 1001: if( list_mode ) fprintf (listfp, + "\tgnu-dummy S2K" ); + break; + case 1002: if (list_mode) fprintf (listfp, + "\tgnu-divert-to-card S2K"); + break; + default: + if( list_mode ) + fprintf (listfp, "\tunknown %sS2K %d\n", + sk->protect.s2k.mode < 1000? "":"GNU ", + sk->protect.s2k.mode ); + rc = G10ERR_INVALID_PACKET; + goto leave; + } + + if( list_mode ) { + fprintf (listfp, ", algo: %d,%s hash: %d", + sk->protect.algo, + sk->protect.sha1chk?" SHA1 protection," + :" simple checksum,", + sk->protect.s2k.hash_algo ); + if( sk->protect.s2k.mode == 1 + || sk->protect.s2k.mode == 3 ) { + fprintf (listfp, ", salt: "); + for(i=0; i < 8; i++ ) + fprintf (listfp, "%02x", sk->protect.s2k.salt[i]); + } + putc ('\n', listfp); + } + + if( sk->protect.s2k.mode == 3 ) { + if( pktlen < 1 ) { + rc = G10ERR_INVALID_PACKET; + goto leave; + } + sk->protect.s2k.count = iobuf_get(inp); + pktlen--; + if( list_mode ) + fprintf (listfp, "\tprotect count: %lu\n", + (ulong)sk->protect.s2k.count); + } + else if( sk->protect.s2k.mode == 1002 ) { + /* Read the serial number. */ + if (pktlen < 1) { + rc = G10ERR_INVALID_PACKET; + goto leave; + } + snlen = iobuf_get (inp); + pktlen--; + if (pktlen < snlen || snlen == -1) { + rc = G10ERR_INVALID_PACKET; + goto leave; + } + } + } + /* Note that a sk->protect.algo > 110 is illegal, but I'm + not erroring on it here as otherwise there would be no + way to delete such a key. */ + else { /* old version; no S2K, so we set mode to 0, hash MD5 */ + sk->protect.s2k.mode = 0; + sk->protect.s2k.hash_algo = DIGEST_ALGO_MD5; + if( list_mode ) + fprintf (listfp, "\tprotect algo: %d (hash algo: %d)\n", + sk->protect.algo, sk->protect.s2k.hash_algo ); + } + /* It is really ugly that we don't know the size + * of the IV here in cases we are not aware of the algorithm. + * so a + * sk->protect.ivlen = cipher_get_blocksize(sk->protect.algo); + * won't work. The only solution I see is to hardwire it here. + * NOTE: if you change the ivlen above 16, don't forget to + * enlarge temp. + */ + switch( sk->protect.algo ) { + case 7: case 8: case 9: /* reserved for AES */ + case 10: /* Twofish */ + sk->protect.ivlen = 16; + break; + default: + sk->protect.ivlen = 8; + } + if( sk->protect.s2k.mode == 1001 ) + sk->protect.ivlen = 0; + else if( sk->protect.s2k.mode == 1002 ) + sk->protect.ivlen = snlen < 16? snlen : 16; + + if( pktlen < sk->protect.ivlen ) { + rc = G10ERR_INVALID_PACKET; + goto leave; + } + for(i=0; i < sk->protect.ivlen && pktlen; i++, pktlen-- ) + temp[i] = iobuf_get_noeof(inp); + if( list_mode ) { + fprintf (listfp, + sk->protect.s2k.mode == 1002? "\tserial-number: " + : "\tprotect IV: "); + for(i=0; i < sk->protect.ivlen; i++ ) + fprintf (listfp, " %02x", temp[i] ); + putc ('\n', listfp); + } + memcpy(sk->protect.iv, temp, sk->protect.ivlen ); + } + else + sk->is_protected = 0; + /* It does not make sense to read it into secure memory. + * If the user is so careless, not to protect his secret key, + * we can assume, that he operates an open system :=(. + * So we put the key into secure memory when we unprotect it. */ + if( sk->protect.s2k.mode == 1001 + || sk->protect.s2k.mode == 1002 ) { + /* better set some dummy stuff here */ + sk->skey[npkey] = mpi_set_opaque(NULL, xstrdup("dummydata"), 10); + pktlen = 0; + } + else if( is_v4 && sk->is_protected ) { + /* ugly; the length is encrypted too, so we read all + * stuff up to the end of the packet into the first + * skey element */ + sk->skey[npkey] = mpi_set_opaque(NULL, + read_rest(inp, pktlen, 0),pktlen); + pktlen = 0; + if( list_mode ) { + fprintf (listfp, "\tencrypted stuff follows\n"); + } + } + else { /* v3 method: the mpi length is not encrypted */ + for(i=npkey; i < nskey; i++ ) { + if ( sk->is_protected ) { + sk->skey[i] = read_protected_v3_mpi (inp, &pktlen); + if( list_mode ) + fprintf (listfp, "\tskey[%d]: [encrypted]\n", i); + } + else { + n = pktlen; + sk->skey[i] = mpi_read(inp, &n, 0 ); + pktlen -=n; + if( list_mode ) { + fprintf (listfp, "\tskey[%d]: ", i); + mpi_print(listfp, sk->skey[i], mpi_print_mode ); + putc ('\n', listfp); + } + } + + if (!sk->skey[i]) + rc = G10ERR_INVALID_PACKET; + } + if (rc) + goto leave; + + sk->csum = read_16(inp); pktlen -= 2; + if( list_mode ) { + fprintf (listfp, "\tchecksum: %04hx\n", sk->csum); + } + } + } + else { + PKT_public_key *pk = pkt->pkt.public_key; + + if( !npkey ) { + pk->pkey[0] = mpi_set_opaque( NULL, + read_rest(inp, pktlen, 0), pktlen ); + pktlen = 0; + goto leave; + } + + for(i=0; i < npkey; i++ ) { + n = pktlen; pk->pkey[i] = mpi_read(inp, &n, 0 ); pktlen -=n; + if( list_mode ) { + fprintf (listfp, "\tpkey[%d]: ", i); + mpi_print(listfp, pk->pkey[i], mpi_print_mode ); + putc ('\n', listfp); + } + if (!pk->pkey[i]) + rc = G10ERR_INVALID_PACKET; + } + if (rc) + goto leave; + } + + leave: + iobuf_skip_rest(inp, pktlen, 0); + return rc; +} + +/* Attribute subpackets have the same format as v4 signature + subpackets. This is not part of OpenPGP, but is done in several + versions of PGP nevertheless. */ +int +parse_attribute_subpkts(PKT_user_id *uid) +{ + size_t n; + int count=0; + struct user_attribute *attribs=NULL; + const byte *buffer=uid->attrib_data; + int buflen=uid->attrib_len; + byte type; + + xfree(uid->attribs); + + while(buflen) + { + n = *buffer++; buflen--; + if( n == 255 ) { /* 4 byte length header */ + if( buflen < 4 ) + goto too_short; + n = (buffer[0] << 24) | (buffer[1] << 16) + | (buffer[2] << 8) | buffer[3]; + buffer += 4; + buflen -= 4; + } + else if( n >= 192 ) { /* 2 byte special encoded length header */ + if( buflen < 2 ) + goto too_short; + n = (( n - 192 ) << 8) + *buffer + 192; + buffer++; + buflen--; + } + if( buflen < n ) + goto too_short; + + attribs=xrealloc(attribs,(count+1)*sizeof(struct user_attribute)); + memset(&attribs[count],0,sizeof(struct user_attribute)); + + type=*buffer; + buffer++; + buflen--; + n--; + + attribs[count].type=type; + attribs[count].data=buffer; + attribs[count].len=n; + buffer+=n; + buflen-=n; + count++; + } + + uid->attribs=attribs; + uid->numattribs=count; + return count; + + too_short: + if(opt.verbose) + log_info("buffer shorter than attribute subpacket\n"); + uid->attribs=attribs; + uid->numattribs=count; + return count; +} + + +static int +parse_user_id( IOBUF inp, int pkttype, unsigned long pktlen, PACKET *packet ) +{ + byte *p; + + /* Cap the size of a user ID at 2k: a value absurdly large enough + that there is no sane user ID string (which is printable text + as of RFC2440bis) that won't fit in it, but yet small enough to + avoid allocation problems. A large pktlen may not be + allocatable, and a very large pktlen could actually cause our + allocation to wrap around in xmalloc to a small number. */ + + if(pktlen>2048) + { + log_error("packet(%d) too large\n", pkttype); + iobuf_skip_rest(inp, pktlen, 0); + return G10ERR_INVALID_PACKET; + } + + packet->pkt.user_id = xmalloc_clear(sizeof *packet->pkt.user_id + pktlen); + packet->pkt.user_id->len = pktlen; + packet->pkt.user_id->ref=1; + + p = packet->pkt.user_id->name; + for( ; pktlen; pktlen--, p++ ) + *p = iobuf_get_noeof(inp); + *p = 0; + + if( list_mode ) { + int n = packet->pkt.user_id->len; + fprintf (listfp, ":user ID packet: \""); + /* fixme: Hey why don't we replace this with print_string?? */ + for(p=packet->pkt.user_id->name; n; p++, n-- ) { + if( *p >= ' ' && *p <= 'z' ) + putc (*p, listfp); + else + fprintf (listfp, "\\x%02x", *p ); + } + fprintf (listfp, "\"\n"); + } + return 0; +} + + +void +make_attribute_uidname(PKT_user_id *uid, size_t max_namelen) +{ + assert ( max_namelen > 70 ); + if(uid->numattribs<=0) + sprintf(uid->name,"[bad attribute packet of size %lu]",uid->attrib_len); + else if(uid->numattribs>1) + sprintf(uid->name,"[%d attributes of size %lu]", + uid->numattribs,uid->attrib_len); + else + { + /* Only one attribute, so list it as the "user id" */ + + if(uid->attribs->type==ATTRIB_IMAGE) + { + u32 len; + byte type; + + if(parse_image_header(uid->attribs,&type,&len)) + sprintf(uid->name,"[%.20s image of size %lu]", + image_type_to_string(type,1),(ulong)len); + else + sprintf(uid->name,"[invalid image]"); + } + else + sprintf(uid->name,"[unknown attribute of size %lu]", + (ulong)uid->attribs->len); + } + + uid->len = strlen(uid->name); +} + +static int +parse_attribute( IOBUF inp, int pkttype, unsigned long pktlen, PACKET *packet ) +{ + byte *p; + +#define EXTRA_UID_NAME_SPACE 71 + packet->pkt.user_id = xmalloc_clear(sizeof *packet->pkt.user_id + + EXTRA_UID_NAME_SPACE); + packet->pkt.user_id->ref=1; + packet->pkt.user_id->attrib_data = xmalloc(pktlen); + packet->pkt.user_id->attrib_len = pktlen; + + p = packet->pkt.user_id->attrib_data; + for( ; pktlen; pktlen--, p++ ) + *p = iobuf_get_noeof(inp); + + /* Now parse out the individual attribute subpackets. This is + somewhat pointless since there is only one currently defined + attribute type (jpeg), but it is correct by the spec. */ + parse_attribute_subpkts(packet->pkt.user_id); + + make_attribute_uidname(packet->pkt.user_id, EXTRA_UID_NAME_SPACE); + + if( list_mode ) { + fprintf (listfp, ":attribute packet: %s\n", packet->pkt.user_id->name ); + } + return 0; +} + + +static int +parse_comment( IOBUF inp, int pkttype, unsigned long pktlen, PACKET *packet ) +{ + byte *p; + + /* Cap comment packet at a reasonable value to avoid an integer + overflow in the malloc below. Comment packets are actually not + anymore define my OpenPGP and we even stopped to use our + private comment packet. */ + if (pktlen>65536) + { + log_error ("packet(%d) too large\n", pkttype); + iobuf_skip_rest (inp, pktlen, 0); + return G10ERR_INVALID_PACKET; + } + packet->pkt.comment = xmalloc(sizeof *packet->pkt.comment + pktlen - 1); + packet->pkt.comment->len = pktlen; + p = packet->pkt.comment->data; + for( ; pktlen; pktlen--, p++ ) + *p = iobuf_get_noeof(inp); + + if( list_mode ) { + int n = packet->pkt.comment->len; + fprintf (listfp, ":%scomment packet: \"", pkttype == PKT_OLD_COMMENT? + "OpenPGP draft " : "GnuPG " ); + for(p=packet->pkt.comment->data; n; p++, n-- ) { + if( *p >= ' ' && *p <= 'z' ) + putc (*p, listfp); + else + fprintf (listfp, "\\x%02x", *p ); + } + fprintf (listfp, "\"\n"); + } + return 0; +} + + +static void +parse_trust( IOBUF inp, int pkttype, unsigned long pktlen, PACKET *pkt ) +{ + int c; + + if (pktlen) + { + c = iobuf_get_noeof(inp); + pktlen--; + pkt->pkt.ring_trust = xmalloc( sizeof *pkt->pkt.ring_trust ); + pkt->pkt.ring_trust->trustval = c; + pkt->pkt.ring_trust->sigcache = 0; + if (!c && pktlen==1) + { + c = iobuf_get_noeof (inp); + pktlen--; + /* we require that bit 7 of the sigcache is 0 (easier eof handling)*/ + if ( !(c & 0x80) ) + pkt->pkt.ring_trust->sigcache = c; + } + if( list_mode ) + fprintf (listfp, ":trust packet: flag=%02x sigcache=%02x\n", + pkt->pkt.ring_trust->trustval, + pkt->pkt.ring_trust->sigcache); + } + else + { + if( list_mode ) + fprintf (listfp, ":trust packet: empty\n"); + } + iobuf_skip_rest (inp, pktlen, 0); +} + + +static int +parse_plaintext( IOBUF inp, int pkttype, unsigned long pktlen, + PACKET *pkt, int new_ctb, int partial ) +{ + int rc = 0; + int mode, namelen; + PKT_plaintext *pt; + byte *p; + int c, i; + + if( !partial && pktlen < 6 ) { + log_error("packet(%d) too short (%lu)\n", pkttype, (ulong)pktlen); + rc = G10ERR_INVALID_PACKET; + goto leave; + } + mode = iobuf_get_noeof(inp); if( pktlen ) pktlen--; + namelen = iobuf_get_noeof(inp); if( pktlen ) pktlen--; + /* Note that namelen will never exceeds 255 byte. */ + pt = pkt->pkt.plaintext = xmalloc(sizeof *pkt->pkt.plaintext + namelen -1); + pt->new_ctb = new_ctb; + pt->mode = mode; + pt->namelen = namelen; + pt->is_partial = partial; + if( pktlen ) { + for( i=0; pktlen > 4 && i < namelen; pktlen--, i++ ) + pt->name[i] = iobuf_get_noeof(inp); + } + else { + for( i=0; i < namelen; i++ ) + if( (c=iobuf_get(inp)) == -1 ) + break; + else + pt->name[i] = c; + } + pt->timestamp = read_32(inp); if( pktlen) pktlen -= 4; + pt->len = pktlen; + pt->buf = inp; + pktlen = 0; + + if( list_mode ) { + fprintf (listfp, ":literal data packet:\n" + "\tmode %c (%X), created %lu, name=\"", + mode >= ' ' && mode <'z'? mode : '?', mode, + (ulong)pt->timestamp ); + for(p=pt->name,i=0; i < namelen; p++, i++ ) { + if( *p >= ' ' && *p <= 'z' ) + putc (*p, listfp); + else + fprintf (listfp, "\\x%02x", *p ); + } + fprintf (listfp, "\",\n\traw data: "); + if(partial) + fprintf (listfp, "unknown length\n"); + else + fprintf (listfp, "%lu bytes\n", (ulong)pt->len ); + } + + leave: + return rc; +} + + +static int +parse_compressed( IOBUF inp, int pkttype, unsigned long pktlen, + PACKET *pkt, int new_ctb ) +{ + PKT_compressed *zd; + + /* pktlen is here 0, but data follows + * (this should be the last object in a file or + * the compress algorithm should know the length) + */ + zd = pkt->pkt.compressed = xmalloc(sizeof *pkt->pkt.compressed ); + zd->algorithm = iobuf_get_noeof(inp); + zd->len = 0; /* not used */ + zd->new_ctb = new_ctb; + zd->buf = inp; + if( list_mode ) + fprintf (listfp, ":compressed packet: algo=%d\n", zd->algorithm); + return 0; +} + + +static int +parse_encrypted( IOBUF inp, int pkttype, unsigned long pktlen, + PACKET *pkt, int new_ctb, int partial ) +{ + int rc = 0; + PKT_encrypted *ed; + unsigned long orig_pktlen = pktlen; + + ed = pkt->pkt.encrypted = xmalloc(sizeof *pkt->pkt.encrypted ); + ed->len = pktlen; + /* we don't know the extralen which is (cipher_blocksize+2) + because the algorithm ist not specified in this packet. + However, it is only important to know this for some sanity + checks on the packet length - it doesn't matter that we can't + do it */ + ed->extralen = 0; + ed->buf = NULL; + ed->new_ctb = new_ctb; + ed->is_partial = partial; + ed->mdc_method = 0; + if( pkttype == PKT_ENCRYPTED_MDC ) { + /* fixme: add some pktlen sanity checks */ + int version; + + version = iobuf_get_noeof(inp); + if (orig_pktlen) + pktlen--; + if( version != 1 ) { + log_error("encrypted_mdc packet with unknown version %d\n", + version); + /*skip_rest(inp, pktlen); should we really do this? */ + rc = G10ERR_INVALID_PACKET; + goto leave; + } + ed->mdc_method = DIGEST_ALGO_SHA1; + } + if( orig_pktlen && pktlen < 10 ) { /* actually this is blocksize+2 */ + log_error("packet(%d) too short\n", pkttype); + rc = G10ERR_INVALID_PACKET; + iobuf_skip_rest(inp, pktlen, partial); + goto leave; + } + if( list_mode ) { + if( orig_pktlen ) + fprintf (listfp, ":encrypted data packet:\n\tlength: %lu\n", + orig_pktlen); + else + fprintf (listfp, ":encrypted data packet:\n\tlength: unknown\n"); + if( ed->mdc_method ) + fprintf (listfp, "\tmdc_method: %d\n", ed->mdc_method ); + } + + ed->buf = inp; + + leave: + return rc; +} + + +static int +parse_mdc( IOBUF inp, int pkttype, unsigned long pktlen, + PACKET *pkt, int new_ctb ) +{ + int rc = 0; + PKT_mdc *mdc; + byte *p; + + mdc = pkt->pkt.mdc= xmalloc(sizeof *pkt->pkt.mdc ); + if( list_mode ) + fprintf (listfp, ":mdc packet: length=%lu\n", pktlen); + if( !new_ctb || pktlen != 20 ) { + log_error("mdc_packet with invalid encoding\n"); + rc = G10ERR_INVALID_PACKET; + goto leave; + } + p = mdc->hash; + for( ; pktlen; pktlen--, p++ ) + *p = iobuf_get_noeof(inp); + + leave: + return rc; +} + + +/* + * This packet is internally generated by GPG (by armor.c) to + * transfer some information to the lower layer. To make sure that + * this packet is really a GPG faked one and not one comming from outside, + * we first check that there is a unique tag in it. + * The format of such a control packet is: + * n byte session marker + * 1 byte control type CTRLPKT_xxxxx + * m byte control data + */ + +static int +parse_gpg_control( IOBUF inp, int pkttype, + unsigned long pktlen, PACKET *packet, int partial ) +{ + byte *p; + const byte *sesmark; + size_t sesmarklen; + int i; + + if ( list_mode ) + fprintf (listfp, ":packet 63: length %lu ", pktlen); + + sesmark = get_session_marker ( &sesmarklen ); + if ( pktlen < sesmarklen+1 ) /* 1 is for the control bytes */ + goto skipit; + for( i=0; i < sesmarklen; i++, pktlen-- ) { + if ( sesmark[i] != iobuf_get_noeof(inp) ) + goto skipit; + } + if (pktlen > 4096) + goto skipit; /* Definitely too large. We skip it to avoid an + overflow in the malloc. */ + if ( list_mode ) + puts ("- gpg control packet"); + + packet->pkt.gpg_control = xmalloc(sizeof *packet->pkt.gpg_control + + pktlen - 1); + packet->pkt.gpg_control->control = iobuf_get_noeof(inp); pktlen--; + packet->pkt.gpg_control->datalen = pktlen; + p = packet->pkt.gpg_control->data; + for( ; pktlen; pktlen--, p++ ) + *p = iobuf_get_noeof(inp); + + return 0; + + skipit: + if ( list_mode ) { + int c; + + i=0; + fprintf (listfp, "- private (rest length %lu)\n", pktlen); + if( partial ) { + while( (c=iobuf_get(inp)) != -1 ) + dump_hex_line(c, &i); + } + else { + for( ; pktlen; pktlen-- ) + dump_hex_line(iobuf_get(inp), &i); + } + putc ('\n', listfp); + } + iobuf_skip_rest(inp,pktlen, 0); + return G10ERR_INVALID_PACKET; +} + +/* create a gpg control packet to be used internally as a placeholder */ +PACKET * +create_gpg_control( ctrlpkttype_t type, const byte *data, size_t datalen ) +{ + PACKET *packet; + byte *p; + + packet = xmalloc( sizeof *packet ); + init_packet(packet); + packet->pkttype = PKT_GPG_CONTROL; + packet->pkt.gpg_control = xmalloc(sizeof *packet->pkt.gpg_control + + datalen - 1); + packet->pkt.gpg_control->control = type; + packet->pkt.gpg_control->datalen = datalen; + p = packet->pkt.gpg_control->data; + for( ; datalen; datalen--, p++ ) + *p = *data++; + + return packet; +} diff --git a/g10/passphrase.c b/g10/passphrase.c new file mode 100644 index 0000000..4dd2f83 --- /dev/null +++ b/g10/passphrase.c @@ -0,0 +1,1084 @@ +/* passphrase.c - Get a passphrase + * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, + * 2006 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#include <config.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <assert.h> +#if !defined(HAVE_DOSISH_SYSTEM) && !defined(__riscos__) +#include <sys/socket.h> +#include <sys/un.h> +#endif +#if defined (_WIN32) +#include <windows.h> +#endif +#include <errno.h> +#ifdef HAVE_LOCALE_H +#include <locale.h> +#endif +#ifdef HAVE_LANGINFO_CODESET +#include <langinfo.h> +#endif + +#include "util.h" +#include "memory.h" +#include "options.h" +#include "ttyio.h" +#include "cipher.h" +#include "keydb.h" +#include "main.h" +#include "i18n.h" +#include "status.h" +#ifdef ENABLE_AGENT_SUPPORT +#include "assuan.h" +#endif /*ENABLE_AGENT_SUPPORT*/ + +static char *fd_passwd = NULL; +static char *next_pw = NULL; +static char *last_pw = NULL; + +static void hash_passphrase( DEK *dek, char *pw, STRING2KEY *s2k, int create ); + +int +have_static_passphrase() +{ + if ( opt.use_agent ) + return 0; + return !!fd_passwd; +} + +/**************** + * Set the passphrase to be used for the next query and only for the next + * one. + */ +void +set_next_passphrase( const char *s ) +{ + xfree(next_pw); + next_pw = NULL; + if( s ) { + next_pw = xmalloc_secure( strlen(s)+1 ); + strcpy(next_pw, s ); + } +} + +/**************** + * Get the last passphrase used in passphrase_to_dek. + * Note: This removes the passphrase from this modules and + * the caller must free the result. May return NULL: + */ +char * +get_last_passphrase() +{ + char *p = last_pw; + last_pw = NULL; + return p; +} + +/* As if we had used the passphrase - make it the last_pw. */ +void +next_to_last_passphrase(void) +{ + if(next_pw) + { + last_pw=next_pw; + next_pw=NULL; + } +} + +/* Here's an interesting question: since this passphrase was passed in + on the command line, is there really any point in using secure + memory for it? I'm going with 'yes', since it doesn't hurt, and + might help in some small way (swapping). */ + +void +set_passphrase_from_string(const char *pass) +{ + xfree( fd_passwd ); + fd_passwd = xmalloc_secure(strlen(pass)+1); + strcpy(fd_passwd,pass); +} + + +void +read_passphrase_from_fd( int fd ) +{ + int i, len; + char *pw; + + if ( opt.use_agent ) + { /* Not used but we have to do a dummy read, so that it won't end + up at the begin of the message if the quite usual trick to + prepend the passphtrase to the message is used. */ + char buf[1]; + + while (!(read (fd, buf, 1) != 1 || *buf == '\n' )) + ; + *buf = 0; + return; + } + + if (!opt.batch ) + tty_printf("Reading passphrase from file descriptor %d ...", fd ); + for (pw = NULL, i = len = 100; ; i++ ) + { + if (i >= len-1 ) + { + char *pw2 = pw; + len += 100; + pw = xmalloc_secure( len ); + if( pw2 ) + { + memcpy(pw, pw2, i ); + xfree (pw2); + } + else + i=0; + } + if (read( fd, pw+i, 1) != 1 || pw[i] == '\n' ) + break; + } + pw[i] = 0; + if (!opt.batch) + tty_printf("\b\b\b \n" ); + + xfree( fd_passwd ); + fd_passwd = pw; +} + + + +#ifdef ENABLE_AGENT_SUPPORT +/* Send one option to the gpg-agent. */ +static int +agent_send_option (assuan_context_t ctx, const char *name, const char *value) +{ + char *line; + int rc; + + if (!value || !*value) + return 0; /* Avoid sending empty option values. */ + + line = xmalloc (7 + strlen (name) + 1 + strlen (value) + 1); + strcpy (stpcpy (stpcpy (stpcpy (line, "OPTION "), name), "="), value); + rc = assuan_transact (ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); + xfree (line); + return rc? -1 : 0; +} + +/* Send all required options to the gpg-agent. */ +static int +agent_send_all_options (assuan_context_t ctx) +{ + char *dft_display = NULL; + const char *dft_ttyname = NULL; + char *dft_ttytype = NULL; + char *old_lc = NULL; + char *dft_lc = NULL; + int rc = 0; + + dft_display = getenv ("DISPLAY"); + if (opt.display || dft_display) + { + if (agent_send_option (ctx, "display", + opt.display ? opt.display : dft_display)) + return -1; + } + + if (!opt.ttyname) + { + const char *tmp; + + dft_ttyname = getenv ("GPG_TTY"); + if ((!dft_ttyname || !*dft_ttyname) && (tmp=ttyname (0))) + dft_ttyname = tmp; + if ((!dft_ttyname || !*dft_ttyname) && (tmp=tty_get_ttyname ())) + dft_ttyname = tmp; + } + if (opt.ttyname || dft_ttyname) + { + if (agent_send_option (ctx, "ttyname", + opt.ttyname ? opt.ttyname : dft_ttyname)) + return -1; + } + + dft_ttytype = getenv ("TERM"); + if (opt.ttytype || (dft_ttyname && dft_ttytype)) + { + if (agent_send_option (ctx, "ttytype", + opt.ttyname ? opt.ttytype : dft_ttytype)) + return -1; + } + +#if defined(HAVE_SETLOCALE) && defined(LC_CTYPE) + old_lc = setlocale (LC_CTYPE, NULL); + if (old_lc) + old_lc = xstrdup (old_lc); + dft_lc = setlocale (LC_CTYPE, ""); +#endif + if (opt.lc_ctype || (dft_ttyname && dft_lc)) + { + rc = agent_send_option (ctx, "lc-ctype", + opt.lc_ctype ? opt.lc_ctype : dft_lc); + } +#if defined(HAVE_SETLOCALE) && defined(LC_CTYPE) + if (old_lc) + { + setlocale (LC_CTYPE, old_lc); + xfree (old_lc); + } +#endif + if (rc) + return rc; + +#if defined(HAVE_SETLOCALE) && defined(LC_MESSAGES) + old_lc = setlocale (LC_MESSAGES, NULL); + if (old_lc) + old_lc = xstrdup (old_lc); + dft_lc = setlocale (LC_MESSAGES, ""); +#endif + if (opt.lc_messages || (dft_ttyname && dft_lc)) + { + rc = agent_send_option (ctx, "lc-messages", + opt.lc_messages ? opt.lc_messages : dft_lc); + } +#if defined(HAVE_SETLOCALE) && defined(LC_MESSAGES) + if (old_lc) + { + setlocale (LC_MESSAGES, old_lc); + xfree (old_lc); + } +#endif + return rc; +} +#endif /*ENABLE_AGENT_SUPPORT*/ + + +/* + * Open a connection to the agent and initializes the connection. + * Returns: -1 on error; on success an Assuan context for that + * connection is returned. With TRY set to true, no error messages + * are printed and the use of the agent won't get disabled on failure. + * If ORIG_CODESET is not NULL, the function will swithc the codeset + * back to that one before printing error messages. + */ +#ifdef ENABLE_AGENT_SUPPORT +assuan_context_t +agent_open (int try, const char *orig_codeset) +{ + int rc; + assuan_context_t ctx; + char *infostr, *p; + int prot; + int pid; + + if (opt.gpg_agent_info) + infostr = xstrdup (opt.gpg_agent_info); + else + { + infostr = getenv ( "GPG_AGENT_INFO" ); + if (!infostr || !*infostr) + { + if (!try) + { +#ifdef ENABLE_NLS + if (orig_codeset) + bind_textdomain_codeset (PACKAGE, orig_codeset); +#endif /*ENABLE_NLS*/ + log_info (_("gpg-agent is not available in this session\n")); + opt.use_agent = 0; + } + return NULL; + } + infostr = xstrdup ( infostr ); + } + + if ( !(p = strchr (infostr, PATHSEP_C)) || p == infostr) + { + if (!try) + { +#ifdef ENABLE_NLS + if (orig_codeset) + bind_textdomain_codeset (PACKAGE, orig_codeset); +#endif /*ENABLE_NLS*/ + log_error ( _("malformed GPG_AGENT_INFO environment variable\n")); + opt.use_agent = 0; + } + xfree (infostr); + return NULL; + } + *p++ = 0; + pid = atoi (p); + while (*p && *p != PATHSEP_C) + p++; + prot = *p? atoi (p+1) : 0; + if (prot != 1) + { + if (!try) + { +#ifdef ENABLE_NLS + if (orig_codeset) + bind_textdomain_codeset (PACKAGE, orig_codeset); +#endif /*ENABLE_NLS*/ + log_error (_("gpg-agent protocol version %d is not supported\n"), + prot); + opt.use_agent = 0; + } + xfree (infostr); + return NULL; + } + + rc = assuan_socket_connect (&ctx, infostr, pid); + if (rc) + { + if (!try) + { +#ifdef ENABLE_NLS + if (orig_codeset) + bind_textdomain_codeset (PACKAGE, orig_codeset); +#endif /*ENABLE_NLS*/ + log_info ( _("can't connect to `%s': %s\n"), + infostr, assuan_strerror (rc)); + opt.use_agent = 0; + } + xfree (infostr ); + return NULL; + } + xfree (infostr); + + if (agent_send_all_options (ctx)) + { + if (!try) + { +#ifdef ENABLE_NLS + if (orig_codeset) + bind_textdomain_codeset (PACKAGE, orig_codeset); +#endif /*ENABLE_NLS*/ + log_error (_("problem with the agent - disabling agent use\n")); + opt.use_agent = 0; + } + assuan_disconnect (ctx); + return NULL; + } + + return ctx; +} +#endif/*ENABLE_AGENT_SUPPORT*/ + + +#ifdef ENABLE_AGENT_SUPPORT +void +agent_close (assuan_context_t ctx) +{ + assuan_disconnect (ctx); +} +#endif /*ENABLE_AGENT_SUPPORT*/ + + +/* Copy the text ATEXT into the buffer P and do plus '+' and percent + escaping. Note that the provided buffer needs to be 3 times the + size of ATEXT plus 1. Returns a pointer to the leading Nul in P. */ +#ifdef ENABLE_AGENT_SUPPORT +static char * +percent_plus_escape (char *p, const char *atext) +{ + const unsigned char *s; + + for (s=atext; *s; s++) + { + if (*s < ' ' || *s == '+') + { + sprintf (p, "%%%02X", *s); + p += 3; + } + else if (*s == ' ') + *p++ = '+'; + else + *p++ = *s; + } + *p = 0; + return p; +} +#endif /*ENABLE_AGENT_SUPPORT*/ + + +#ifdef ENABLE_AGENT_SUPPORT + +/* Object for the agent_okay_cb function. */ +struct agent_okay_cb_s { + char *pw; +}; + +/* A callback used to get the passphrase from the okay line. See + agent-get_passphrase for details. LINE is the rest of the OK + status line without leading white spaces. */ +static assuan_error_t +agent_okay_cb (void *opaque, const char *line) +{ + struct agent_okay_cb_s *parm = opaque; + int i; + + /* Note: If the malloc below fails we won't be able to wipe the + memory at LINE given the current implementation of the Assuan + code. There is no easy ay around this w/o adding a lot of more + memory function code to allow wiping arbitrary stuff on memory + failure. */ + parm->pw = xmalloc_secure (strlen (line)/2+2); + + for (i=0; hexdigitp (line) && hexdigitp (line+1); line += 2) + parm->pw[i++] = xtoi_2 (line); + parm->pw[i] = 0; + return 0; +} +#endif /*ENABLE_AGENT_SUPPORT*/ + + + +/* + * Ask the GPG Agent for the passphrase. + * Mode 0: Allow cached passphrase + * 1: No cached passphrase FIXME: Not really implemented + * 2: Ditto, but change the text to "repeat entry" + * + * Note that TRYAGAIN_TEXT must not be translated. If canceled is not + * NULL, the function does set it to 1 if the user canceled the + * operation. If CACHEID is not NULL, it will be used as the cacheID + * for the gpg-agent; if is NULL and a key fingerprint can be + * computed, this will be used as the cacheid. + */ +static char * +agent_get_passphrase ( u32 *keyid, int mode, const char *cacheid, + const char *tryagain_text, + const char *custom_description, + const char *custom_prompt, int *canceled) +{ +#ifdef ENABLE_AGENT_SUPPORT + char *atext = NULL; + assuan_context_t ctx = NULL; + char *pw = NULL; + PKT_public_key *pk = xmalloc_clear( sizeof *pk ); + byte fpr[MAX_FINGERPRINT_LEN]; + int have_fpr = 0; + char *orig_codeset = NULL; + + if (canceled) + *canceled = 0; + +#if MAX_FINGERPRINT_LEN < 20 +#error agent needs a 20 byte fingerprint +#endif + + memset (fpr, 0, MAX_FINGERPRINT_LEN ); + if( keyid && get_pubkey( pk, keyid ) ) + { + if (pk) + free_public_key( pk ); + pk = NULL; /* oops: no key for some reason */ + } + +#ifdef ENABLE_NLS + /* The Assuan agent protocol requires us to transmit utf-8 strings */ + orig_codeset = bind_textdomain_codeset (PACKAGE, NULL); +#ifdef HAVE_LANGINFO_CODESET + if (!orig_codeset) + orig_codeset = nl_langinfo (CODESET); +#endif + if (orig_codeset) + { /* We only switch when we are able to restore the codeset later. */ + orig_codeset = xstrdup (orig_codeset); + if (!bind_textdomain_codeset (PACKAGE, "utf-8")) + orig_codeset = NULL; + } +#endif + + if ( !(ctx = agent_open (0, orig_codeset)) ) + goto failure; + + if (custom_description) + atext = native_to_utf8 (custom_description); + else if ( !mode && pk && keyid ) + { + char *uid; + size_t uidlen; + const char *algo_name = pubkey_algo_to_string ( pk->pubkey_algo ); + const char *timestr; + char *maink; + + if ( !algo_name ) + algo_name = "?"; + +#define KEYIDSTRING _(" (main key ID %s)") + + maink = xmalloc ( strlen (KEYIDSTRING) + keystrlen() + 20 ); + if( keyid[2] && keyid[3] && keyid[0] != keyid[2] + && keyid[1] != keyid[3] ) + sprintf( maink, KEYIDSTRING, keystr(&keyid[2]) ); + else + *maink = 0; + + uid = get_user_id ( keyid, &uidlen ); + timestr = strtimestamp (pk->timestamp); + +#undef KEYIDSTRING + +#define PROMPTSTRING _("You need a passphrase to unlock the secret" \ + " key for user:\n" \ + "\"%.*s\"\n" \ + "%u-bit %s key, ID %s, created %s%s\n" ) + + atext = xmalloc ( 100 + strlen (PROMPTSTRING) + + uidlen + 15 + strlen(algo_name) + keystrlen() + + strlen (timestr) + strlen (maink) ); + sprintf (atext, PROMPTSTRING, + (int)uidlen, uid, + nbits_from_pk (pk), algo_name, keystr(&keyid[0]), timestr, + maink ); + xfree (uid); + xfree (maink); + +#undef PROMPTSTRING + + { + size_t dummy; + fingerprint_from_pk( pk, fpr, &dummy ); + have_fpr = 1; + } + + } + else if (mode == 2 ) + atext = xstrdup ( _("Repeat passphrase\n") ); + else + atext = xstrdup ( _("Enter passphrase\n") ); + + { + char *line, *p; + int i, rc; + struct agent_okay_cb_s okay_cb_parm; + + if (!tryagain_text) + tryagain_text = "X"; + else + tryagain_text = _(tryagain_text); + + /* We allocate 23 times the needed space for thye texts so that + there is enough space for escaping. */ + line = xmalloc (15 + 46 + + 3*strlen (atext) + + 3*strlen (custom_prompt? custom_prompt:"") + + (cacheid? (3*strlen (cacheid)): 0) + + 3*strlen (tryagain_text) + + 1); + strcpy (line, "GET_PASSPHRASE "); + p = line+15; + if (!mode && cacheid) + { + p = percent_plus_escape (p, cacheid); + } + else if (!mode && have_fpr) + { + for (i=0; i < 20; i++, p +=2 ) + sprintf (p, "%02X", fpr[i]); + } + else + *p++ = 'X'; /* No caching. */ + *p++ = ' '; + + p = percent_plus_escape (p, tryagain_text); + *p++ = ' '; + + /* The prompt. */ + if (custom_prompt) + { + char *tmp = native_to_utf8 (custom_prompt); + p = percent_plus_escape (p, tmp); + xfree (tmp); + } + else + *p++ = 'X'; /* Use the standard prompt. */ + *p++ = ' '; + + /* Copy description. */ + percent_plus_escape (p, atext); + + /* Call gpg-agent. */ + memset (&okay_cb_parm, 0, sizeof okay_cb_parm); + rc = assuan_transact2 (ctx, line, NULL, NULL, NULL, NULL, NULL, NULL, + agent_okay_cb, &okay_cb_parm); + + xfree (line); + xfree (atext); atext = NULL; + if (!rc) + { + assert (okay_cb_parm.pw); + pw = okay_cb_parm.pw; + agent_close (ctx); + if (pk) + free_public_key( pk ); +#ifdef ENABLE_NLS + if (orig_codeset) + bind_textdomain_codeset (PACKAGE, orig_codeset); +#endif + xfree (orig_codeset); + return pw; + } + else if (rc && (rc & 0xffff) == 99) + { + /* 99 is GPG_ERR_CANCELED. */ + log_info (_("cancelled by user\n") ); + if (canceled) + *canceled = 1; + } + else + { + log_error (_("problem with the agent - disabling agent use\n")); + opt.use_agent = 0; + } + } + + + failure: +#ifdef ENABLE_NLS + if (orig_codeset) + { + bind_textdomain_codeset (PACKAGE, orig_codeset); + xfree (orig_codeset); + } +#endif + xfree (atext); + agent_close (ctx); + xfree (pw ); + if (pk) + free_public_key( pk ); + +#endif /*ENABLE_AGENT_SUPPORT*/ + + return NULL; +} + + +/* + * Clear the cached passphrase. If CACHEID is not NULL, it will be + * used instead of a cache ID derived from KEYID. + */ +void +passphrase_clear_cache ( u32 *keyid, const char *cacheid, int algo ) +{ +#ifdef ENABLE_AGENT_SUPPORT + assuan_context_t ctx = NULL; + PKT_public_key *pk; + byte fpr[MAX_FINGERPRINT_LEN]; + +#if MAX_FINGERPRINT_LEN < 20 +#error agent needs a 20 byte fingerprint +#endif + + if (!opt.use_agent) + return; + + if (!cacheid) + { + pk = xcalloc (1, sizeof *pk); + memset (fpr, 0, MAX_FINGERPRINT_LEN ); + if( !keyid || get_pubkey( pk, keyid ) ) + { + goto failure; /* oops: no key for some reason */ + } + + { + size_t dummy; + fingerprint_from_pk( pk, fpr, &dummy ); + } + } + else + pk = NULL; + + if ( !(ctx = agent_open (0, NULL)) ) + goto failure; + + { + char *line, *p; + int i, rc; + + if (cacheid) + { + line = xmalloc (17 + 3*strlen (cacheid) + 2); + strcpy (line, "CLEAR_PASSPHRASE "); + p = line+17; + p = percent_plus_escape (p, cacheid); + } + else + { + line = xmalloc (17 + 40 + 2); + strcpy (line, "CLEAR_PASSPHRASE "); + p = line+17; + for (i=0; i < 20; i++, p +=2 ) + sprintf (p, "%02X", fpr[i]); + } + *p = 0; + + rc = assuan_transact (ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); + xfree (line); + if (rc) + { + log_error (_("problem with the agent - disabling agent use\n")); + opt.use_agent = 0; + } + } + + failure: + agent_close (ctx); + if (pk) + free_public_key( pk ); +#endif /*ENABLE_AGENT_SUPPORT*/ +} + + +/**************** + * Ask for a passphrase and return that string. + */ +char * +ask_passphrase (const char *description, + const char *tryagain_text, + const char *promptid, + const char *prompt, + const char *cacheid, int *canceled) +{ + char *pw = NULL; + + if (canceled) + *canceled = 0; + + if (!opt.batch && description) + { + if (strchr (description, '%')) + { + char *tmp = unescape_percent_string (description); + tty_printf ("\n%s\n", tmp); + xfree (tmp); + } + else + tty_printf ("\n%s\n",description); + } + + agent_died: + if ( opt.use_agent ) + { + pw = agent_get_passphrase (NULL, 0, cacheid, + tryagain_text, description, prompt, + canceled ); + if (!pw) + { + if (!opt.use_agent) + goto agent_died; + pw = NULL; + } + } + else if (fd_passwd) + { + pw = xmalloc_secure (strlen(fd_passwd)+1); + strcpy (pw, fd_passwd); + } + else if (opt.batch) + { + log_error(_("can't query passphrase in batch mode\n")); + pw = NULL; + } + else { + if (tryagain_text) + tty_printf(_("%s.\n"), tryagain_text); + pw = cpr_get_hidden(promptid? promptid : "passphrase.ask", + prompt?prompt : _("Enter passphrase: ") ); + tty_kill_prompt(); + } + + if (!pw || !*pw) + write_status( STATUS_MISSING_PASSPHRASE ); + + return pw; +} + + +/* Return a new DEK object Using the string-to-key sepcifier S2K. Use + * KEYID and PUBKEY_ALGO to prompt the user. + + MODE 0: Allow cached passphrase + 1: Ignore cached passphrase + 2: Ditto, but change the text to "repeat entry" +*/ +DEK * +passphrase_to_dek( u32 *keyid, int pubkey_algo, + int cipher_algo, STRING2KEY *s2k, int mode, + const char *tryagain_text, int *canceled) +{ + char *pw = NULL; + DEK *dek; + STRING2KEY help_s2k; + + if (canceled) + *canceled = 0; + + if( !s2k ) { + /* This is used for the old rfc1991 mode + * Note: This must match the code in encode.c with opt.rfc1991 set */ + s2k = &help_s2k; + s2k->mode = 0; + s2k->hash_algo = S2K_DIGEST_ALGO; + } + + /* If we do not have a passphrase available in NEXT_PW and status + information are request, we print them now. */ + if( !next_pw && is_status_enabled() ) { + char buf[50]; + + if( keyid ) { + u32 used_kid[2]; + char *us; + + if( keyid[2] && keyid[3] ) { + used_kid[0] = keyid[2]; + used_kid[1] = keyid[3]; + } + else { + used_kid[0] = keyid[0]; + used_kid[1] = keyid[1]; + } + + us = get_long_user_id_string( keyid ); + write_status_text( STATUS_USERID_HINT, us ); + xfree(us); + + sprintf( buf, "%08lX%08lX %08lX%08lX %d 0", + (ulong)keyid[0], (ulong)keyid[1], + (ulong)used_kid[0], (ulong)used_kid[1], + pubkey_algo ); + + write_status_text( STATUS_NEED_PASSPHRASE, buf ); + } + else { + sprintf( buf, "%d %d %d", cipher_algo, s2k->mode, s2k->hash_algo ); + write_status_text( STATUS_NEED_PASSPHRASE_SYM, buf ); + } + } + + /* If we do have a keyID, we do not have a passphrase available in + NEXT_PW, we are not running in batch mode and we do not want to + ignore the passphrase cache (mode!=1), print a prompt with + information on that key. */ + if( keyid && !opt.batch && !next_pw && mode!=1 ) { + PKT_public_key *pk = xmalloc_clear( sizeof *pk ); + char *p; + + p=get_user_id_native(keyid); + tty_printf("\n"); + tty_printf(_("You need a passphrase to unlock the secret key for\n" + "user: \"%s\"\n"),p); + xfree(p); + + if( !get_pubkey( pk, keyid ) ) { + const char *s = pubkey_algo_to_string( pk->pubkey_algo ); + tty_printf( _("%u-bit %s key, ID %s, created %s"), + nbits_from_pk( pk ), s?s:"?", keystr(keyid), + strtimestamp(pk->timestamp) ); + if( keyid[2] && keyid[3] && keyid[0] != keyid[2] + && keyid[1] != keyid[3] ) + { + if(keystrlen()>10) + { + tty_printf("\n"); + tty_printf(_(" (subkey on main key ID %s)"), + keystr(&keyid[2]) ); + } + else + tty_printf( _(" (main key ID %s)"), keystr(&keyid[2]) ); + } + tty_printf("\n"); + } + + tty_printf("\n"); + if (pk) + free_public_key( pk ); + } + + agent_died: + if( next_pw ) { + /* Simply return the passphrase we already have in NEXT_PW. */ + pw = next_pw; + next_pw = NULL; + } + else if ( opt.use_agent ) { + /* Divert to the gpg-agent. */ + pw = agent_get_passphrase ( keyid, mode == 2? 1: 0, NULL, + tryagain_text, NULL, NULL, canceled ); + if (!pw) + { + if (!opt.use_agent) + goto agent_died; + pw = xstrdup (""); + } + if( *pw && mode == 2 ) + { + int i; + for(i=0;i<opt.passwd_repeat;i++) + { + char *pw2 = agent_get_passphrase ( keyid, 2, NULL, NULL, NULL, + NULL, canceled ); + if (!pw2) + { + if (!opt.use_agent) + { + xfree (pw); + pw = NULL; + goto agent_died; + } + pw2 = xstrdup (""); + } + if( strcmp(pw, pw2) ) + { + xfree(pw2); + xfree(pw); + return NULL; + } + xfree(pw2); + } + } + } + else if( fd_passwd ) { + /* Return the passphrase we have store in FD_PASSWD. */ + pw = xmalloc_secure( strlen(fd_passwd)+1 ); + strcpy( pw, fd_passwd ); + } + else if( opt.batch ) + { + log_error(_("can't query passphrase in batch mode\n")); + pw = xstrdup( "" ); /* return an empty passphrase */ + } + else { + /* Read the passphrase from the tty or the command-fd. */ + pw = cpr_get_hidden("passphrase.enter", _("Enter passphrase: ") ); + tty_kill_prompt(); + if( mode == 2 && !cpr_enabled() ) + { + int i; + for(i=0;i<opt.passwd_repeat;i++) + { + char *pw2 = cpr_get_hidden("passphrase.repeat", + _("Repeat passphrase: ") ); + tty_kill_prompt(); + if( strcmp(pw, pw2) ) + { + xfree(pw2); + xfree(pw); + return NULL; + } + xfree(pw2); + } + } + } + + if( !pw || !*pw ) + write_status( STATUS_MISSING_PASSPHRASE ); + + /* Hash the passphrase and store it in a newly allocated DEK + object. Keep a copy of the passphrase in LAST_PW for use by + get_last_passphrase(). */ + dek = xmalloc_secure_clear ( sizeof *dek ); + dek->algo = cipher_algo; + if( !*pw && mode == 2 ) + dek->keylen = 0; + else + hash_passphrase( dek, pw, s2k, mode==2 ); + xfree(last_pw); + last_pw = pw; + return dek; +} + + +/**************** + * Hash a passphrase using the supplied s2k. If create is true, create + * a new salt or what else must be filled into the s2k for a new key. + * always needs: dek->algo, s2k->mode, s2k->hash_algo. + */ +static void +hash_passphrase( DEK *dek, char *pw, STRING2KEY *s2k, int create ) +{ + MD_HANDLE md; + int pass, i; + int used = 0; + int pwlen = strlen(pw); + + assert( s2k->hash_algo ); + dek->keylen = cipher_get_keylen( dek->algo ) / 8; + if( !(dek->keylen > 0 && dek->keylen <= DIM(dek->key)) ) + BUG(); + + md = md_open( s2k->hash_algo, 1); + for(pass=0; used < dek->keylen ; pass++ ) { + if( pass ) { + md_reset(md); + for(i=0; i < pass; i++ ) /* preset the hash context */ + md_putc(md, 0 ); + } + + if( s2k->mode == 1 || s2k->mode == 3 ) { + int len2 = pwlen + 8; + ulong count = len2; + + if( create && !pass ) { + randomize_buffer(s2k->salt, 8, 1); + if( s2k->mode == 3 ) + s2k->count = opt.s2k_count; + } + + if( s2k->mode == 3 ) { + count = S2K_DECODE_COUNT(s2k->count); + if( count < len2 ) + count = len2; + } + /* a little bit complicated because we need a ulong for count */ + while( count > len2 ) { /* maybe iterated+salted */ + md_write( md, s2k->salt, 8 ); + md_write( md, pw, pwlen ); + count -= len2; + } + if( count < 8 ) + md_write( md, s2k->salt, count ); + else { + md_write( md, s2k->salt, 8 ); + count -= 8; + md_write( md, pw, count ); + } + } + else + md_write( md, pw, pwlen ); + md_final( md ); + i = md_digest_length( s2k->hash_algo ); + if( i > dek->keylen - used ) + i = dek->keylen - used; + memcpy( dek->key+used, md_read(md, s2k->hash_algo), i ); + used += i; + } + md_close(md); +} + diff --git a/g10/photoid.c b/g10/photoid.c new file mode 100644 index 0000000..3676d38 --- /dev/null +++ b/g10/photoid.c @@ -0,0 +1,385 @@ +/* photoid.c - photo ID handling code + * Copyright (C) 2001, 2002, 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 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#include <config.h> +#include <errno.h> +#include <stdio.h> +#include <string.h> +#ifdef _WIN32 +# include <windows.h> +# ifndef VER_PLATFORM_WIN32_WINDOWS +# define VER_PLATFORM_WIN32_WINDOWS 1 +# endif +#endif +#include "packet.h" +#include "status.h" +#include "exec.h" +#include "keydb.h" +#include "util.h" +#include "i18n.h" +#include "iobuf.h" +#include "memory.h" +#include "options.h" +#include "main.h" +#include "photoid.h" +#include "ttyio.h" + +/* Generate a new photo id packet, or return NULL if canceled */ +PKT_user_id * +generate_photo_id(PKT_public_key *pk,const char *photo_name) +{ + PKT_user_id *uid; + int error=1,i; + unsigned int len; + char *filename; + byte *photo=NULL; + byte header[16]; + IOBUF file; + int overflow; + + header[0]=0x10; /* little side of photo header length */ + header[1]=0; /* big side of photo header length */ + header[2]=1; /* 1 == version of photo header */ + header[3]=1; /* 1 == JPEG */ + + for(i=4;i<16;i++) /* The reserved bytes */ + header[i]=0; + +#define EXTRA_UID_NAME_SPACE 71 + uid=xmalloc_clear(sizeof(*uid)+71); + + if(photo_name && *photo_name) + filename=make_filename(photo_name,(void *)NULL); + else + { + tty_printf(_("\nPick an image to use for your photo ID." + " The image must be a JPEG file.\n" + "Remember that the image is stored within your public key." + " If you use a\n" + "very large picture, your key will become very large" + " as well!\n" + "Keeping the image close to 240x288 is a good size" + " to use.\n")); + filename=NULL; + } + + while(photo==NULL) + { + if(filename==NULL) + { + char *tempname; + + tty_printf("\n"); + + tty_enable_completion(NULL); + + tempname=cpr_get("photoid.jpeg.add", + _("Enter JPEG filename for photo ID: ")); + + tty_disable_completion(); + + filename=make_filename(tempname,(void *)NULL); + + xfree(tempname); + + if(strlen(filename)==0) + goto scram; + } + + file=iobuf_open(filename); + if (file && is_secured_file (iobuf_get_fd (file))) + { + iobuf_close (file); + file = NULL; + errno = EPERM; + } + if(!file) + { + log_error(_("unable to open JPEG file `%s': %s\n"), + filename,strerror(errno)); + xfree(filename); + filename=NULL; + continue; + } + + + len=iobuf_get_filelength(file, &overflow); + if(len>6144 || overflow) + { + tty_printf( _("This JPEG is really large (%d bytes) !\n"),len); + if(!cpr_get_answer_is_yes("photoid.jpeg.size", + _("Are you sure you want to use it? (y/N) "))) + { + iobuf_close(file); + xfree(filename); + filename=NULL; + continue; + } + } + + photo=xmalloc(len); + iobuf_read(file,photo,len); + iobuf_close(file); + + /* Is it a JPEG? */ + if(photo[0]!=0xFF || photo[1]!=0xD8 || + photo[6]!='J' || photo[7]!='F' || photo[8]!='I' || photo[9]!='F') + { + log_error(_("`%s' is not a JPEG file\n"),filename); + xfree(photo); + photo=NULL; + xfree(filename); + filename=NULL; + continue; + } + + /* Build the packet */ + build_attribute_subpkt(uid,1,photo,len,header,16); + parse_attribute_subpkts(uid); + make_attribute_uidname(uid, EXTRA_UID_NAME_SPACE); + + /* Showing the photo is not safe when noninteractive since the + "user" may not be able to dismiss a viewer window! */ + if(opt.command_fd==-1) + { + show_photos(uid->attribs,uid->numattribs,pk,NULL); + switch(cpr_get_answer_yes_no_quit("photoid.jpeg.okay", + _("Is this photo correct (y/N/q)? "))) + { + case -1: + goto scram; + case 0: + free_attributes(uid); + xfree(photo); + photo=NULL; + xfree(filename); + filename=NULL; + continue; + } + } + } + + error=0; + uid->ref=1; + + scram: + xfree(filename); + xfree(photo); + + if(error) + { + free_attributes(uid); + xfree(uid); + return NULL; + } + + return uid; +} + +/* Returns 0 for error, 1 for valid */ +int parse_image_header(const struct user_attribute *attr,byte *type,u32 *len) +{ + u16 headerlen; + + if(attr->len<3) + return 0; + + /* For historical reasons (i.e. "oops!"), the header length is + little endian. */ + headerlen=(attr->data[1]<<8) | attr->data[0]; + + if(headerlen>attr->len) + return 0; + + if(type && attr->len>=4) + { + if(attr->data[2]==1) /* header version 1 */ + *type=attr->data[3]; + else + *type=0; + } + + *len=attr->len-headerlen; + + if(*len==0) + return 0; + + return 1; +} + +/* style==0 for extension, 1 for name, 2 for MIME type. Remember that + the "name" style string could be used in a user ID name field, so + make sure it is not too big (see parse-packet.c:parse_attribute). + Extensions should be 3 characters long for the best cross-platform + compatibility. */ +char *image_type_to_string(byte type,int style) +{ + char *string; + + switch(type) + { + case 1: /* jpeg */ + if(style==0) + string="jpg"; + else if(style==1) + string="jpeg"; + else + string="image/jpeg"; + break; + + default: + if(style==0) + string="bin"; + else if(style==1) + string="unknown"; + else + string="image/x-unknown"; + break; + } + + return string; +} + +#if !defined(FIXED_PHOTO_VIEWER) && !defined(DISABLE_PHOTO_VIEWER) +static const char * +get_default_photo_command(void) +{ +#if defined(_WIN32) + OSVERSIONINFO osvi; + + memset(&osvi,0,sizeof(osvi)); + osvi.dwOSVersionInfoSize=sizeof(osvi); + GetVersionEx(&osvi); + + if(osvi.dwPlatformId==VER_PLATFORM_WIN32_WINDOWS) + return "start /w %i"; + else + return "cmd /c start /w %i"; +#elif defined(__APPLE__) + /* OS X. This really needs more than just __APPLE__. */ + return "open %I"; +#elif defined(__riscos__) + return "Filer_Run %I"; +#else + if(path_access("xloadimage",X_OK)==0) + return "xloadimage -fork -quiet -title 'KeyID 0x%k' stdin"; + else if(path_access("eog",X_OK)==0) + return "eog %i"; + else if(path_access("display",X_OK)==0) + return "display -title 'KeyID 0x%k' %i"; + else + return ""; +#endif +} +#endif + +void show_photos(const struct user_attribute *attrs, + int count,PKT_public_key *pk,PKT_secret_key *sk) +{ +#ifndef DISABLE_PHOTO_VIEWER + int i; + struct expando_args args; + u32 len; + u32 kid[2]={0,0}; + + memset(&args,0,sizeof(args)); + args.pk=pk; + args.sk=sk; + + if(pk) + keyid_from_pk(pk,kid); + else if(sk) + keyid_from_sk(sk,kid); + + for(i=0;i<count;i++) + if(attrs[i].type==ATTRIB_IMAGE && + parse_image_header(&attrs[i],&args.imagetype,&len)) + { + char *command,*name; + struct exec_info *spawn; + int offset=attrs[i].len-len; + +#ifdef FIXED_PHOTO_VIEWER + opt.photo_viewer=FIXED_PHOTO_VIEWER; +#else + if(!opt.photo_viewer) + opt.photo_viewer=get_default_photo_command(); +#endif + + if(!*opt.photo_viewer) + { + log_info(_("no photo viewer set\n")); + goto fail; + } + + /* make command grow */ + command=pct_expando(opt.photo_viewer,&args); + if(!command) + goto fail; + + name=xmalloc(16+strlen(EXTSEP_S)+ + strlen(image_type_to_string(args.imagetype,0))+1); + + /* Make the filename. Notice we are not using the image + encoding type for more than cosmetics. Most external image + viewers can handle a multitude of types, and even if one + cannot understand a particular type, we have no way to know + which. The spec permits this, by the way. -dms */ + +#ifdef USE_ONLY_8DOT3 + sprintf(name,"%08lX" EXTSEP_S "%s",(ulong)kid[1], + image_type_to_string(args.imagetype,0)); +#else + sprintf(name,"%08lX%08lX" EXTSEP_S "%s",(ulong)kid[0],(ulong)kid[1], + image_type_to_string(args.imagetype,0)); +#endif + + if(exec_write(&spawn,NULL,command,name,1,1)!=0) + { + xfree(name); + goto fail; + } + +#ifdef __riscos__ + riscos_set_filetype_by_mimetype(spawn->tempfile_in, + image_type_to_string(args.imagetype,2)); +#endif + + xfree(name); + + fwrite(&attrs[i].data[offset],attrs[i].len-offset,1,spawn->tochild); + + if(exec_read(spawn)!=0) + { + exec_finish(spawn); + goto fail; + } + + if(exec_finish(spawn)!=0) + goto fail; + } + + return; + + fail: + log_error(_("unable to display photo ID!\n")); +#endif +} diff --git a/g10/photoid.h b/g10/photoid.h new file mode 100644 index 0000000..d13669c --- /dev/null +++ b/g10/photoid.h @@ -0,0 +1,35 @@ +/* photoid.h + * Copyright (C) 2001, 2002 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +/* Photo ID functions */ + +#ifndef _PHOTOID_H_ +#define _PHOTOID_H_ + +#include "packet.h" + +PKT_user_id *generate_photo_id(PKT_public_key *pk,const char *filename); +int parse_image_header(const struct user_attribute *attr,byte *type,u32 *len); +char *image_type_to_string(byte type,int style); +void show_photos(const struct user_attribute *attrs, + int count,PKT_public_key *pk,PKT_secret_key *sk); + +#endif /* !_PHOTOID_H_ */ diff --git a/g10/pipemode.c b/g10/pipemode.c new file mode 100644 index 0000000..750aa3b --- /dev/null +++ b/g10/pipemode.c @@ -0,0 +1,318 @@ +/* pipemode.c - pipemode handler + * Copyright (C) 1998, 1990, 2000, 2001 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <assert.h> + +#include "options.h" +#include "packet.h" +#include "errors.h" +#include "iobuf.h" +#include "keydb.h" +#include "memory.h" +#include "util.h" +#include "main.h" +#include "status.h" +#include "filter.h" + + +#define CONTROL_PACKET_SPACE 30 +#define FAKED_LITERAL_PACKET_SPACE (9+2+2) + + +enum pipemode_state_e { + STX_init = 0, + STX_wait_operation, + STX_begin, + STX_text, + STX_detached_signature, + STX_detached_signature_wait_text, + STX_signed_data, + STX_wait_init +}; + +struct pipemode_context_s { + enum pipemode_state_e state; + int operation; + int stop; + int block_mode; + UnarmorPump unarmor_ctx; +}; + + +static size_t +make_control ( byte *buf, int code, int operation ) +{ + const byte *sesmark; + size_t sesmarklen, n=0;; + + sesmark = get_session_marker( &sesmarklen ); + if ( sesmarklen > 20 ) + BUG(); + + buf[n++] = 0xff; /* new format, type 63, 1 length byte */ + n++; /* length will fixed below */ + memcpy(buf+n, sesmark, sesmarklen ); n+= sesmarklen; + buf[n++] = CTRLPKT_PIPEMODE; + buf[n++] = code; + buf[n++] = operation; + buf[1] = n-2; + return n; +} + + + +static int +pipemode_filter( void *opaque, int control, + IOBUF a, byte *buf, size_t *ret_len) +{ + size_t size = *ret_len; + struct pipemode_context_s *stx = opaque; + int rc=0; + size_t n = 0; + int esc = 0; + + if( control == IOBUFCTRL_UNDERFLOW ) { + *ret_len = 0; + /* reserve some space for one control packet */ + if ( size <= CONTROL_PACKET_SPACE+FAKED_LITERAL_PACKET_SPACE ) + BUG(); + size -= CONTROL_PACKET_SPACE+FAKED_LITERAL_PACKET_SPACE; + + if ( stx->block_mode ) { + /* reserve 2 bytes for the block length */ + buf[n++] = 0; + buf[n++] = 0; + } + + + while ( n < size ) { + /* FIXME: we have to make sure that we have a large enough + * buffer for a control packet even after we already read + * something. The easest way to do this is probably by ungetting + * the control sequence and returning the buffer we have + * already assembled */ + int c = iobuf_get (a); + if (c == -1) { + if ( stx->state != STX_init ) { + log_error ("EOF encountered at wrong state\n"); + stx->stop = 1; + return -1; + } + break; + } + if ( esc ) { + switch (c) { + case '@': + if ( stx->state == STX_text ) { + buf[n++] = c; + break; + } + else if ( stx->state == STX_detached_signature ) { + esc = 0; + goto do_unarmor; /* not a very elegant solution */ + } + else if ( stx->state == STX_detached_signature_wait_text) { + esc = 0; + break; /* just ignore it in this state */ + } + log_error ("@@ not allowed in current state\n"); + return -1; + case '<': /* begin of stream part */ + if ( stx->state != STX_init ) { + log_error ("nested begin of stream\n"); + stx->stop = 1; + return -1; + } + stx->state = STX_wait_operation; + stx->block_mode = 0; + unarmor_pump_release (stx->unarmor_ctx); + stx->unarmor_ctx = NULL; + break; + case '>': /* end of stream part */ + if ( stx->state != STX_wait_init ) { + log_error ("invalid state for @>\n"); + stx->stop = 1; + return -1; + } + stx->state = STX_init; + break; + case 'V': /* operation = verify */ + case 'E': /* operation = encrypt */ + case 'S': /* operation = sign */ + case 'B': /* operation = detach sign */ + case 'C': /* operation = clearsign */ + case 'D': /* operation = decrypt */ + if ( stx->state != STX_wait_operation ) { + log_error ("invalid state for operation code\n"); + stx->stop = 1; + return -1; + } + stx->operation = c; + if ( stx->operation == 'B') { + stx->state = STX_detached_signature; + if ( !opt.no_armor ) + stx->unarmor_ctx = unarmor_pump_new (); + } + else + stx->state = STX_begin; + n += make_control ( buf+n, 1, stx->operation ); + /* must leave after a control packet */ + goto leave; + + case 't': /* plaintext text follows */ + if ( stx->state == STX_detached_signature_wait_text ) + stx->state = STX_detached_signature; + if ( stx->state == STX_detached_signature ) { + if ( stx->operation != 'B' ) { + log_error ("invalid operation for this state\n"); + stx->stop = 1; + return -1; + } + stx->state = STX_signed_data; + n += make_control ( buf+n, 2, 'B' ); + /* and now we fake a literal data packet much the same + * as in armor.c */ + buf[n++] = 0xaf; /* old packet format, type 11, + var length */ + buf[n++] = 0; /* set the length header */ + buf[n++] = 6; + buf[n++] = 'b'; /* we ignore it anyway */ + buf[n++] = 0; /* namelength */ + memset(buf+n, 0, 4); /* timestamp */ + n += 4; + /* and return now so that we are sure to have + * more space in the bufer for the next control + * packet */ + stx->block_mode = 1; + goto leave2; + } + else { + log_error ("invalid state for @t\n"); + stx->stop = 1; + return -1; + } + break; + + case '.': /* ready */ + if ( stx->state == STX_signed_data ) { + if (stx->block_mode) { + buf[0] = (n-2) >> 8; + buf[1] = (n-2); + if ( buf[0] || buf[1] ) { + /* end of blocks marker */ + buf[n++] = 0; + buf[n++] = 0; + } + stx->block_mode = 0; + } + n += make_control ( buf+n, 3, 'B' ); + } + else { + log_error ("invalid state for @.\n"); + stx->stop = 1; + return -1; + } + stx->state = STX_wait_init; + goto leave; + + default: + log_error ("invalid escape sequence 0x%02x in stream\n", + c); + stx->stop = 1; + return -1; + } + esc = 0; + } + else if (c == '@') + esc = 1; + else if (stx->unarmor_ctx) { + do_unarmor: /* used to handle a @@ */ + c = unarmor_pump (stx->unarmor_ctx, c); + if ( !(c & ~255) ) + buf[n++] = c; + else if ( c < 0 ) { + /* end of armor or error - we don't care becuase + the armor can be modified anyway. The unarmored + stuff should stand for itself. */ + unarmor_pump_release (stx->unarmor_ctx); + stx->unarmor_ctx = NULL; + stx->state = STX_detached_signature_wait_text; + } + } + else if (stx->state == STX_detached_signature_wait_text) + ; /* just wait */ + else + buf[n++] = c; + } + + leave: + if ( !n ) { + stx->stop = 1; + rc = -1; /* eof */ + } + if ( stx->block_mode ) { + /* fixup the block length */ + buf[0] = (n-2) >> 8; + buf[1] = (n-2); + } + leave2: + /*log_hexdump ("pipemode:", buf, n );*/ + *ret_len = n; + } + else if( control == IOBUFCTRL_DESC ) + *(char**)buf = "pipemode_filter"; + return rc; +} + + + +void +run_in_pipemode(void) +{ + IOBUF fp; + armor_filter_context_t afx; + struct pipemode_context_s stx; + int rc; + + memset( &afx, 0, sizeof afx); + memset( &stx, 0, sizeof stx); + + fp = iobuf_open("-"); + iobuf_push_filter (fp, pipemode_filter, &stx ); + + do { + write_status (STATUS_BEGIN_STREAM); + rc = proc_packets( NULL, fp ); + write_status (STATUS_END_STREAM); + } while ( !stx.stop ); + +} + + + + + + diff --git a/g10/pkclist.c b/g10/pkclist.c new file mode 100644 index 0000000..4c0ffd7 --- /dev/null +++ b/g10/pkclist.c @@ -0,0 +1,1448 @@ +/* pkclist.c + * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, + * 2006 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <assert.h> + +#include "options.h" +#include "packet.h" +#include "errors.h" +#include "keydb.h" +#include "memory.h" +#include "util.h" +#include "main.h" +#include "trustdb.h" +#include "ttyio.h" +#include "status.h" +#include "photoid.h" +#include "i18n.h" + +#define CONTROL_D ('D' - 'A' + 1) + +/**************** + * Show the revocation reason as it is stored with the given signature + */ +static void +do_show_revocation_reason( PKT_signature *sig ) +{ + size_t n, nn; + const byte *p, *pp; + int seq = 0; + const char *text; + + while( (p = enum_sig_subpkt (sig->hashed, SIGSUBPKT_REVOC_REASON, + &n, &seq, NULL )) ) { + if( !n ) + continue; /* invalid - just skip it */ + + if( *p == 0 ) + text = _("No reason specified"); + else if( *p == 0x01 ) + text = _("Key is superseded"); + else if( *p == 0x02 ) + text = _("Key has been compromised"); + else if( *p == 0x03 ) + text = _("Key is no longer used"); + else if( *p == 0x20 ) + text = _("User ID is no longer valid"); + else + text = NULL; + + log_info( _("reason for revocation: ") ); + if( text ) + fputs( text, log_stream() ); + else + fprintf( log_stream(), "code=%02x", *p ); + putc( '\n', log_stream() ); + n--; p++; + pp = NULL; + do { + /* We don't want any empty lines, so skip them */ + while( n && *p == '\n' ) { + p++; + n--; + } + if( n ) { + pp = memchr( p, '\n', n ); + nn = pp? pp - p : n; + log_info( _("revocation comment: ") ); + print_string( log_stream(), p, nn, 0 ); + putc( '\n', log_stream() ); + p += nn; n -= nn; + } + } while( pp ); + } +} + +/* Mode 0: try and find the revocation based on the pk (i.e. check + subkeys, etc.) Mode 1: use only the revocation on the main pk */ + +void +show_revocation_reason( PKT_public_key *pk, int mode ) +{ + /* Hmmm, this is not so easy becuase we have to duplicate the code + * used in the trustbd to calculate the keyflags. We need to find + * a clean way to check revocation certificates on keys and + * signatures. And there should be no duplicate code. Because we + * enter this function only when the trustdb told us that we have + * a revoked key, we could simply look for a revocation cert and + * display this one, when there is only one. Let's try to do this + * until we have a better solution. */ + KBNODE node, keyblock = NULL; + byte fingerprint[MAX_FINGERPRINT_LEN]; + size_t fingerlen; + int rc; + + /* get the keyblock */ + fingerprint_from_pk( pk, fingerprint, &fingerlen ); + rc = get_keyblock_byfprint( &keyblock, fingerprint, fingerlen ); + if( rc ) { /* that should never happen */ + log_debug( "failed to get the keyblock\n"); + return; + } + + for( node=keyblock; node; node = node->next ) { + if( (mode && node->pkt->pkttype == PKT_PUBLIC_KEY) || + ( ( node->pkt->pkttype == PKT_PUBLIC_KEY + || node->pkt->pkttype == PKT_PUBLIC_SUBKEY ) + && !cmp_public_keys( node->pkt->pkt.public_key, pk ) ) ) + break; + } + if( !node ) { + log_debug("Oops, PK not in keyblock\n"); + release_kbnode( keyblock ); + return; + } + /* now find the revocation certificate */ + for( node = node->next; node ; node = node->next ) { + if( node->pkt->pkttype == PKT_PUBLIC_SUBKEY ) + break; + if( node->pkt->pkttype == PKT_SIGNATURE + && (node->pkt->pkt.signature->sig_class == 0x20 + || node->pkt->pkt.signature->sig_class == 0x28 ) ) { + /* FIXME: we should check the signature here */ + do_show_revocation_reason ( node->pkt->pkt.signature ); + break; + } + } + + /* We didn't find it, so check if the whole key is revoked */ + if(!node && !mode) + show_revocation_reason(pk,1); + + release_kbnode( keyblock ); +} + + +/**************** + * mode: 0 = standard + * 1 = Without key info and additional menu option 'm' + * this does also add an option to set the key to ultimately trusted. + * Returns: + * -2 = nothing changed - caller should show some additional info + * -1 = quit operation + * 0 = nothing changed + * 1 = new ownertrust now in new_trust + */ +static int +do_edit_ownertrust (PKT_public_key *pk, int mode, + unsigned *new_trust, int defer_help ) +{ + char *p; + u32 keyid[2]; + int changed=0; + int quit=0; + int show=0; + int min_num; + int did_help=defer_help; + unsigned int minimum=get_min_ownertrust(pk); + + switch(minimum) + { + default: + case TRUST_UNDEFINED: min_num=1; break; + case TRUST_NEVER: min_num=2; break; + case TRUST_MARGINAL: min_num=3; break; + case TRUST_FULLY: min_num=4; break; + } + + keyid_from_pk (pk, keyid); + for(;;) { + /* A string with valid answers. + + Note to translators: These are the allowed answers in lower and + uppercase. Below you will find the matching strings which + should be translated accordingly and the letter changed to + match the one in the answer string. + + i = please show me more information + m = back to the main menu + s = skip this key + q = quit + */ + const char *ans = _("iImMqQsS"); + + if( !did_help ) + { + if( !mode ) + { + KBNODE keyblock, un; + + tty_printf(_("No trust value assigned to:\n")); + tty_printf("%4u%c/%s %s\n",nbits_from_pk( pk ), + pubkey_letter( pk->pubkey_algo ), + keystr(keyid), datestr_from_pk( pk ) ); + p=get_user_id_native(keyid); + tty_printf(_(" \"%s\"\n"),p); + xfree(p); + + keyblock = get_pubkeyblock (keyid); + if (!keyblock) + BUG (); + for (un=keyblock; un; un = un->next) + { + if (un->pkt->pkttype != PKT_USER_ID ) + continue; + if (un->pkt->pkt.user_id->is_revoked ) + continue; + if (un->pkt->pkt.user_id->is_expired ) + continue; + /* Only skip textual primaries */ + if (un->pkt->pkt.user_id->is_primary + && !un->pkt->pkt.user_id->attrib_data ) + continue; + + if((opt.verify_options&VERIFY_SHOW_PHOTOS) + && un->pkt->pkt.user_id->attrib_data) + show_photos(un->pkt->pkt.user_id->attribs, + un->pkt->pkt.user_id->numattribs,pk,NULL); + + p=utf8_to_native(un->pkt->pkt.user_id->name, + un->pkt->pkt.user_id->len,0); + + tty_printf(_(" aka \"%s\"\n"),p); + } + + print_fingerprint (pk, NULL, 2); + tty_printf("\n"); + release_kbnode (keyblock); + } + + if(opt.trust_model==TM_DIRECT) + { + tty_printf(_("How much do you trust that this key actually " + "belongs to the named user?\n")); + tty_printf("\n"); + } + else + { + /* This string also used in keyedit.c:trustsig_prompt */ + tty_printf(_("Please decide how far you trust this user to" + " correctly verify other users' keys\n" + "(by looking at passports, checking fingerprints from" + " different sources, etc.)\n")); + tty_printf("\n"); + } + + if(min_num<=1) + tty_printf (_(" %d = I don't know or won't say\n"), 1); + if(min_num<=2) + tty_printf (_(" %d = I do NOT trust\n"), 2); + if(min_num<=3) + tty_printf (_(" %d = I trust marginally\n"), 3); + if(min_num<=4) + tty_printf (_(" %d = I trust fully\n"), 4); + if (mode) + tty_printf (_(" %d = I trust ultimately\n"), 5); +#if 0 + /* not yet implemented */ + tty_printf (" i = please show me more information\n"); +#endif + if( mode ) + tty_printf(_(" m = back to the main menu\n")); + else + { + tty_printf(_(" s = skip this key\n")); + tty_printf(_(" q = quit\n")); + } + tty_printf("\n"); + if(minimum) + tty_printf(_("The minimum trust level for this key is: %s\n\n"), + trust_value_to_string(minimum)); + did_help = 1; + } + if( strlen(ans) != 8 ) + BUG(); + p = cpr_get("edit_ownertrust.value",_("Your decision? ")); + trim_spaces(p); + cpr_kill_prompt(); + if( !*p ) + did_help = 0; + else if( *p && p[1] ) + ; + else if( !p[1] && ((*p >= '0'+min_num) && *p <= (mode?'5':'4')) ) + { + unsigned int trust; + switch( *p ) + { + case '1': trust = TRUST_UNDEFINED; break; + case '2': trust = TRUST_NEVER ; break; + case '3': trust = TRUST_MARGINAL ; break; + case '4': trust = TRUST_FULLY ; break; + case '5': trust = TRUST_ULTIMATE ; break; + default: BUG(); + } + if (trust == TRUST_ULTIMATE + && !cpr_get_answer_is_yes ("edit_ownertrust.set_ultimate.okay", + _("Do you really want to set this key" + " to ultimate trust? (y/N) "))) + ; /* no */ + else + { + *new_trust = trust; + changed = 1; + break; + } + } +#if 0 + /* not yet implemented */ + else if( *p == ans[0] || *p == ans[1] ) + { + tty_printf(_("Certificates leading to an ultimately trusted key:\n")); + show = 1; + break; + } +#endif + else if( mode && (*p == ans[2] || *p == ans[3] || *p == CONTROL_D ) ) + { + break ; /* back to the menu */ + } + else if( !mode && (*p == ans[6] || *p == ans[7] ) ) + { + break; /* skip */ + } + else if( !mode && (*p == ans[4] || *p == ans[5] ) ) + { + quit = 1; + break ; /* back to the menu */ + } + xfree(p); p = NULL; + } + xfree(p); + return show? -2: quit? -1 : changed; +} + +/* + * Display a menu to change the ownertrust of the key PK (which should + * be a primary key). + * For mode values see do_edit_ownertrust () + */ +int +edit_ownertrust (PKT_public_key *pk, int mode ) +{ + unsigned int trust = 0; /* Needs to be initialized to avoid gcc warning. */ + int no_help = 0; + + for(;;) + { + switch ( do_edit_ownertrust (pk, mode, &trust, no_help ) ) + { + case -1: /* quit */ + return -1; + case -2: /* show info */ + no_help = 1; + break; + case 1: /* trust value set */ + trust &= ~TRUST_FLAG_DISABLED; + trust |= get_ownertrust (pk) & TRUST_FLAG_DISABLED; + update_ownertrust (pk, trust ); + return 1; + default: + return 0; + } + } +} + + +/**************** + * Check whether we can trust this pk which has a trustlevel of TRUSTLEVEL + * Returns: true if we trust. + */ +static int +do_we_trust( PKT_public_key *pk, unsigned int trustlevel ) +{ + /* We should not be able to get here with a revoked or expired + key */ + if(trustlevel & TRUST_FLAG_REVOKED + || trustlevel & TRUST_FLAG_SUB_REVOKED + || (trustlevel & TRUST_MASK) == TRUST_EXPIRED) + BUG(); + + if( opt.trust_model==TM_ALWAYS ) + { + if( opt.verbose ) + log_info("No trust check due to `--trust-model always' option\n"); + return 1; + } + + switch(trustlevel & TRUST_MASK) + { + default: + log_error ("invalid trustlevel %u returned from validation layer\n", + trustlevel); + /* fall thru */ + case TRUST_UNKNOWN: + case TRUST_UNDEFINED: + log_info(_("%s: There is no assurance this key belongs" + " to the named user\n"),keystr_from_pk(pk)); + return 0; /* no */ + + case TRUST_MARGINAL: + log_info(_("%s: There is limited assurance this key belongs" + " to the named user\n"),keystr_from_pk(pk)); + return 1; /* yes */ + + case TRUST_FULLY: + if( opt.verbose ) + log_info(_("This key probably belongs to the named user\n")); + return 1; /* yes */ + + case TRUST_ULTIMATE: + if( opt.verbose ) + log_info(_("This key belongs to us\n")); + return 1; /* yes */ + } + + return 1; /*NOTREACHED*/ +} + + +/**************** + * wrapper around do_we_trust, so we can ask whether to use the + * key anyway. + */ +static int +do_we_trust_pre( PKT_public_key *pk, unsigned int trustlevel ) +{ + int rc; + + rc = do_we_trust( pk, trustlevel ); + + if( !opt.batch && !rc ) + { + print_pubkey_info(NULL,pk); + print_fingerprint (pk, NULL, 2); + tty_printf("\n"); + + tty_printf( + _("It is NOT certain that the key belongs to the person named\n" + "in the user ID. If you *really* know what you are doing,\n" + "you may answer the next question with yes.\n")); + + tty_printf("\n"); + + if( cpr_get_answer_is_yes("untrusted_key.override", + _("Use this key anyway? (y/N) ")) ) + rc = 1; + + /* Hmmm: Should we set a flag to tell the user about + * his decision the next time he encrypts for this recipient? + */ + } + + return rc; +} + + +/**************** + * Check whether we can trust this signature. + * Returns: Error if we shall not trust this signatures. + */ +int +check_signatures_trust( PKT_signature *sig ) +{ + PKT_public_key *pk = xmalloc_clear( sizeof *pk ); + unsigned int trustlevel; + int rc=0; + + rc = get_pubkey( pk, sig->keyid ); + if (rc) + { /* this should not happen */ + log_error("Ooops; the key vanished - can't check the trust\n"); + rc = G10ERR_NO_PUBKEY; + goto leave; + } + + if ( opt.trust_model==TM_ALWAYS ) + { + if( !opt.quiet ) + log_info(_("WARNING: Using untrusted key!\n")); + if (opt.with_fingerprint) + print_fingerprint (pk, NULL, 1); + goto leave; + } + + if(pk->maybe_revoked && !pk->is_revoked) + log_info(_("WARNING: this key might be revoked (revocation key" + " not present)\n")); + + trustlevel = get_validity (pk, NULL); + + if ( (trustlevel & TRUST_FLAG_REVOKED) ) + { + write_status( STATUS_KEYREVOKED ); + if(pk->is_revoked==2) + log_info(_("WARNING: This key has been revoked by its" + " designated revoker!\n")); + else + log_info(_("WARNING: This key has been revoked by its owner!\n")); + log_info(_(" This could mean that the signature is forged.\n")); + show_revocation_reason( pk, 0 ); + } + else if ((trustlevel & TRUST_FLAG_SUB_REVOKED) ) + { + write_status( STATUS_KEYREVOKED ); + log_info(_("WARNING: This subkey has been revoked by its owner!\n")); + show_revocation_reason( pk, 0 ); + } + + if ((trustlevel & TRUST_FLAG_DISABLED)) + log_info (_("Note: This key has been disabled.\n")); + + /* If we have PKA information adjust the trustlevel. */ + if (sig->pka_info && sig->pka_info->valid) + { + unsigned char fpr[MAX_FINGERPRINT_LEN]; + PKT_public_key *primary_pk; + size_t fprlen; + int okay; + + + primary_pk = xmalloc_clear (sizeof *primary_pk); + get_pubkey (primary_pk, pk->main_keyid); + fingerprint_from_pk (primary_pk, fpr, &fprlen); + free_public_key (primary_pk); + + if ( fprlen == 20 && !memcmp (sig->pka_info->fpr, fpr, 20) ) + { + okay = 1; + write_status_text (STATUS_PKA_TRUST_GOOD, sig->pka_info->email); + log_info (_("Note: Verified signer's address is `%s'\n"), + sig->pka_info->email); + } + else + { + okay = 0; + write_status_text (STATUS_PKA_TRUST_BAD, sig->pka_info->email); + log_info (_("Note: Signer's address `%s' " + "does not match DNS entry\n"), sig->pka_info->email); + } + + switch ( (trustlevel & TRUST_MASK) ) + { + case TRUST_UNKNOWN: + case TRUST_UNDEFINED: + case TRUST_MARGINAL: + if (okay && opt.verify_options&VERIFY_PKA_TRUST_INCREASE) + { + trustlevel = ((trustlevel & ~TRUST_MASK) | TRUST_FULLY); + log_info (_("trustlevel adjusted to FULL" + " due to valid PKA info\n")); + } + /* (fall through) */ + case TRUST_FULLY: + if (!okay) + { + trustlevel = ((trustlevel & ~TRUST_MASK) | TRUST_NEVER); + log_info (_("trustlevel adjusted to NEVER" + " due to bad PKA info\n")); + } + break; + } + } + + /* Now let the user know what up with the trustlevel. */ + switch ( (trustlevel & TRUST_MASK) ) + { + case TRUST_EXPIRED: + log_info(_("Note: This key has expired!\n")); + print_fingerprint (pk, NULL, 1); + break; + + default: + log_error ("invalid trustlevel %u returned from validation layer\n", + trustlevel); + /* fall thru */ + case TRUST_UNKNOWN: + case TRUST_UNDEFINED: + write_status( STATUS_TRUST_UNDEFINED ); + log_info(_("WARNING: This key is not certified with" + " a trusted signature!\n")); + log_info(_(" There is no indication that the " + "signature belongs to the owner.\n" )); + print_fingerprint (pk, NULL, 1); + break; + + case TRUST_NEVER: + /* currently we won't get that status */ + write_status( STATUS_TRUST_NEVER ); + log_info(_("WARNING: We do NOT trust this key!\n")); + log_info(_(" The signature is probably a FORGERY.\n")); + if (opt.with_fingerprint) + print_fingerprint (pk, NULL, 1); + rc = G10ERR_BAD_SIGN; + break; + + case TRUST_MARGINAL: + write_status( STATUS_TRUST_MARGINAL ); + log_info(_("WARNING: This key is not certified with" + " sufficiently trusted signatures!\n")); + log_info(_(" It is not certain that the" + " signature belongs to the owner.\n" )); + print_fingerprint (pk, NULL, 1); + break; + + case TRUST_FULLY: + write_status( STATUS_TRUST_FULLY ); + if (opt.with_fingerprint) + print_fingerprint (pk, NULL, 1); + break; + + case TRUST_ULTIMATE: + write_status( STATUS_TRUST_ULTIMATE ); + if (opt.with_fingerprint) + print_fingerprint (pk, NULL, 1); + break; + } + + leave: + free_public_key( pk ); + return rc; +} + + +void +release_pk_list( PK_LIST pk_list ) +{ + PK_LIST pk_rover; + + for( ; pk_list; pk_list = pk_rover ) { + pk_rover = pk_list->next; + free_public_key( pk_list->pk ); + xfree( pk_list ); + } +} + + +static int +key_present_in_pk_list(PK_LIST pk_list, PKT_public_key *pk) +{ + for( ; pk_list; pk_list = pk_list->next) + if (cmp_public_keys(pk_list->pk, pk) == 0) + return 0; + + return -1; +} + + +/**************** + * Return a malloced string with a default reciepient if there is any + */ +static char * +default_recipient(void) +{ + PKT_secret_key *sk; + byte fpr[MAX_FINGERPRINT_LEN+1]; + size_t n; + char *p; + int i; + + if( opt.def_recipient ) + return xstrdup( opt.def_recipient ); + if( !opt.def_recipient_self ) + return NULL; + sk = xmalloc_clear( sizeof *sk ); + i = get_seckey_byname( sk, NULL, 0 ); + if( i ) { + free_secret_key( sk ); + return NULL; + } + n = MAX_FINGERPRINT_LEN; + fingerprint_from_sk( sk, fpr, &n ); + free_secret_key( sk ); + p = xmalloc( 2*n+3 ); + *p++ = '0'; + *p++ = 'x'; + for(i=0; i < n; i++ ) + sprintf( p+2*i, "%02X", fpr[i] ); + p -= 2; + return p; +} + +static int +expand_id(const char *id,STRLIST *into,unsigned int flags) +{ + struct groupitem *groups; + int count=0; + + for(groups=opt.grouplist;groups;groups=groups->next) + { + /* need strcasecmp() here, as this should be localized */ + if(strcasecmp(groups->name,id)==0) + { + STRLIST each,sl; + + /* this maintains the current utf8-ness */ + for(each=groups->values;each;each=each->next) + { + sl=add_to_strlist(into,each->d); + sl->flags=flags; + count++; + } + + break; + } + } + + return count; +} + +/* For simplicity, and to avoid potential loops, we only expand once - + you can't make an alias that points to an alias. */ +static STRLIST +expand_group(STRLIST input) +{ + STRLIST sl,output=NULL,rover; + + for(rover=input;rover;rover=rover->next) + if(expand_id(rover->d,&output,rover->flags)==0) + { + /* Didn't find any groups, so use the existing string */ + sl=add_to_strlist(&output,rover->d); + sl->flags=rover->flags; + } + + return output; +} + + +/* This is the central function to collect the keys for recipients. + It is thus used to prepare a public key encryption. encrypt-to + keys, default keys and the keys for the actual recipients are all + collected here. When not in batch mode and no recipient has been + passed on the commandline, the function will also ask for + recipients. + + RCPTS is a string list with the recipients; NULL is an allowed + value but not very useful. Group expansion is done on these names; + they may be in any of the user Id formats we can handle. The flags + bits for each string in the string list are used for: + Bit 0: This is an encrypt-to recipient. + Bit 1: This is a hidden recipient. + + USE is the desired use for the key - usually PUBKEY_USAGE_ENC. + RET_PK_LIST. + + On success a list of keys is stored at the address RET_PK_LIST; the + caller must free this list. On error the value at this address is + not changed. + */ +int +build_pk_list( STRLIST rcpts, PK_LIST *ret_pk_list, unsigned int use ) +{ + PK_LIST pk_list = NULL; + PKT_public_key *pk=NULL; + int rc=0; + int any_recipients=0; + STRLIST rov,remusr; + char *def_rec = NULL; + + /* Try to expand groups if any have been defined. */ + if (opt.grouplist) + remusr = expand_group (rcpts); + else + remusr = rcpts; + + /* Check whether there are any recipients in the list and build the + * list of the encrypt-to ones (we always trust them). */ + for ( rov = remusr; rov; rov = rov->next ) + { + if ( !(rov->flags & 1) ) + { + /* This is a regular recipient; i.e. not an encrypt-to + one. */ + any_recipients = 1; + + /* Hidden recipients are not allowed while in PGP mode, + issue a warning and switch into GnuPG mode. */ + if ((rov->flags&2) && (PGP2 || PGP6 || PGP7 || PGP8)) + { + log_info(_("you may not use %s while in %s mode\n"), + "--hidden-recipient", + compliance_option_string()); + + compliance_failure(); + } + } + else if ( (use & PUBKEY_USAGE_ENC) && !opt.no_encrypt_to ) + { + /* Encryption has been requested and --encrypt-to has not + been disabled. Check this encrypt-to key. */ + pk = xmalloc_clear( sizeof *pk ); + pk->req_usage = use; + + /* We explicitly allow encrypt-to to an disabled key; thus + we pass 1 as last argument. */ + if ( (rc = get_pubkey_byname ( pk, rov->d, NULL, NULL, 1 )) ) + { + free_public_key ( pk ); pk = NULL; + log_error (_("%s: skipped: %s\n"), rov->d, g10_errstr(rc) ); + write_status_text_and_buffer (STATUS_INV_RECP, "0 ", + rov->d, strlen (rov->d), -1); + goto fail; + } + else if ( !(rc=check_pubkey_algo2 (pk->pubkey_algo, use )) ) + { + /* Skip the actual key if the key is already present + * in the list. Add it to our list if not. */ + if (key_present_in_pk_list(pk_list, pk) == 0) + { + free_public_key (pk); pk = NULL; + log_info (_("%s: skipped: public key already present\n"), + rov->d); + } + else + { + PK_LIST r; + r = xmalloc( sizeof *r ); + r->pk = pk; pk = NULL; + r->next = pk_list; + r->flags = (rov->flags&2)?1:0; + pk_list = r; + + /* Hidden encrypt-to recipients are not allowed while + in PGP mode, issue a warning and switch into + GnuPG mode. */ + if ((r->flags&1) && (PGP2 || PGP6 || PGP7 || PGP8)) + { + log_info(_("you may not use %s while in %s mode\n"), + "--hidden-encrypt-to", + compliance_option_string()); + + compliance_failure(); + } + } + } + else + { + /* The public key is not usable for encryption or not + available. */ + free_public_key( pk ); pk = NULL; + log_error(_("%s: skipped: %s\n"), rov->d, g10_errstr(rc) ); + write_status_text_and_buffer (STATUS_INV_RECP, "0 ", + rov->d, strlen (rov->d), -1); + goto fail; + } + } + } + + /* If we don't have any recipients yet and we are not in batch mode + drop into interactive selection mode. */ + if ( !any_recipients && !opt.batch ) + { + int have_def_rec; + char *answer = NULL; + STRLIST backlog = NULL; + + if (pk_list) + any_recipients = 1; + def_rec = default_recipient(); + have_def_rec = !!def_rec; + if ( !have_def_rec ) + tty_printf(_("You did not specify a user ID. (you may use \"-r\")\n")); + + for (;;) + { + rc = 0; + xfree(answer); + if ( have_def_rec ) + { + /* A default recipient is taken as the first entry. */ + answer = def_rec; + def_rec = NULL; + } + else if (backlog) + { + /* This is part of our trick to expand and display groups. */ + answer = pop_strlist (&backlog); + } + else + { + /* Show the list of already collected recipients and ask + for more. */ + PK_LIST iter; + + tty_printf("\n"); + tty_printf(_("Current recipients:\n")); + for (iter=pk_list;iter;iter=iter->next) + { + u32 keyid[2]; + + keyid_from_pk(iter->pk,keyid); + tty_printf("%4u%c/%s %s \"", + nbits_from_pk(iter->pk), + pubkey_letter(iter->pk->pubkey_algo), + keystr(keyid), + datestr_from_pk(iter->pk)); + + if (iter->pk->user_id) + tty_print_utf8_string(iter->pk->user_id->name, + iter->pk->user_id->len); + else + { + size_t n; + char *p = get_user_id( keyid, &n ); + tty_print_utf8_string( p, n ); + xfree(p); + } + tty_printf("\"\n"); + } + + answer = cpr_get_utf8("pklist.user_id.enter", + _("\nEnter the user ID. " + "End with an empty line: ")); + trim_spaces(answer); + cpr_kill_prompt(); + } + + if ( !answer || !*answer ) + { + xfree(answer); + break; /* No more recipients entered - get out of loop. */ + } + + /* Do group expand here too. The trick here is to continue + the loop if any expansion occured. The code above will + then list all expanded keys. */ + if (expand_id(answer,&backlog,0)) + continue; + + /* Get and check key for the current name. */ + if (pk) + free_public_key (pk); + pk = xmalloc_clear( sizeof *pk ); + pk->req_usage = use; + rc = get_pubkey_byname( pk, answer, NULL, NULL, 0 ); + if (rc) + tty_printf(_("No such user ID.\n")); + else if ( !(rc=check_pubkey_algo2(pk->pubkey_algo, use)) ) + { + if ( have_def_rec ) + { + /* No validation for a default recipient. */ + if (!key_present_in_pk_list(pk_list, pk)) + { + free_public_key (pk); pk = NULL; + log_info (_("skipped: public key " + "already set as default recipient\n") ); + } + else + { + PK_LIST r = xmalloc (sizeof *r); + r->pk = pk; pk = NULL; + r->next = pk_list; + r->flags = 0; /* No throwing default ids. */ + pk_list = r; + } + any_recipients = 1; + continue; + } + else + { /* Check validity of this key. */ + int trustlevel; + + trustlevel = get_validity (pk, pk->user_id); + if ( (trustlevel & TRUST_FLAG_DISABLED) ) + { + tty_printf (_("Public key is disabled.\n") ); + } + else if ( do_we_trust_pre (pk, trustlevel) ) + { + /* Skip the actual key if the key is already + * present in the list */ + if (!key_present_in_pk_list(pk_list, pk)) + { + free_public_key(pk); pk = NULL; + log_info(_("skipped: public key already set\n") ); + } + else + { + PK_LIST r; + r = xmalloc( sizeof *r ); + r->pk = pk; pk = NULL; + r->next = pk_list; + r->flags = 0; /* No throwing interactive ids. */ + pk_list = r; + } + any_recipients = 1; + continue; + } + } + } + xfree(def_rec); def_rec = NULL; + have_def_rec = 0; + } + if ( pk ) + { + free_public_key( pk ); + pk = NULL; + } + } + else if ( !any_recipients && (def_rec = default_recipient()) ) + { + /* We are in batch mode and have only a default recipient. */ + pk = xmalloc_clear( sizeof *pk ); + pk->req_usage = use; + + /* The default recipient is allowed to be disabled; thus pass 1 + as last argument. */ + rc = get_pubkey_byname (pk, def_rec, NULL, NULL, 1); + if (rc) + log_error(_("unknown default recipient \"%s\"\n"), def_rec ); + else if ( !(rc=check_pubkey_algo2(pk->pubkey_algo, use)) ) + { + /* Mark any_recipients here since the default recipient + would have been used if it wasn't already there. It + doesn't really matter if we got this key from the default + recipient or an encrypt-to. */ + any_recipients = 1; + if (!key_present_in_pk_list(pk_list, pk)) + log_info (_("skipped: public key already set " + "as default recipient\n")); + else + { + PK_LIST r = xmalloc( sizeof *r ); + r->pk = pk; pk = NULL; + r->next = pk_list; + r->flags = 0; /* No throwing default ids. */ + pk_list = r; + } + } + if ( pk ) + { + free_public_key( pk ); + pk = NULL; + } + xfree(def_rec); def_rec = NULL; + } + else + { + /* General case: Check all keys. */ + any_recipients = 0; + for (; remusr; remusr = remusr->next ) + { + if ( (remusr->flags & 1) ) + continue; /* encrypt-to keys are already handled. */ + + pk = xmalloc_clear( sizeof *pk ); + pk->req_usage = use; + if ( (rc = get_pubkey_byname( pk, remusr->d, NULL, NULL, 0 )) ) + { + /* Key not found or other error. */ + free_public_key( pk ); pk = NULL; + log_error(_("%s: skipped: %s\n"), remusr->d, g10_errstr(rc) ); + write_status_text_and_buffer (STATUS_INV_RECP, "0 ", + remusr->d, strlen (remusr->d), + -1); + goto fail; + } + else if ( !(rc=check_pubkey_algo2(pk->pubkey_algo, use )) ) + { + /* Key found and usable. Check validity. */ + int trustlevel; + + trustlevel = get_validity (pk, pk->user_id); + if ( (trustlevel & TRUST_FLAG_DISABLED) ) + { + /*Key has been disabled. */ + free_public_key(pk); pk = NULL; + log_info(_("%s: skipped: public key is disabled\n"), + remusr->d); + write_status_text_and_buffer (STATUS_INV_RECP, "0 ", + remusr->d, + strlen (remusr->d), + -1); + rc=G10ERR_UNU_PUBKEY; + goto fail; + } + else if ( do_we_trust_pre( pk, trustlevel ) ) + { + /* Note: do_we_trust may have changed the trustlevel */ + + /* We have at least one valid recipient. It doesn't + * matters if this recipient is already present. */ + any_recipients = 1; + + /* Skip the actual key if the key is already present + * in the list */ + if (!key_present_in_pk_list(pk_list, pk)) + { + free_public_key(pk); pk = NULL; + log_info(_("%s: skipped: public key already present\n"), + remusr->d); + } + else + { + PK_LIST r; + r = xmalloc( sizeof *r ); + r->pk = pk; pk = NULL; + r->next = pk_list; + r->flags = (remusr->flags&2)?1:0; + pk_list = r; + } + } + else + { /* We don't trust this key. */ + free_public_key( pk ); pk = NULL; + write_status_text_and_buffer (STATUS_INV_RECP, "10 ", + remusr->d, + strlen (remusr->d), + -1); + rc=G10ERR_UNU_PUBKEY; + goto fail; + } + } + else + { + /* Key found but not usable for us (e.g. sign-only key). */ + free_public_key( pk ); pk = NULL; + write_status_text_and_buffer (STATUS_INV_RECP, "0 ", + remusr->d, + strlen (remusr->d), + -1); + log_error(_("%s: skipped: %s\n"), remusr->d, g10_errstr(rc) ); + goto fail; + } + } + } + + if ( !rc && !any_recipients ) + { + log_error(_("no valid addressees\n")); + write_status_text (STATUS_NO_RECP, "0"); + rc = G10ERR_NO_USER_ID; + } + + fail: + + if ( rc ) + release_pk_list( pk_list ); + else + *ret_pk_list = pk_list; + if (opt.grouplist) + free_strlist(remusr); + return rc; +} + + +/* In pgp6 mode, disallow all ciphers except IDEA (1), 3DES (2), and + CAST5 (3), all hashes except MD5 (1), SHA1 (2), and RIPEMD160 (3), + and all compressions except none (0) and ZIP (1). pgp7 and pgp8 + mode expands the cipher list to include AES128 (7), AES192 (8), + AES256 (9), and TWOFISH (10). pgp8 adds the SHA-256 hash (8). For + a true PGP key all of this is unneeded as they are the only items + present in the preferences subpacket, but checking here covers the + weird case of encrypting to a key that had preferences from a + different implementation which was then used with PGP. I am not + completely comfortable with this as the right thing to do, as it + slightly alters the list of what the user is supposedly requesting. + It is not against the RFC however, as the preference chosen will + never be one that the user didn't specify somewhere ("The + implementation may use any mechanism to pick an algorithm in the + intersection"), and PGP has no mechanism to fix such a broken + preference list, so I'm including it. -dms */ + +int +algo_available( preftype_t preftype, int algo, const union pref_hint *hint ) +{ + if( preftype == PREFTYPE_SYM ) + { + if(PGP6 && (algo != CIPHER_ALGO_IDEA + && algo != CIPHER_ALGO_3DES + && algo != CIPHER_ALGO_CAST5)) + return 0; + + if(PGP7 && (algo != CIPHER_ALGO_IDEA + && algo != CIPHER_ALGO_3DES + && algo != CIPHER_ALGO_CAST5 + && algo != CIPHER_ALGO_AES + && algo != CIPHER_ALGO_AES192 + && algo != CIPHER_ALGO_AES256 + && algo != CIPHER_ALGO_TWOFISH)) + return 0; + + /* PGP8 supports all the ciphers we do.. */ + + return algo && !check_cipher_algo( algo ); + } + else if( preftype == PREFTYPE_HASH ) + { + if(hint && hint->digest_length) + { + if(hint->digest_length!=20 || opt.flags.dsa2) + { + /* If --enable-dsa2 is set or the hash isn't 160 bits + (which implies DSA2), then we'll accept a hash that + is larger than we need. Otherwise we won't accept + any hash that isn't exactly the right size. */ + if(hint->digest_length > md_digest_length(algo)) + return 0; + } + else if(hint->digest_length != md_digest_length(algo)) + return 0; + } + + if((PGP6 || PGP7) && (algo != DIGEST_ALGO_MD5 + && algo != DIGEST_ALGO_SHA1 + && algo != DIGEST_ALGO_RMD160)) + return 0; + + + if(PGP8 && (algo != DIGEST_ALGO_MD5 + && algo != DIGEST_ALGO_SHA1 + && algo != DIGEST_ALGO_RMD160 + && algo != DIGEST_ALGO_SHA256)) + return 0; + + return algo && !check_digest_algo( algo ); + } + else if( preftype == PREFTYPE_ZIP ) + { + if((PGP6 || PGP7) && (algo != COMPRESS_ALGO_NONE + && algo != COMPRESS_ALGO_ZIP)) + return 0; + + /* PGP8 supports all the compression algos we do */ + + return !check_compress_algo( algo ); + } + else + return 0; +} + + + +/**************** + * Return -1 if we could not find an algorithm. + */ +int +select_algo_from_prefs(PK_LIST pk_list, int preftype, + int request, const union pref_hint *hint) +{ + PK_LIST pkr; + u32 bits[8]; + const prefitem_t *prefs; + int i, j; + int compr_hack=0; + int any; + + if( !pk_list ) + return -1; + + memset( bits, ~0, 8 * sizeof *bits ); + for( pkr = pk_list; pkr; pkr = pkr->next ) { + u32 mask[8]; + + memset( mask, 0, 8 * sizeof *mask ); + if( preftype == PREFTYPE_SYM ) { + if( PGP2 && + pkr->pk->version < 4 && + pkr->pk->selfsigversion < 4 ) + mask[0] |= (1<<1); /* IDEA is implicitly there for v3 keys + with v3 selfsigs (rfc2440:12.1) if + --pgp2 mode is on. This doesn't + mean it's actually available, of + course. */ + else + mask[0] |= (1<<2); /* 3DES is implicitly there for everyone else */ + } + else if( preftype == PREFTYPE_HASH ) { + /* While I am including this code for completeness, note + that currently --pgp2 mode locks the hash at MD5, so this + function will never even be called. Even if the hash + wasn't locked at MD5, we don't support sign+encrypt in + --pgp2 mode, and that's the only time PREFTYPE_HASH is + used anyway. -dms */ + if( PGP2 && + pkr->pk->version < 4 && + pkr->pk->selfsigversion < 4 ) + mask[0] |= (1<<1); /* MD5 is there for v3 keys with v3 + selfsigs when --pgp2 is on. */ + else + mask[0] |= (1<<2); /* SHA1 is there for everyone else */ + } + else if( preftype == PREFTYPE_ZIP ) + mask[0] |= (1<<0); /* Uncompressed is implicit */ + + if (pkr->pk->user_id) /* selected by user ID */ + prefs = pkr->pk->user_id->prefs; + else + prefs = pkr->pk->prefs; + + any = 0; + if( prefs ) { + for (i=0; prefs[i].type; i++ ) { + if( prefs[i].type == preftype ) { + mask[prefs[i].value/32] |= 1 << (prefs[i].value%32); + any = 1; + } + } + } + + if( (!prefs || !any) && preftype == PREFTYPE_ZIP ) { + mask[0] |= 3; /* asume no_compression and old pgp */ + compr_hack = 1; + } + +#if 0 + log_debug("pref mask=%08lX%08lX%08lX%08lX%08lX%08lX%08lX%08lX\n", + (ulong)mask[7], (ulong)mask[6], (ulong)mask[5], (ulong)mask[4], + (ulong)mask[3], (ulong)mask[2], (ulong)mask[1], (ulong)mask[0]); +#endif + for(i=0; i < 8; i++ ) + bits[i] &= mask[i]; +#if 0 + log_debug("pref bits=%08lX%08lX%08lX%08lX%08lX%08lX%08lX%08lX\n", + (ulong)bits[7], (ulong)bits[6], (ulong)bits[5], (ulong)bits[4], + (ulong)bits[3], (ulong)bits[2], (ulong)bits[1], (ulong)bits[0]); +#endif + } + /* usable algorithms are now in bits + * We now use the last key from pk_list to select + * the algorithm we want to use. there are no + * preferences for the last key, we select the one + * corresponding to first set bit. + */ + i = -1; + any = 0; + + /* Can we use the requested algorithm? */ + if(request>-1 && (bits[request/32] & (1<<(request%32))) && + algo_available(preftype,request,hint)) + return request; + + /* If we have personal prefs set, use them instead of the last key */ + if(preftype==PREFTYPE_SYM && opt.personal_cipher_prefs) + prefs=opt.personal_cipher_prefs; + else if(preftype==PREFTYPE_HASH && opt.personal_digest_prefs) + prefs=opt.personal_digest_prefs; + else if(preftype==PREFTYPE_ZIP && opt.personal_compress_prefs) + prefs=opt.personal_compress_prefs; + + if( prefs ) { + for(j=0; prefs[j].type; j++ ) { + if( prefs[j].type == preftype ) { + if( (bits[prefs[j].value/32] & (1<<(prefs[j].value%32))) ) { + if( algo_available( preftype, prefs[j].value, hint ) ) { + any = 1; + i = prefs[j].value; + break; + } + } + } + } + } + if( !prefs || !any ) { + for(j=0; j < 256; j++ ) + if( (bits[j/32] & (1<<(j%32))) ) { + if( algo_available( preftype, j, hint ) ) { + i = j; + break; + } + } + } + +#if 0 + log_debug("prefs of type %d: selected %d\n", preftype, i ); +#endif + if( compr_hack && !i ) { + /* selected no compression, but we should check whether + * algorithm 1 is also available (the ordering is not relevant + * in this case). */ + if( bits[0] & (1<<1) ) + i = 1; /* yep; we can use compression algo 1 */ + } + + /* "If you are building an authentication system, the recipient + may specify a preferred signing algorithm. However, the signer + would be foolish to use a weak algorithm simply because the + recipient requests it." RFC2440:13. If we settle on MD5, and + SHA1 is also available, use SHA1 instead. Of course, if the + user intentionally chose MD5 (by putting it in their personal + prefs), then we should do what they say. */ + + if(preftype==PREFTYPE_HASH && + i==DIGEST_ALGO_MD5 && (bits[0] & (1<<DIGEST_ALGO_SHA1))) + { + i=DIGEST_ALGO_SHA1; + + if(opt.personal_digest_prefs) + for(j=0; prefs[j].type; j++ ) + if(opt.personal_digest_prefs[j].type==PREFTYPE_HASH && + opt.personal_digest_prefs[j].value==DIGEST_ALGO_MD5) + { + i=DIGEST_ALGO_MD5; + break; + } + } + + return i; +} + +/* + * Select the MDC flag from the pk_list. We can only use MDC if all recipients + * support this feature + */ +int +select_mdc_from_pklist (PK_LIST pk_list) +{ + PK_LIST pkr; + + if( !pk_list ) + return 0; + + for (pkr = pk_list; pkr; pkr = pkr->next) { + int mdc; + + if (pkr->pk->user_id) /* selected by user ID */ + mdc = pkr->pk->user_id->flags.mdc; + else + mdc = pkr->pk->mdc_feature; + if (!mdc) + return 0; /* at least one recipient does not support it */ + } + return 1; /* can be used */ +} diff --git a/g10/plaintext.c b/g10/plaintext.c new file mode 100644 index 0000000..243296b --- /dev/null +++ b/g10/plaintext.c @@ -0,0 +1,589 @@ +/* plaintext.c - process plaintext packets + * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, + * 2006 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <assert.h> +#include <sys/types.h> +#ifdef HAVE_DOSISH_SYSTEM +#include <fcntl.h> /* for setmode() */ +#endif + +#include "util.h" +#include "memory.h" +#include "options.h" +#include "packet.h" +#include "ttyio.h" +#include "filter.h" +#include "main.h" +#include "status.h" +#include "i18n.h" + + +/**************** + * Handle a plaintext packet. If MFX is not NULL, update the MDs + * Note: we should use the filter stuff here, but we have to add some + * easy mimic to set a read limit, so we calculate only the + * bytes from the plaintext. + */ +int +handle_plaintext( PKT_plaintext *pt, md_filter_context_t *mfx, + int nooutput, int clearsig ) +{ + char *fname = NULL; + FILE *fp = NULL; + static off_t count=0; + int rc = 0; + int c; + int convert = (pt->mode == 't' || pt->mode == 'u'); +#ifdef __riscos__ + int filetype = 0xfff; +#endif + + /* Let people know what the plaintext info is. This allows the + receiving program to try and do something different based on + the format code (say, recode UTF-8 to local). */ + if(!nooutput && is_status_enabled()) + { + char status[50]; + + sprintf(status,"%X %lu ",(byte)pt->mode,(ulong)pt->timestamp); + write_status_text_and_buffer(STATUS_PLAINTEXT, + status,pt->name,pt->namelen,0); + + if(!pt->is_partial) + { + sprintf(status,"%lu",(ulong)pt->len); + write_status_text(STATUS_PLAINTEXT_LENGTH,status); + } + } + + /* create the filename as C string */ + if( nooutput ) + ; + else if( opt.outfile ) { + fname = xmalloc( strlen( opt.outfile ) + 1); + strcpy(fname, opt.outfile ); + } + else if( pt->namelen == 8 && !memcmp( pt->name, "_CONSOLE", 8 ) ) { + log_info(_("data not saved; use option \"--output\" to save it\n")); + nooutput = 1; + } + else if( !opt.flags.use_embedded_filename ) { + fname = make_outfile_name( iobuf_get_real_fname(pt->buf) ); + if( !fname ) + fname = ask_outfile_name( pt->name, pt->namelen ); + if( !fname ) { + rc = G10ERR_CREATE_FILE; + goto leave; + } + } + else + fname=utf8_to_native(pt->name,pt->namelen,0); + + if( nooutput ) + ; + else if ( iobuf_is_pipe_filename (fname) || !*fname) + { + /* No filename or "-" given; write to stdout. */ + fp = stdout; +#ifdef HAVE_DOSISH_SYSTEM + setmode ( fileno(fp) , O_BINARY ); +#endif + } + else { + while( !overwrite_filep (fname) ) { + char *tmp = ask_outfile_name (NULL, 0); + if ( !tmp || !*tmp ) { + xfree (tmp); + rc = G10ERR_CREATE_FILE; + goto leave; + } + xfree (fname); + fname = tmp; + } + } + +#ifndef __riscos__ + if( fp || nooutput ) + ; + else if (is_secured_filename (fname)) + { + errno = EPERM; + log_error(_("error creating `%s': %s\n"), fname, strerror(errno) ); + rc = G10ERR_CREATE_FILE; + goto leave; + } + else if( !(fp = fopen(fname,"wb")) ) { + log_error(_("error creating `%s': %s\n"), fname, strerror(errno) ); + rc = G10ERR_CREATE_FILE; + goto leave; + } +#else /* __riscos__ */ + /* If no output filename was given, i.e. we constructed it, + convert all '.' in fname to '/' but not vice versa as + we don't create directories! */ + if( !opt.outfile ) + for( c=0; fname[c]; ++c ) + if( fname[c] == '.' ) + fname[c] = '/'; + + if( fp || nooutput ) + ; + else { + fp = fopen(fname,"wb"); + if( !fp ) { + log_error(_("error creating `%s': %s\n"), fname, strerror(errno) ); + rc = G10ERR_CREATE_FILE; + if (errno == 106) + log_info("Do output file and input file have the same name?\n"); + goto leave; + } + + /* If there's a ,xxx extension in the embedded filename, + use that, else check whether the user input (in fname) + has a ,xxx appended, then use that in preference */ + if( (c = riscos_get_filetype_from_string( pt->name, + pt->namelen )) != -1 ) + filetype = c; + if( (c = riscos_get_filetype_from_string( fname, + strlen(fname) )) != -1 ) + filetype = c; + riscos_set_filetype_by_number(fname, filetype); + } +#endif /* __riscos__ */ + + if( !pt->is_partial ) { + /* We have an actual length (which might be zero). */ + + if (clearsig) { + log_error ("clearsig encountered while not expected\n"); + rc = G10ERR_UNEXPECTED; + goto leave; + } + + if( convert ) { /* text mode */ + for( ; pt->len; pt->len-- ) { + if( (c = iobuf_get(pt->buf)) == -1 ) { + log_error("Problem reading source (%u bytes remaining)\n", + (unsigned)pt->len); + rc = G10ERR_READ_FILE; + goto leave; + } + if( mfx->md ) + md_putc(mfx->md, c ); +#ifndef HAVE_DOSISH_SYSTEM + if( c == '\r' ) /* convert to native line ending */ + continue; /* fixme: this hack might be too simple */ +#endif + if( fp ) + { + if(opt.max_output && (++count)>opt.max_output) + { + log_error("Error writing to `%s': %s\n", + fname,"exceeded --max-output limit\n"); + rc = G10ERR_WRITE_FILE; + goto leave; + } + else if( putc( c, fp ) == EOF ) + { + log_error("Error writing to `%s': %s\n", + fname, strerror(errno) ); + rc = G10ERR_WRITE_FILE; + goto leave; + } + } + } + } + else { /* binary mode */ + byte *buffer = xmalloc( 32768 ); + while( pt->len ) { + int len = pt->len > 32768 ? 32768 : pt->len; + len = iobuf_read( pt->buf, buffer, len ); + if( len == -1 ) { + log_error("Problem reading source (%u bytes remaining)\n", + (unsigned)pt->len); + rc = G10ERR_READ_FILE; + xfree( buffer ); + goto leave; + } + if( mfx->md ) + md_write( mfx->md, buffer, len ); + if( fp ) + { + if(opt.max_output && (count+=len)>opt.max_output) + { + log_error("Error writing to `%s': %s\n", + fname,"exceeded --max-output limit\n"); + rc = G10ERR_WRITE_FILE; + xfree( buffer ); + goto leave; + } + else if( fwrite( buffer, 1, len, fp ) != len ) + { + log_error("Error writing to `%s': %s\n", + fname, strerror(errno) ); + rc = G10ERR_WRITE_FILE; + xfree( buffer ); + goto leave; + } + } + pt->len -= len; + } + xfree( buffer ); + } + } + else if( !clearsig ) { + if( convert ) { /* text mode */ + while( (c = iobuf_get(pt->buf)) != -1 ) { + if( mfx->md ) + md_putc(mfx->md, c ); +#ifndef HAVE_DOSISH_SYSTEM + if( convert && c == '\r' ) + continue; /* fixme: this hack might be too simple */ +#endif + if( fp ) + { + if(opt.max_output && (++count)>opt.max_output) + { + log_error("Error writing to `%s': %s\n", + fname,"exceeded --max-output limit\n"); + rc = G10ERR_WRITE_FILE; + goto leave; + } + else if( putc( c, fp ) == EOF ) + { + log_error("Error writing to `%s': %s\n", + fname, strerror(errno) ); + rc = G10ERR_WRITE_FILE; + goto leave; + } + } + } + } + else { /* binary mode */ + byte *buffer = xmalloc( 32768 ); + int eof; + for( eof=0; !eof; ) { + /* Why do we check for len < 32768: + * If we won't, we would practically read 2 EOFs but + * the first one has already popped the block_filter + * off and therefore we don't catch the boundary. + * So, always assume EOF if iobuf_read returns less bytes + * then requested */ + int len = iobuf_read( pt->buf, buffer, 32768 ); + if( len == -1 ) + break; + if( len < 32768 ) + eof = 1; + if( mfx->md ) + md_write( mfx->md, buffer, len ); + if( fp ) + { + if(opt.max_output && (count+=len)>opt.max_output) + { + log_error("Error writing to `%s': %s\n", + fname,"exceeded --max-output limit\n"); + rc = G10ERR_WRITE_FILE; + xfree( buffer ); + goto leave; + } + else if( fwrite( buffer, 1, len, fp ) != len ) { + log_error("Error writing to `%s': %s\n", + fname, strerror(errno) ); + rc = G10ERR_WRITE_FILE; + xfree( buffer ); + goto leave; + } + } + } + xfree( buffer ); + } + pt->buf = NULL; + } + else { /* clear text signature - don't hash the last cr,lf */ + int state = 0; + + while( (c = iobuf_get(pt->buf)) != -1 ) { + if( fp ) + { + if(opt.max_output && (++count)>opt.max_output) + { + log_error("Error writing to `%s': %s\n", + fname,"exceeded --max-output limit\n"); + rc = G10ERR_WRITE_FILE; + goto leave; + } + else if( putc( c, fp ) == EOF ) + { + log_error("Error writing to `%s': %s\n", + fname, strerror(errno) ); + rc = G10ERR_WRITE_FILE; + goto leave; + } + } + if( !mfx->md ) + continue; + if( state == 2 ) { + md_putc(mfx->md, '\r' ); + md_putc(mfx->md, '\n' ); + state = 0; + } + if( !state ) { + if( c == '\r' ) + state = 1; + else if( c == '\n' ) + state = 2; + else + md_putc(mfx->md, c ); + } + else if( state == 1 ) { + if( c == '\n' ) + state = 2; + else { + md_putc(mfx->md, '\r' ); + if( c == '\r' ) + state = 1; + else { + state = 0; + md_putc(mfx->md, c ); + } + } + } + } + pt->buf = NULL; + } + + if( fp && fp != stdout && fclose(fp) ) { + log_error("Error closing `%s': %s\n", fname, strerror(errno) ); + fp = NULL; + rc = G10ERR_WRITE_FILE; + goto leave; + } + fp = NULL; + + leave: + if( fp && fp != stdout ) + fclose(fp); + xfree(fname); + return rc; +} + +static void +do_hash( MD_HANDLE md, MD_HANDLE md2, IOBUF fp, int textmode ) +{ + text_filter_context_t tfx; + int c; + + if( textmode ) { + memset( &tfx, 0, sizeof tfx); + iobuf_push_filter( fp, text_filter, &tfx ); + } + if( md2 ) { /* work around a strange behaviour in pgp2 */ + /* It seems that at least PGP5 converts a single CR to a CR,LF too */ + int lc = -1; + while( (c = iobuf_get(fp)) != -1 ) { + if( c == '\n' && lc == '\r' ) + md_putc(md2, c); + else if( c == '\n' ) { + md_putc(md2, '\r'); + md_putc(md2, c); + } + else if( c != '\n' && lc == '\r' ) { + md_putc(md2, '\n'); + md_putc(md2, c); + } + else + md_putc(md2, c); + + if( md ) + md_putc(md, c ); + lc = c; + } + } + else { + while( (c = iobuf_get(fp)) != -1 ) { + if( md ) + md_putc(md, c ); + } + } +} + + +/**************** + * Ask for the detached datafile and calculate the digest from it. + * INFILE is the name of the input file. + */ +int +ask_for_detached_datafile( MD_HANDLE md, MD_HANDLE md2, + const char *inname, int textmode ) +{ + progress_filter_context_t pfx; + char *answer = NULL; + IOBUF fp; + int rc = 0; + + fp = open_sigfile( inname, &pfx ); /* open default file */ + + if( !fp && !opt.batch ) { + int any=0; + tty_printf(_("Detached signature.\n")); + do { + char *name; + xfree(answer); + tty_enable_completion(NULL); + name = cpr_get("detached_signature.filename", + _("Please enter name of data file: ")); + tty_disable_completion(); + cpr_kill_prompt(); + answer=make_filename(name,(void *)NULL); + xfree(name); + + if( any && !*answer ) { + rc = G10ERR_READ_FILE; + goto leave; + } + fp = iobuf_open(answer); + if (fp && is_secured_file (iobuf_get_fd (fp))) + { + iobuf_close (fp); + fp = NULL; + errno = EPERM; + } + if( !fp && errno == ENOENT ) { + tty_printf("No such file, try again or hit enter to quit.\n"); + any++; + } + else if( !fp ) + { + log_error(_("can't open `%s': %s\n"), answer, strerror(errno)); + rc = G10ERR_READ_FILE; + goto leave; + } + } while( !fp ); + } + + if( !fp ) { + if( opt.verbose ) + log_info(_("reading stdin ...\n")); + fp = iobuf_open( NULL ); + assert(fp); + } + do_hash( md, md2, fp, textmode ); + iobuf_close(fp); + + leave: + xfree(answer); + return rc; +} + + + +/**************** + * Hash the given files and append the hash to hash context md. + * If FILES is NULL, hash stdin. + */ +int +hash_datafiles( MD_HANDLE md, MD_HANDLE md2, STRLIST files, + const char *sigfilename, int textmode ) +{ + progress_filter_context_t pfx; + IOBUF fp; + STRLIST sl; + + if( !files ) { + /* check whether we can open the signed material */ + fp = open_sigfile( sigfilename, &pfx ); + if( fp ) { + do_hash( md, md2, fp, textmode ); + iobuf_close(fp); + return 0; + } + log_error (_("no signed data\n")); + return G10ERR_OPEN_FILE; + } + + + for (sl=files; sl; sl = sl->next ) { + fp = iobuf_open( sl->d ); + if (fp && is_secured_file (iobuf_get_fd (fp))) + { + iobuf_close (fp); + fp = NULL; + errno = EPERM; + } + if( !fp ) { + log_error(_("can't open signed data `%s'\n"), + print_fname_stdin(sl->d)); + return G10ERR_OPEN_FILE; + } + handle_progress (&pfx, fp, sl->d); + do_hash( md, md2, fp, textmode ); + iobuf_close(fp); + } + + return 0; +} + + +/* Set up a plaintext packet with the appropriate filename. If there + is a --set-filename, use it (it's already UTF8). If there is a + regular filename, UTF8-ize it if necessary. If there is no + filenames at all, set the field empty. */ + +PKT_plaintext * +setup_plaintext_name(const char *filename,IOBUF iobuf) +{ + PKT_plaintext *pt; + + if(filename || opt.set_filename) + { + char *s; + + if(opt.set_filename) + s=make_basename(opt.set_filename,iobuf_get_real_fname(iobuf)); + else if(filename && !opt.flags.utf8_filename) + { + char *tmp=native_to_utf8(filename); + s=make_basename(tmp,iobuf_get_real_fname(iobuf)); + xfree(tmp); + } + else + s=make_basename(filename,iobuf_get_real_fname(iobuf)); + + pt = xmalloc (sizeof *pt + strlen(s) - 1); + pt->namelen = strlen (s); + memcpy (pt->name, s, pt->namelen); + xfree (s); + } + else + { + /* no filename */ + pt = xmalloc (sizeof *pt - 1); + pt->namelen = 0; + } + + return pt; +} diff --git a/g10/progress.c b/g10/progress.c new file mode 100644 index 0000000..8c8265f --- /dev/null +++ b/g10/progress.c @@ -0,0 +1,118 @@ +/* progress.c + * Copyright (C) 2003 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#include <config.h> +#include <stdio.h> + +#include "iobuf.h" +#include "filter.h" +#include "status.h" +#include "util.h" +#include "options.h" + +/**************** + * The filter is used to report progress to the user. + */ +int +progress_filter (void *opaque, int control, + IOBUF a, byte *buf, size_t *ret_len) +{ + int rc = 0; + progress_filter_context_t *pfx = opaque; + + if (control == IOBUFCTRL_INIT) + { + char buffer[50]; + + pfx->last = 0; + pfx->offset = 0; + pfx->last_time = make_timestamp (); + + sprintf (buffer, "%.20s ? %lu %lu", + pfx->what? pfx->what : "?", + pfx->offset, + pfx->total); + write_status_text (STATUS_PROGRESS, buffer); + } + else if (control == IOBUFCTRL_UNDERFLOW) + { + u32 timestamp = make_timestamp (); + int len = iobuf_read (a, buf, *ret_len); + + if (len >= 0) + { + pfx->offset += len; + *ret_len = len; + } + else + { + *ret_len = 0; + rc = -1; + } + if ((len == -1 && pfx->offset != pfx->last) + || timestamp - pfx->last_time > 0) + { + char buffer[50]; + + sprintf (buffer, "%.20s ? %lu %lu", + pfx->what? pfx->what : "?", + pfx->offset, + pfx->total); + write_status_text (STATUS_PROGRESS, buffer); + + pfx->last = pfx->offset; + pfx->last_time = timestamp; + } + } + else if (control == IOBUFCTRL_FREE) + { + /* Note, that we must always dealloc resources of a filter + within the filter handler and not anywhere else. (We set it + to NULL and check all uses just in case.) */ + xfree (pfx->what); + pfx->what = NULL; + } + else if (control == IOBUFCTRL_DESC) + *(char**)buf = "progress_filter"; + return rc; +} + +void +handle_progress (progress_filter_context_t *pfx, IOBUF inp, const char *name) +{ + off_t filesize = 0; + + if (!opt.enable_progress_filter) + return; + + if (!is_status_enabled ()) + return; + + if ( !iobuf_is_pipe_filename (name) && *name ) + filesize = iobuf_get_filelength (inp, NULL); + else if (opt.set_filesize) + filesize = opt.set_filesize; + + /* register the progress filter */ + pfx->what = xstrdup (name ? name : "stdin"); + pfx->total = filesize; + iobuf_push_filter (inp, progress_filter, pfx); +} diff --git a/g10/pubkey-enc.c b/g10/pubkey-enc.c new file mode 100644 index 0000000..cffa79c --- /dev/null +++ b/g10/pubkey-enc.c @@ -0,0 +1,355 @@ +/* pubkey-enc.c - public key encoded packet handling + * Copyright (C) 1998, 1999, 2000, 2001, 2002 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include "util.h" +#include "memory.h" +#include "packet.h" +#include "mpi.h" +#include "keydb.h" +#include "trustdb.h" +#include "cipher.h" +#include "status.h" +#include "options.h" +#include "main.h" +#include "i18n.h" +#include "cardglue.h" + +static int get_it( PKT_pubkey_enc *k, + DEK *dek, PKT_secret_key *sk, u32 *keyid ); + + +/* check that the given algo is mentioned in one of the valid user IDs */ +static int +is_algo_in_prefs ( KBNODE keyblock, preftype_t type, int algo ) +{ + KBNODE k; + + for (k=keyblock; k; k=k->next) { + if (k->pkt->pkttype == PKT_USER_ID) { + PKT_user_id *uid = k->pkt->pkt.user_id; + prefitem_t *prefs = uid->prefs; + + if (uid->created && prefs && + !uid->is_revoked && !uid->is_expired ) { + for (; prefs->type; prefs++ ) + if (prefs->type == type && prefs->value == algo) + return 1; + } + } + } + return 0; +} + + +/**************** + * Get the session key from a pubkey enc packet and return + * it in DEK, which should have been allocated in secure memory. + */ +int +get_session_key( PKT_pubkey_enc *k, DEK *dek ) +{ + PKT_secret_key *sk = NULL; + int rc; + + rc = check_pubkey_algo2 (k->pubkey_algo, PUBKEY_USAGE_ENC); + if( rc ) + goto leave; + + if( (k->keyid[0] || k->keyid[1]) && !opt.try_all_secrets ) { + sk = xmalloc_clear( sizeof *sk ); + sk->pubkey_algo = k->pubkey_algo; /* we want a pubkey with this algo*/ + if( !(rc = get_seckey( sk, k->keyid )) ) + rc = get_it( k, dek, sk, k->keyid ); + } + else { /* anonymous receiver: Try all available secret keys */ + void *enum_context = NULL; + u32 keyid[2]; + char *p; + + for(;;) { + if( sk ) + free_secret_key( sk ); + sk = xmalloc_clear( sizeof *sk ); + rc=enum_secret_keys( &enum_context, sk, 1, 0); + if( rc ) { + rc = G10ERR_NO_SECKEY; + break; + } + if( sk->pubkey_algo != k->pubkey_algo ) + continue; + keyid_from_sk( sk, keyid ); + log_info(_("anonymous recipient; trying secret key %s ...\n"), + keystr(keyid)); + + if(!opt.try_all_secrets && !is_status_enabled()) + { + p=get_last_passphrase(); + set_next_passphrase(p); + xfree(p); + } + + rc = check_secret_key( sk, opt.try_all_secrets?1:-1 ); /* ask + only + once */ + if( !rc ) + { + rc = get_it( k, dek, sk, keyid ); + /* Successfully checked the secret key (either it was + a card, had no passphrase, or had the right + passphrase) but couldn't decrypt the session key, + so thus that key is not the anonymous recipient. + Move the next passphrase into last for the next + round. We only do this if the secret key was + successfully checked as in the normal case, + check_secret_key handles this for us via + passphrase_to_dek */ + if(rc) + next_to_last_passphrase(); + } + + if( !rc ) + { + log_info(_("okay, we are the anonymous recipient.\n") ); + break; + } + } + enum_secret_keys( &enum_context, NULL, 0, 0 ); /* free context */ + } + + leave: + if( sk ) + free_secret_key( sk ); + return rc; +} + + +static int +get_it( PKT_pubkey_enc *enc, DEK *dek, PKT_secret_key *sk, u32 *keyid ) +{ + int rc; + MPI plain_dek = NULL; + byte *frame = NULL; + unsigned n, nframe; + u16 csum, csum2; + + int card = 0; + + if (sk->is_protected && sk->protect.s2k.mode == 1002) + { /* Note, that we only support RSA for now. */ +#ifdef ENABLE_CARD_SUPPORT + unsigned char *rbuf; + size_t rbuflen; + char *snbuf; + unsigned char *indata = NULL; + unsigned int indatalen; + + snbuf = serialno_and_fpr_from_sk (sk->protect.iv, sk->protect.ivlen, sk); + + indata = mpi_get_buffer (enc->data[0], &indatalen, NULL); + if (!indata) + BUG (); + + rc = agent_scd_pkdecrypt (snbuf, indata, indatalen, &rbuf, &rbuflen); + xfree (snbuf); + xfree (indata); + if (rc) + goto leave; + + frame = rbuf; + nframe = rbuflen; + card = 1; +#else + rc = G10ERR_UNSUPPORTED; + goto leave; +#endif /*!ENABLE_CARD_SUPPORT*/ + } + else + { + rc = pubkey_decrypt(sk->pubkey_algo, &plain_dek, enc->data, sk->skey ); + if( rc ) + goto leave; + frame = mpi_get_buffer( plain_dek, &nframe, NULL ); + mpi_free( plain_dek ); plain_dek = NULL; + } + + /* Now get the DEK (data encryption key) from the frame + * + * Old versions encode the DEK in in this format (msb is left): + * + * 0 1 DEK(16 bytes) CSUM(2 bytes) 0 RND(n bytes) 2 + * + * Later versions encode the DEK like this: + * + * 0 2 RND(n bytes) 0 A DEK(k bytes) CSUM(2 bytes) + * + * (mpi_get_buffer already removed the leading zero). + * + * RND are non-zero randow bytes. + * A is the cipher algorithm + * DEK is the encryption key (session key) with length k + * CSUM + */ + if( DBG_CIPHER ) + log_hexdump("DEK frame:", frame, nframe ); + n=0; + if (!card) + { + if( n + 7 > nframe ) + { rc = G10ERR_WRONG_SECKEY; goto leave; } + if( frame[n] == 1 && frame[nframe-1] == 2 ) { + log_info(_("old encoding of the DEK is not supported\n")); + rc = G10ERR_CIPHER_ALGO; + goto leave; + } + if( frame[n] != 2 ) /* somethink is wrong */ + { rc = G10ERR_WRONG_SECKEY; goto leave; } + for(n++; n < nframe && frame[n]; n++ ) /* skip the random bytes */ + ; + n++; /* and the zero byte */ + } + + if( n + 4 > nframe ) + { rc = G10ERR_WRONG_SECKEY; goto leave; } + + dek->keylen = nframe - (n+1) - 2; + dek->algo = frame[n++]; + if( dek->algo == CIPHER_ALGO_IDEA ) + write_status(STATUS_RSA_OR_IDEA); + rc = check_cipher_algo( dek->algo ); + if( rc ) { + if( !opt.quiet && rc == G10ERR_CIPHER_ALGO ) { + log_info(_("cipher algorithm %d%s is unknown or disabled\n"), + dek->algo, dek->algo == CIPHER_ALGO_IDEA? " (IDEA)":""); + if(dek->algo==CIPHER_ALGO_IDEA) + idea_cipher_warn(0); + } + dek->algo = 0; + goto leave; + } + if( (dek->keylen*8) != cipher_get_keylen( dek->algo ) ) { + rc = G10ERR_WRONG_SECKEY; + goto leave; + } + + /* copy the key to DEK and compare the checksum */ + csum = frame[nframe-2] << 8; + csum |= frame[nframe-1]; + memcpy( dek->key, frame+n, dek->keylen ); + for( csum2=0, n=0; n < dek->keylen; n++ ) + csum2 += dek->key[n]; + if( csum != csum2 ) { + rc = G10ERR_WRONG_SECKEY; + goto leave; + } + if( DBG_CIPHER ) + log_hexdump("DEK is:", dek->key, dek->keylen ); + /* check that the algo is in the preferences and whether it has expired */ + { + PKT_public_key *pk = NULL; + KBNODE pkb = get_pubkeyblock (keyid); + + if( !pkb ) { + rc = -1; + log_error("oops: public key not found for preference check\n"); + } + else if(pkb->pkt->pkt.public_key->selfsigversion > 3 + && dek->algo != CIPHER_ALGO_3DES + && !opt.quiet + && !is_algo_in_prefs( pkb, PREFTYPE_SYM, dek->algo )) + log_info(_("WARNING: cipher algorithm %s not found in recipient" + " preferences\n"),cipher_algo_to_string(dek->algo)); + if (!rc) { + KBNODE k; + + for (k=pkb; k; k = k->next) { + if (k->pkt->pkttype == PKT_PUBLIC_KEY + || k->pkt->pkttype == PKT_PUBLIC_SUBKEY){ + u32 aki[2]; + keyid_from_pk(k->pkt->pkt.public_key, aki); + + if (aki[0]==keyid[0] && aki[1]==keyid[1]) { + pk = k->pkt->pkt.public_key; + break; + } + } + } + if (!pk) + BUG (); + if ( pk->expiredate && pk->expiredate <= make_timestamp() ) { + log_info(_("NOTE: secret key %s expired at %s\n"), + keystr(keyid), asctimestamp( pk->expiredate) ); + } + } + + if ( pk && pk->is_revoked ) { + log_info( _("NOTE: key has been revoked") ); + putc( '\n', log_stream() ); + show_revocation_reason( pk, 1 ); + } + + release_kbnode (pkb); + rc = 0; + } + + + leave: + mpi_free(plain_dek); + xfree(frame); + return rc; +} + + +/**************** + * Get the session key from the given string. + * String is supposed to be formatted as this: + * <algo-id>:<even-number-of-hex-digits> + */ +int +get_override_session_key( DEK *dek, const char *string ) +{ + const char *s; + int i; + + if ( !string ) + return G10ERR_BAD_KEY; + dek->algo = atoi(string); + if ( dek->algo < 1 ) + return G10ERR_BAD_KEY; + if ( !(s = strchr ( string, ':' )) ) + return G10ERR_BAD_KEY; + s++; + for(i=0; i < DIM(dek->key) && *s; i++, s +=2 ) { + int c = hextobyte ( s ); + if (c == -1) + return G10ERR_BAD_KEY; + dek->key[i] = c; + } + if ( *s ) + return G10ERR_BAD_KEY; + dek->keylen = i; + return 0; +} + diff --git a/g10/revoke.c b/g10/revoke.c new file mode 100644 index 0000000..c2deefa --- /dev/null +++ b/g10/revoke.c @@ -0,0 +1,738 @@ +/* revoke.c + * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, + * 2004 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <assert.h> +#include <ctype.h> + +#include "options.h" +#include "packet.h" +#include "errors.h" +#include "keydb.h" +#include "memory.h" +#include "util.h" +#include "main.h" +#include "ttyio.h" +#include "status.h" +#include "i18n.h" + + +struct revocation_reason_info { + int code; + char *desc; +}; + + +int +revocation_reason_build_cb( PKT_signature *sig, void *opaque ) +{ + struct revocation_reason_info *reason = opaque; + char *ud = NULL; + byte *buffer; + size_t buflen = 1; + + if(!reason) + return 0; + + if( reason->desc ) { + ud = native_to_utf8( reason->desc ); + buflen += strlen(ud); + } + buffer = xmalloc( buflen ); + *buffer = reason->code; + if( ud ) { + memcpy(buffer+1, ud, strlen(ud) ); + xfree( ud ); + } + + build_sig_subpkt( sig, SIGSUBPKT_REVOC_REASON, buffer, buflen ); + xfree( buffer ); + return 0; +} + +/* Outputs a minimal pk (as defined by 2440) from a keyblock. A + minimal pk consists of the public key packet and a user ID. We try + and pick a user ID that has a uid signature, and include it if + possible. */ +static int +export_minimal_pk(IOBUF out,KBNODE keyblock, + PKT_signature *revsig,PKT_signature *revkey) +{ + KBNODE node; + PACKET pkt; + PKT_user_id *uid=NULL; + PKT_signature *selfsig=NULL; + u32 keyid[2]; + int rc; + + node=find_kbnode(keyblock,PKT_PUBLIC_KEY); + if(!node) + { + log_error("key incomplete\n"); + return G10ERR_GENERAL; + } + + keyid_from_pk(node->pkt->pkt.public_key,keyid); + + pkt=*node->pkt; + rc=build_packet(out,&pkt); + if(rc) + { + log_error(_("build_packet failed: %s\n"), g10_errstr(rc) ); + return rc; + } + + init_packet(&pkt); + pkt.pkttype=PKT_SIGNATURE; + + /* the revocation itself, if any. 2440 likes this to come first. */ + if(revsig) + { + pkt.pkt.signature=revsig; + rc=build_packet(out,&pkt); + if(rc) + { + log_error(_("build_packet failed: %s\n"), g10_errstr(rc) ); + return rc; + } + } + + /* If a revkey in a 1F sig is present, include it too */ + if(revkey) + { + pkt.pkt.signature=revkey; + rc=build_packet(out,&pkt); + if(rc) + { + log_error(_("build_packet failed: %s\n"), g10_errstr(rc) ); + return rc; + } + } + + while(!selfsig) + { + KBNODE signode; + + node=find_next_kbnode(node,PKT_USER_ID); + if(!node) + { + /* We're out of user IDs - none were self-signed. */ + if(uid) + break; + else + { + log_error(_("key %s has no user IDs\n"),keystr(keyid)); + return G10ERR_GENERAL; + } + } + + if(node->pkt->pkt.user_id->attrib_data) + continue; + + uid=node->pkt->pkt.user_id; + signode=node; + + while((signode=find_next_kbnode(signode,PKT_SIGNATURE))) + { + if(keyid[0]==signode->pkt->pkt.signature->keyid[0] && + keyid[1]==signode->pkt->pkt.signature->keyid[1] && + IS_UID_SIG(signode->pkt->pkt.signature)) + { + selfsig=signode->pkt->pkt.signature; + break; + } + } + } + + pkt.pkttype=PKT_USER_ID; + pkt.pkt.user_id=uid; + + rc=build_packet(out,&pkt); + if(rc) + { + log_error(_("build_packet failed: %s\n"), g10_errstr(rc) ); + return rc; + } + + if(selfsig) + { + pkt.pkttype=PKT_SIGNATURE; + pkt.pkt.signature=selfsig; + + rc=build_packet(out,&pkt); + if(rc) + { + log_error(_("build_packet failed: %s\n"), g10_errstr(rc) ); + return rc; + } + } + + return 0; +} + +/**************** + * Generate a revocation certificate for UNAME via a designated revoker + */ +int +gen_desig_revoke( const char *uname, STRLIST locusr ) +{ + int rc = 0; + armor_filter_context_t afx; + PKT_public_key *pk = NULL; + PKT_secret_key *sk = NULL; + PKT_signature *sig = NULL; + IOBUF out = NULL; + struct revocation_reason_info *reason = NULL; + KEYDB_HANDLE kdbhd; + KEYDB_SEARCH_DESC desc; + KBNODE keyblock=NULL,node; + u32 keyid[2]; + int i,any=0; + SK_LIST sk_list=NULL; + + if( opt.batch ) + { + log_error(_("can't do this in batch mode\n")); + return G10ERR_GENERAL; + } + + memset( &afx, 0, sizeof afx); + + kdbhd = keydb_new (0); + classify_user_id (uname, &desc); + rc = desc.mode? keydb_search (kdbhd, &desc, 1) : G10ERR_INV_USER_ID; + if (rc) { + log_error (_("key \"%s\" not found: %s\n"),uname, g10_errstr (rc)); + goto leave; + } + + rc = keydb_get_keyblock (kdbhd, &keyblock ); + if( rc ) { + log_error (_("error reading keyblock: %s\n"), g10_errstr(rc) ); + goto leave; + } + + /* To parse the revkeys */ + merge_keys_and_selfsig(keyblock); + + /* get the key from the keyblock */ + node = find_kbnode( keyblock, PKT_PUBLIC_KEY ); + if( !node ) + BUG (); + + pk=node->pkt->pkt.public_key; + + keyid_from_pk(pk,keyid); + + if(locusr) + { + rc=build_sk_list(locusr,&sk_list,0,PUBKEY_USAGE_CERT); + if(rc) + goto leave; + } + + /* Are we a designated revoker for this key? */ + + if(!pk->revkey && pk->numrevkeys) + BUG(); + + for(i=0;i<pk->numrevkeys;i++) + { + SK_LIST list; + + if(sk) + free_secret_key(sk); + + if(sk_list) + { + for(list=sk_list;list;list=list->next) + { + byte fpr[MAX_FINGERPRINT_LEN]; + size_t fprlen; + + fingerprint_from_sk(list->sk,fpr,&fprlen); + + /* Don't get involved with keys that don't have 160 + bit fingerprints */ + if(fprlen!=20) + continue; + + if(memcmp(fpr,pk->revkey[i].fpr,20)==0) + break; + } + + if(list) + sk=copy_secret_key(NULL,list->sk); + else + continue; + } + else + { + sk=xmalloc_secure_clear(sizeof(*sk)); + rc=get_seckey_byfprint(sk,pk->revkey[i].fpr,MAX_FINGERPRINT_LEN); + } + + /* We have the revocation key */ + if(!rc) + { + PKT_signature *revkey = NULL; + + any = 1; + + print_pubkey_info (NULL, pk); + tty_printf ("\n"); + + tty_printf (_("To be revoked by:\n")); + print_seckey_info (sk); + + if(pk->revkey[i].class&0x40) + tty_printf(_("(This is a sensitive revocation key)\n")); + tty_printf("\n"); + + if( !cpr_get_answer_is_yes("gen_desig_revoke.okay", + _("Create a designated revocation certificate for this key? (y/N) "))) + continue; + + /* get the reason for the revocation (this is always v4) */ + reason = ask_revocation_reason( 1, 0, 1 ); + if( !reason ) + continue; + + rc = check_secret_key( sk, 0 ); + if( rc ) + continue; + + if( !opt.armor ) + tty_printf(_("ASCII armored output forced.\n")); + + if( (rc = open_outfile( NULL, 0, &out )) ) + goto leave; + + afx.what = 1; + afx.hdrlines = "Comment: A designated revocation certificate" + " should follow\n"; + iobuf_push_filter( out, armor_filter, &afx ); + + /* create it */ + rc = make_keysig_packet( &sig, pk, NULL, NULL, sk, 0x20, 0, + 0, 0, 0, + revocation_reason_build_cb, reason ); + if( rc ) { + log_error(_("make_keysig_packet failed: %s\n"), g10_errstr(rc)); + goto leave; + } + + /* Spit out a minimal pk as well, since otherwise there is + no way to know which key to attach this revocation to. + Also include the direct key signature that contains + this revocation key. We're allowed to include + sensitive revocation keys along with a revocation, as + this may be the only time the recipient has seen it. + Note that this means that if we have multiple different + sensitive revocation keys in a given direct key + signature, we're going to include them all here. This + is annoying, but the good outweighs the bad, since + without including this a sensitive revoker can't really + do their job. People should not include multiple + sensitive revocation keys in one signature: 2440 says + "Note that it may be appropriate to isolate this + subpacket within a separate signature so that it is not + combined with other subpackets that need to be + exported." -dms */ + + while(!revkey) + { + KBNODE signode; + + signode=find_next_kbnode(node,PKT_SIGNATURE); + if(!signode) + break; + + node=signode; + + if(keyid[0]==signode->pkt->pkt.signature->keyid[0] && + keyid[1]==signode->pkt->pkt.signature->keyid[1] && + IS_KEY_SIG(signode->pkt->pkt.signature)) + { + int j; + + for(j=0;j<signode->pkt->pkt.signature->numrevkeys;j++) + { + if(pk->revkey[i].class== + signode->pkt->pkt.signature->revkey[j]->class && + pk->revkey[i].algid== + signode->pkt->pkt.signature->revkey[j]->algid && + memcmp(pk->revkey[i].fpr, + signode->pkt->pkt.signature->revkey[j]->fpr, + MAX_FINGERPRINT_LEN)==0) + { + revkey=signode->pkt->pkt.signature; + break; + } + } + } + } + + if(!revkey) + BUG(); + + rc=export_minimal_pk(out,keyblock,sig,revkey); + if(rc) + goto leave; + + /* and issue a usage notice */ + tty_printf(_("Revocation certificate created.\n")); + break; + } + } + + if(!any) + log_error(_("no revocation keys found for \"%s\"\n"),uname); + + leave: + if( pk ) + free_public_key( pk ); + if( sk ) + free_secret_key( sk ); + if( sig ) + free_seckey_enc( sig ); + + release_sk_list(sk_list); + + if( rc ) + iobuf_cancel(out); + else + iobuf_close(out); + release_revocation_reason_info( reason ); + return rc; +} + + +/**************** + * Generate a revocation certificate for UNAME + */ +int +gen_revoke( const char *uname ) +{ + int rc = 0; + armor_filter_context_t afx; + PACKET pkt; + PKT_secret_key *sk; /* used as pointer into a kbnode */ + PKT_public_key *pk = NULL; + PKT_signature *sig = NULL; + u32 sk_keyid[2]; + IOBUF out = NULL; + KBNODE keyblock = NULL, pub_keyblock = NULL; + KBNODE node; + KEYDB_HANDLE kdbhd; + struct revocation_reason_info *reason = NULL; + KEYDB_SEARCH_DESC desc; + + if( opt.batch ) + { + log_error(_("can't do this in batch mode\n")); + return G10ERR_GENERAL; + } + + memset( &afx, 0, sizeof afx); + init_packet( &pkt ); + + /* search the userid: + * We don't want the whole getkey stuff here but the entire keyblock + */ + kdbhd = keydb_new (1); + classify_user_id (uname, &desc); + rc = desc.mode? keydb_search (kdbhd, &desc, 1) : G10ERR_INV_USER_ID; + if (rc) + { + log_error (_("secret key \"%s\" not found: %s\n"), + uname, g10_errstr (rc)); + goto leave; + } + + rc = keydb_get_keyblock (kdbhd, &keyblock ); + if( rc ) { + log_error (_("error reading keyblock: %s\n"), g10_errstr(rc) ); + goto leave; + } + + /* get the keyid from the keyblock */ + node = find_kbnode( keyblock, PKT_SECRET_KEY ); + if( !node ) + BUG (); + + /* fixme: should make a function out of this stuff, + * it's used all over the source */ + sk = node->pkt->pkt.secret_key; + keyid_from_sk( sk, sk_keyid ); + print_seckey_info (sk); + + pk = xmalloc_clear( sizeof *pk ); + + /* FIXME: We should get the public key direct from the secret one */ + + pub_keyblock=get_pubkeyblock(sk_keyid); + if(!pub_keyblock) + { + log_error(_("no corresponding public key: %s\n"), g10_errstr(rc) ); + goto leave; + } + + node=find_kbnode(pub_keyblock,PKT_PUBLIC_KEY); + if(!node) + BUG(); + + pk=node->pkt->pkt.public_key; + + if( cmp_public_secret_key( pk, sk ) ) { + log_error(_("public key does not match secret key!\n") ); + rc = G10ERR_GENERAL; + goto leave; + } + + tty_printf("\n"); + if( !cpr_get_answer_is_yes("gen_revoke.okay", + _("Create a revocation certificate for this key? (y/N) ")) ) + { + rc = 0; + goto leave; + } + + if(sk->version>=4 || opt.force_v4_certs) { + /* get the reason for the revocation */ + reason = ask_revocation_reason( 1, 0, 1 ); + if( !reason ) { /* user decided to cancel */ + rc = 0; + goto leave; + } + } + + switch( is_secret_key_protected( sk ) ) { + case -1: + log_error(_("unknown protection algorithm\n")); + rc = G10ERR_PUBKEY_ALGO; + break; + case -3: + tty_printf (_("Secret parts of primary key are not available.\n")); + rc = G10ERR_NO_SECKEY; + break; + case 0: + tty_printf(_("NOTE: This key is not protected!\n")); + break; + default: + rc = check_secret_key( sk, 0 ); + break; + } + if( rc ) + goto leave; + + + if( !opt.armor ) + tty_printf(_("ASCII armored output forced.\n")); + + if( (rc = open_outfile( NULL, 0, &out )) ) + goto leave; + + afx.what = 1; + afx.hdrlines = "Comment: A revocation certificate should follow\n"; + iobuf_push_filter( out, armor_filter, &afx ); + + /* create it */ + rc = make_keysig_packet( &sig, pk, NULL, NULL, sk, 0x20, 0, + opt.force_v4_certs?4:0, 0, 0, + revocation_reason_build_cb, reason ); + if( rc ) { + log_error(_("make_keysig_packet failed: %s\n"), g10_errstr(rc)); + goto leave; + } + + if(PGP2 || PGP6 || PGP7 || PGP8) + { + /* Use a minimal pk for PGPx mode, since PGP can't import bare + revocation certificates. */ + rc=export_minimal_pk(out,pub_keyblock,sig,NULL); + if(rc) + goto leave; + } + else + { + init_packet( &pkt ); + pkt.pkttype = PKT_SIGNATURE; + pkt.pkt.signature = sig; + + rc = build_packet( out, &pkt ); + if( rc ) { + log_error(_("build_packet failed: %s\n"), g10_errstr(rc) ); + goto leave; + } + } + + /* and issue a usage notice */ + tty_printf(_("Revocation certificate created.\n\n" +"Please move it to a medium which you can hide away; if Mallory gets\n" +"access to this certificate he can use it to make your key unusable.\n" +"It is smart to print this certificate and store it away, just in case\n" +"your media become unreadable. But have some caution: The print system of\n" +"your machine might store the data and make it available to others!\n")); + + leave: + if( sig ) + free_seckey_enc( sig ); + release_kbnode( keyblock ); + release_kbnode( pub_keyblock ); + keydb_release (kdbhd); + if( rc ) + iobuf_cancel(out); + else + iobuf_close(out); + release_revocation_reason_info( reason ); + return rc; +} + + + +struct revocation_reason_info * +ask_revocation_reason( int key_rev, int cert_rev, int hint ) +{ + int code=-1; + char *description = NULL; + struct revocation_reason_info *reason; + const char *text_0 = _("No reason specified"); + const char *text_1 = _("Key has been compromised"); + const char *text_2 = _("Key is superseded"); + const char *text_3 = _("Key is no longer used"); + const char *text_4 = _("User ID is no longer valid"); + const char *code_text = NULL; + + do { + code=-1; + xfree(description); + description = NULL; + + tty_printf(_("Please select the reason for the revocation:\n")); + tty_printf( " 0 = %s\n", text_0 ); + if( key_rev ) + tty_printf(" 1 = %s\n", text_1 ); + if( key_rev ) + tty_printf(" 2 = %s\n", text_2 ); + if( key_rev ) + tty_printf(" 3 = %s\n", text_3 ); + if( cert_rev ) + tty_printf(" 4 = %s\n", text_4 ); + tty_printf( " Q = %s\n", _("Cancel") ); + if( hint ) + tty_printf(_("(Probably you want to select %d here)\n"), hint ); + + while(code==-1) { + int n; + char *answer = cpr_get("ask_revocation_reason.code", + _("Your decision? ")); + trim_spaces( answer ); + cpr_kill_prompt(); + if( *answer == 'q' || *answer == 'Q') + return NULL; /* cancel */ + if( hint && !*answer ) + n = hint; + else if(!digitp( answer ) ) + n = -1; + else + n = atoi(answer); + xfree(answer); + if( n == 0 ) { + code = 0x00; /* no particular reason */ + code_text = text_0; + } + else if( key_rev && n == 1 ) { + code = 0x02; /* key has been compromised */ + code_text = text_1; + } + else if( key_rev && n == 2 ) { + code = 0x01; /* key is superseded */ + code_text = text_2; + } + else if( key_rev && n == 3 ) { + code = 0x03; /* key is no longer used */ + code_text = text_3; + } + else if( cert_rev && n == 4 ) { + code = 0x20; /* uid is no longer valid */ + code_text = text_4; + } + else + tty_printf(_("Invalid selection.\n")); + } + + tty_printf(_("Enter an optional description; " + "end it with an empty line:\n") ); + for(;;) { + char *answer = cpr_get("ask_revocation_reason.text", "> " ); + trim_trailing_ws( answer, strlen(answer) ); + cpr_kill_prompt(); + if( !*answer ) { + xfree(answer); + break; + } + + { + char *p = make_printable_string( answer, strlen(answer), 0 ); + xfree(answer); + answer = p; + } + + if( !description ) + description = xstrdup(answer); + else { + char *p = xmalloc( strlen(description) + strlen(answer) + 2 ); + strcpy(stpcpy(stpcpy( p, description),"\n"),answer); + xfree(description); + description = p; + } + xfree(answer); + } + + tty_printf(_("Reason for revocation: %s\n"), code_text ); + if( !description ) + tty_printf(_("(No description given)\n") ); + else + tty_printf("%s\n", description ); + + } while( !cpr_get_answer_is_yes("ask_revocation_reason.okay", + _("Is this okay? (y/N) ")) ); + + reason = xmalloc( sizeof *reason ); + reason->code = code; + reason->desc = description; + return reason; +} + +void +release_revocation_reason_info( struct revocation_reason_info *reason ) +{ + if( reason ) { + xfree( reason->desc ); + xfree( reason ); + } +} diff --git a/g10/seckey-cert.c b/g10/seckey-cert.c new file mode 100644 index 0000000..79cf22a --- /dev/null +++ b/g10/seckey-cert.c @@ -0,0 +1,427 @@ +/* seckey-cert.c - secret key certificate packet handling + * Copyright (C) 1998, 1999, 2000, 2001, 2002 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include "util.h" +#include "memory.h" +#include "packet.h" +#include "mpi.h" +#include "keydb.h" +#include "cipher.h" +#include "main.h" +#include "options.h" +#include "i18n.h" +#include "status.h" + + +static int +do_check( PKT_secret_key *sk, const char *tryagain_text, int mode, + int *canceled ) +{ + byte *buffer; + u16 csum=0; + int i, res; + unsigned nbytes; + + if( sk->is_protected ) { /* remove the protection */ + DEK *dek = NULL; + u32 keyid[4]; /* 4! because we need two of them */ + CIPHER_HANDLE cipher_hd=NULL; + PKT_secret_key *save_sk; + + if( sk->protect.s2k.mode == 1001 ) { + log_info(_("secret key parts are not available\n")); + return G10ERR_GENERAL; + } + if( sk->protect.algo == CIPHER_ALGO_NONE ) + BUG(); + if( check_cipher_algo( sk->protect.algo ) ) { + log_info(_("protection algorithm %d%s is not supported\n"), + sk->protect.algo,sk->protect.algo==1?" (IDEA)":"" ); + if (sk->protect.algo==CIPHER_ALGO_IDEA) + { + write_status (STATUS_RSA_OR_IDEA); + idea_cipher_warn (0); + } + return G10ERR_CIPHER_ALGO; + } + if(check_digest_algo(sk->protect.s2k.hash_algo)) + { + log_info(_("protection digest %d is not supported\n"), + sk->protect.s2k.hash_algo); + return G10ERR_DIGEST_ALGO; + } + keyid_from_sk( sk, keyid ); + keyid[2] = keyid[3] = 0; + if( !sk->is_primary ) { + keyid[2] = sk->main_keyid[0]; + keyid[3] = sk->main_keyid[1]; + } + dek = passphrase_to_dek( keyid, sk->pubkey_algo, sk->protect.algo, + &sk->protect.s2k, mode, + tryagain_text, canceled ); + if (!dek && canceled && *canceled) + return G10ERR_GENERAL; + + cipher_hd = cipher_open( sk->protect.algo, + CIPHER_MODE_AUTO_CFB, 1); + cipher_setkey( cipher_hd, dek->key, dek->keylen ); + xfree(dek); + save_sk = copy_secret_key( NULL, sk ); + cipher_setiv( cipher_hd, sk->protect.iv, sk->protect.ivlen ); + csum = 0; + if( sk->version >= 4 ) { + unsigned int ndata; + byte *p, *data; + u16 csumc = 0; + + i = pubkey_get_npkey(sk->pubkey_algo); + assert( mpi_is_opaque( sk->skey[i] ) ); + p = mpi_get_opaque( sk->skey[i], &ndata ); + if ( ndata > 1 ) + csumc = p[ndata-2] << 8 | p[ndata-1]; + data = xmalloc_secure( ndata ); + cipher_decrypt( cipher_hd, data, p, ndata ); + mpi_free( sk->skey[i] ); sk->skey[i] = NULL ; + p = data; + if (sk->protect.sha1chk) { + /* This is the new SHA1 checksum method to detect + tampering with the key as used by the Klima/Rosa + attack */ + sk->csum = 0; + csum = 1; + if( ndata < 20 ) + log_error("not enough bytes for SHA-1 checksum\n"); + else { + MD_HANDLE h = md_open (DIGEST_ALGO_SHA1, 1); + if (!h) + BUG(); /* algo not available */ + md_write (h, data, ndata - 20); + md_final (h); + if (!memcmp (md_read (h, DIGEST_ALGO_SHA1), + data + ndata - 20, 20) ) { + /* digest does match. We have to keep the old + style checksum in sk->csum, so that the + test used for unprotected keys does work. + This test gets used when we are adding new + keys. */ + sk->csum = csum = checksum (data, ndata-20); + } + md_close (h); + } + } + else { + if( ndata < 2 ) { + log_error("not enough bytes for checksum\n"); + sk->csum = 0; + csum = 1; + } + else { + csum = checksum( data, ndata-2); + sk->csum = data[ndata-2] << 8 | data[ndata-1]; + if ( sk->csum != csum ) { + /* This is a PGP 7.0.0 workaround */ + sk->csum = csumc; /* take the encrypted one */ + } + } + } + + /* Must check it here otherwise the mpi_read_xx would fail + because the length may have an arbitrary value */ + if( sk->csum == csum ) { + for( ; i < pubkey_get_nskey(sk->pubkey_algo); i++ ) { + nbytes = ndata; + sk->skey[i] = mpi_read_from_buffer(p, &nbytes, 1 ); + if (!sk->skey[i]) + { + /* Checksum was okay, but not correctly + decrypted. */ + sk->csum = 0; + csum = 1; + break; + } + ndata -= nbytes; + p += nbytes; + } + /* Note: at this point ndata should be 2 for a simple + checksum or 20 for the sha1 digest */ + } + xfree(data); + } + else { + for(i=pubkey_get_npkey(sk->pubkey_algo); + i < pubkey_get_nskey(sk->pubkey_algo); i++ ) { + byte *p; + unsigned int ndata; + + assert (mpi_is_opaque (sk->skey[i])); + p = mpi_get_opaque (sk->skey[i], &ndata); + assert (ndata >= 2); + assert (ndata == ((p[0] << 8 | p[1]) + 7)/8 + 2); + buffer = xmalloc_secure (ndata); + cipher_sync (cipher_hd); + buffer[0] = p[0]; + buffer[1] = p[1]; + cipher_decrypt (cipher_hd, buffer+2, p+2, ndata-2); + csum += checksum (buffer, ndata); + mpi_free (sk->skey[i]); + sk->skey[i] = mpi_read_from_buffer (buffer, &ndata, 1); + xfree (buffer); + if (!sk->skey[i]) + { + /* Checksum was okay, but not correctly + decrypted. */ + sk->csum = 0; + csum = 1; + break; + } +/* csum += checksum_mpi (sk->skey[i]); */ + } + } + cipher_close( cipher_hd ); + /* now let's see whether we have used the right passphrase */ + if( csum != sk->csum ) { + copy_secret_key( sk, save_sk ); + passphrase_clear_cache ( keyid, NULL, sk->pubkey_algo ); + free_secret_key( save_sk ); + return G10ERR_BAD_PASS; + } + /* the checksum may fail, so we also check the key itself */ + res = pubkey_check_secret_key( sk->pubkey_algo, sk->skey ); + if( res ) { + copy_secret_key( sk, save_sk ); + passphrase_clear_cache ( keyid, NULL, sk->pubkey_algo ); + free_secret_key( save_sk ); + return G10ERR_BAD_PASS; + } + free_secret_key( save_sk ); + sk->is_protected = 0; + } + else { /* not protected, assume it is okay if the checksum is okay */ + csum = 0; + for(i=pubkey_get_npkey(sk->pubkey_algo); + i < pubkey_get_nskey(sk->pubkey_algo); i++ ) { + csum += checksum_mpi( sk->skey[i] ); + } + if( csum != sk->csum ) + return G10ERR_CHECKSUM; + } + + return 0; +} + + + +/**************** + * Check the secret key + * Ask up to 3 (or n) times for a correct passphrase + * If n is negative, disable the key info prompt and make n=abs(n) + */ +int +check_secret_key( PKT_secret_key *sk, int n ) +{ + int rc = G10ERR_BAD_PASS; + int i,mode; + + if (sk && sk->is_protected && sk->protect.s2k.mode == 1002) + return 0; /* Let the card support stuff handle this. */ + + if(n<0) + { + n=abs(n); + mode=1; + } + else + mode=0; + + if( n < 1 ) + n = (opt.batch && !opt.use_agent)? 1 : 3; /* use the default value */ + + for(i=0; i < n && rc == G10ERR_BAD_PASS; i++ ) { + int canceled = 0; + const char *tryagain = NULL; + if (i) { + tryagain = N_("Invalid passphrase; please try again"); + log_info (_("%s ...\n"), _(tryagain)); + } + rc = do_check( sk, tryagain, mode, &canceled ); + if( rc == G10ERR_BAD_PASS && is_status_enabled() ) { + u32 kid[2]; + char buf[50]; + + keyid_from_sk( sk, kid ); + sprintf(buf, "%08lX%08lX", (ulong)kid[0], (ulong)kid[1]); + write_status_text( STATUS_BAD_PASSPHRASE, buf ); + } + if( have_static_passphrase() || canceled) + break; + } + + if( !rc ) + write_status( STATUS_GOOD_PASSPHRASE ); + + return rc; +} + +/**************** + * check whether the secret key is protected. + * Returns: 0 not protected, -1 on error or the protection algorithm + * -2 indicates a card stub. + * -3 indicates a not-online stub. + */ +int +is_secret_key_protected( PKT_secret_key *sk ) +{ + return sk->is_protected? + sk->protect.s2k.mode == 1002? -2 : + sk->protect.s2k.mode == 1001? -3 : sk->protect.algo : 0; +} + + + +/**************** + * Protect the secret key with the passphrase from DEK + */ +int +protect_secret_key( PKT_secret_key *sk, DEK *dek ) +{ + int i,j, rc = 0; + byte *buffer; + unsigned nbytes; + u16 csum; + + if( !dek ) + return 0; + + if( !sk->is_protected ) { /* okay, apply the protection */ + CIPHER_HANDLE cipher_hd=NULL; + + if( check_cipher_algo( sk->protect.algo ) ) + rc = G10ERR_CIPHER_ALGO; /* unsupport protection algorithm */ + else { + print_cipher_algo_note( sk->protect.algo ); + cipher_hd = cipher_open( sk->protect.algo, + CIPHER_MODE_AUTO_CFB, 1 ); + if( cipher_setkey( cipher_hd, dek->key, dek->keylen ) ) + log_info(_("WARNING: Weak key detected" + " - please change passphrase again.\n")); + sk->protect.ivlen = cipher_get_blocksize( sk->protect.algo ); + assert( sk->protect.ivlen <= DIM(sk->protect.iv) ); + if( sk->protect.ivlen != 8 && sk->protect.ivlen != 16 ) + BUG(); /* yes, we are very careful */ + randomize_buffer(sk->protect.iv, sk->protect.ivlen, 1); + cipher_setiv( cipher_hd, sk->protect.iv, sk->protect.ivlen ); + if( sk->version >= 4 ) { + byte *bufarr[PUBKEY_MAX_NSKEY]; + unsigned narr[PUBKEY_MAX_NSKEY]; + unsigned nbits[PUBKEY_MAX_NSKEY]; + int ndata=0; + byte *p, *data; + + for(j=0, i = pubkey_get_npkey(sk->pubkey_algo); + i < pubkey_get_nskey(sk->pubkey_algo); i++, j++ ) { + assert( !mpi_is_opaque( sk->skey[i] ) ); + bufarr[j] = mpi_get_buffer( sk->skey[i], &narr[j], NULL ); + nbits[j] = mpi_get_nbits( sk->skey[i] ); + ndata += narr[j] + 2; + } + for( ; j < PUBKEY_MAX_NSKEY; j++ ) + bufarr[j] = NULL; + ndata += opt.simple_sk_checksum? 2 : 20; /* for checksum */ + + data = xmalloc_secure( ndata ); + p = data; + for(j=0; j < PUBKEY_MAX_NSKEY && bufarr[j]; j++ ) { + p[0] = nbits[j] >> 8 ; + p[1] = nbits[j]; + p += 2; + memcpy(p, bufarr[j], narr[j] ); + p += narr[j]; + xfree(bufarr[j]); + } + + if (opt.simple_sk_checksum) { + log_info (_("generating the deprecated 16-bit checksum" + " for secret key protection\n")); + csum = checksum( data, ndata-2); + sk->csum = csum; + *p++ = csum >> 8; + *p++ = csum; + sk->protect.sha1chk = 0; + } + else { + MD_HANDLE h = md_open (DIGEST_ALGO_SHA1, 1); + if (!h) + BUG(); /* algo not available */ + md_write (h, data, ndata - 20); + md_final (h); + memcpy (p, md_read (h, DIGEST_ALGO_SHA1), 20); + p += 20; + md_close (h); + sk->csum = csum = 0; + sk->protect.sha1chk = 1; + } + assert( p == data+ndata ); + + cipher_encrypt( cipher_hd, data, data, ndata ); + for(i = pubkey_get_npkey(sk->pubkey_algo); + i < pubkey_get_nskey(sk->pubkey_algo); i++ ) { + mpi_free( sk->skey[i] ); + sk->skey[i] = NULL; + } + i = pubkey_get_npkey(sk->pubkey_algo); + sk->skey[i] = mpi_set_opaque(NULL, data, ndata ); + } + else { + csum = 0; + for(i=pubkey_get_npkey(sk->pubkey_algo); + i < pubkey_get_nskey(sk->pubkey_algo); i++ ) { + byte *data; + unsigned int nbits; + + csum += checksum_mpi (sk->skey[i]); + buffer = mpi_get_buffer( sk->skey[i], &nbytes, NULL ); + cipher_sync (cipher_hd); + assert ( !mpi_is_opaque (sk->skey[i]) ); + data = xmalloc (nbytes+2); + nbits = mpi_get_nbits (sk->skey[i]); + assert (nbytes == (nbits + 7)/8); + data[0] = nbits >> 8; + data[1] = nbits; + cipher_encrypt (cipher_hd, data+2, buffer, nbytes); + xfree( buffer ); + + mpi_free (sk->skey[i]); + sk->skey[i] = mpi_set_opaque (NULL, data, nbytes+2); + } + sk->csum = csum; + } + sk->is_protected = 1; + cipher_close( cipher_hd ); + } + } + return rc; +} + diff --git a/g10/seskey.c b/g10/seskey.c new file mode 100644 index 0000000..dd4dc0b --- /dev/null +++ b/g10/seskey.c @@ -0,0 +1,271 @@ +/* seskey.c - make sesssion keys etc. + * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, + * 2006 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include "util.h" +#include "cipher.h" +#include "mpi.h" +#include "main.h" +#include "i18n.h" + + +/**************** + * Make a session key and put it into DEK + */ +void +make_session_key( DEK *dek ) +{ + CIPHER_HANDLE chd; + int i, rc; + + dek->keylen = cipher_get_keylen( dek->algo ) / 8; + + chd = cipher_open( dek->algo, CIPHER_MODE_AUTO_CFB, 1 ); + randomize_buffer( dek->key, dek->keylen, 1 ); + for(i=0; i < 16; i++ ) { + rc = cipher_setkey( chd, dek->key, dek->keylen ); + if( !rc ) { + cipher_close( chd ); + return; + } + log_info(_("weak key created - retrying\n") ); + /* Renew the session key until we get a non-weak key. */ + randomize_buffer( dek->key, dek->keylen, 1 ); + } + log_fatal(_( + "cannot avoid weak key for symmetric cipher; tried %d times!\n"), + i); +} + + +/**************** + * Encode the session key. NBITS is the number of bits which should be used + * for packing the session key. + * returns: A mpi with the session key (caller must free) + */ +MPI +encode_session_key( DEK *dek, unsigned nbits ) +{ + int nframe = (nbits+7) / 8; + byte *p; + byte *frame; + int i,n; + u16 csum; + MPI a; + + /* the current limitation is that we can only use a session key + * whose length is a multiple of BITS_PER_MPI_LIMB + * I think we can live with that. + */ + if( dek->keylen + 7 > nframe || !nframe ) + log_bug("can't encode a %d bit key in a %d bits frame\n", + dek->keylen*8, nbits ); + + /* We encode the session key in this way: + * + * 0 2 RND(n bytes) 0 A DEK(k bytes) CSUM(2 bytes) + * + * (But how can we store the leading 0 - the external representaion + * of MPIs doesn't allow leading zeroes =:-) + * + * RND are non-zero random bytes. + * A is the cipher algorithm + * DEK is the encryption key (session key) length k depends on the + * cipher algorithm (20 is used with blowfish160). + * CSUM is the 16 bit checksum over the DEK + */ + csum = 0; + for( p = dek->key, i=0; i < dek->keylen; i++ ) + csum += *p++; + + frame = xmalloc_secure( nframe ); + n = 0; + frame[n++] = 0; + frame[n++] = 2; + i = nframe - 6 - dek->keylen; + assert( i > 0 ); + p = get_random_bits( i*8, 1, 1 ); + /* replace zero bytes by new values */ + for(;;) { + int j, k; + byte *pp; + + /* count the zero bytes */ + for(j=k=0; j < i; j++ ) + if( !p[j] ) + k++; + if( !k ) + break; /* okay: no zero bytes */ + k += k/128 + 3; /* better get some more */ + pp = get_random_bits( k*8, 1, 1); + for(j=0; j < i && k ;) { + if( !p[j] ) + p[j] = pp[--k]; + if (p[j]) + j++; + } + xfree(pp); + } + memcpy( frame+n, p, i ); + xfree(p); + n += i; + frame[n++] = 0; + frame[n++] = dek->algo; + memcpy( frame+n, dek->key, dek->keylen ); n += dek->keylen; + frame[n++] = csum >>8; + frame[n++] = csum; + assert( n == nframe ); + a = mpi_alloc_secure( (nframe+BYTES_PER_MPI_LIMB-1) / BYTES_PER_MPI_LIMB ); + mpi_set_buffer( a, frame, nframe, 0 ); + xfree(frame); + return a; +} + + +static MPI +do_encode_md( MD_HANDLE md, int algo, size_t len, unsigned nbits, + const byte *asn, size_t asnlen ) +{ + int nframe = (nbits+7) / 8; + byte *frame; + int i,n; + MPI a; + + if( len + asnlen + 4 > nframe ) + log_bug("can't encode a %d bit MD into a %d bits frame\n", + (int)(len*8), (int)nbits); + + /* We encode the MD in this way: + * + * 0 1 PAD(n bytes) 0 ASN(asnlen bytes) MD(len bytes) + * + * PAD consists of FF bytes. + */ + frame = md_is_secure(md)? xmalloc_secure( nframe ) : xmalloc( nframe ); + n = 0; + frame[n++] = 0; + frame[n++] = 1; /* block type */ + i = nframe - len - asnlen -3 ; + assert( i > 1 ); + memset( frame+n, 0xff, i ); n += i; + frame[n++] = 0; + memcpy( frame+n, asn, asnlen ); n += asnlen; + memcpy( frame+n, md_read(md, algo), len ); n += len; + assert( n == nframe ); + a = md_is_secure(md)? + mpi_alloc_secure( (nframe+BYTES_PER_MPI_LIMB-1) / BYTES_PER_MPI_LIMB ) + : mpi_alloc( (nframe+BYTES_PER_MPI_LIMB-1) / BYTES_PER_MPI_LIMB ); + mpi_set_buffer( a, frame, nframe, 0 ); + xfree(frame); + + /* Note that PGP before version 2.3 encoded the MD as: + * + * 0 1 MD(16 bytes) 0 PAD(n bytes) 1 + * + * The MD is always 16 bytes here because it's always MD5. We do + * not support pre-v2.3 signatures, but I'm including this comment + * so the information is easily found in the future. + */ + + return a; +} + + +/**************** + * Encode a message digest into an MPI. + * If it's for a DSA signature, make sure that the hash is large + * enough to fill up q. If the hash is too big, take the leftmost + * bits. + */ +MPI +encode_md_value( PKT_public_key *pk, PKT_secret_key *sk, + MD_HANDLE md, int hash_algo ) +{ + MPI frame; + + assert(hash_algo); + assert(pk || sk); + + if((pk?pk->pubkey_algo:sk->pubkey_algo) == PUBKEY_ALGO_DSA) + { + /* It's a DSA signature, so find out the size of q. */ + + unsigned int qbytes=mpi_get_nbits(pk?pk->pkey[1]:sk->skey[1]); + + /* Make sure it is a multiple of 8 bits. */ + + if(qbytes%8) + { + log_error(_("DSA requires the hash length to be a" + " multiple of 8 bits\n")); + return NULL; + } + + /* Don't allow any q smaller than 160 bits. This might need a + revisit as the DSA2 design firms up, but for now, we don't + want someone to issue signatures from a key with a 16-bit q + or something like that, which would look correct but allow + trivial forgeries. Yes, I know this rules out using MD5 with + DSA. ;) */ + + if(qbytes<160) + { + log_error(_("DSA key %s uses an unsafe (%u bit) hash\n"), + pk?keystr_from_pk(pk):keystr_from_sk(sk),qbytes); + return NULL; + } + + qbytes/=8; + + /* Check if we're too short. Too long is safe as we'll + automatically left-truncate. */ + + if(md_digest_length(hash_algo) < qbytes) + { + log_error(_("DSA key %s requires a %u bit or larger hash\n"), + pk?keystr_from_pk(pk):keystr_from_sk(sk),qbytes*8); + return NULL; + } + + frame = md_is_secure(md)? mpi_alloc_secure((qbytes+BYTES_PER_MPI_LIMB-1) + / BYTES_PER_MPI_LIMB ) + : mpi_alloc((qbytes+BYTES_PER_MPI_LIMB-1) / BYTES_PER_MPI_LIMB ); + + mpi_set_buffer( frame, md_read(md, hash_algo), qbytes, 0 ); + } + else + { + const byte *asn; + size_t asnlen,mdlen; + + asn = md_asn_oid( hash_algo, &asnlen, &mdlen ); + frame = do_encode_md( md, hash_algo, mdlen, + mpi_get_nbits(pk?pk->pkey[0]:sk->skey[0]), + asn, asnlen ); + } + + return frame; +} diff --git a/g10/sig-check.c b/g10/sig-check.c new file mode 100644 index 0000000..1570334 --- /dev/null +++ b/g10/sig-check.c @@ -0,0 +1,616 @@ +/* sig-check.c - Check a signature + * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, + * 2004 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include "util.h" +#include "packet.h" +#include "memory.h" +#include "mpi.h" +#include "keydb.h" +#include "cipher.h" +#include "main.h" +#include "status.h" +#include "i18n.h" +#include "options.h" + +struct cmp_help_context_s { + PKT_signature *sig; + MD_HANDLE md; +}; + +static int do_check( PKT_public_key *pk, PKT_signature *sig, MD_HANDLE digest, + int *r_expired, int *r_revoked, PKT_public_key *ret_pk); + +/**************** + * Check the signature which is contained in SIG. + * The MD_HANDLE should be currently open, so that this function + * is able to append some data, before finalizing the digest. + */ +int +signature_check( PKT_signature *sig, MD_HANDLE digest ) +{ + return signature_check2( sig, digest, NULL, NULL, NULL, NULL ); +} + +int +signature_check2( PKT_signature *sig, MD_HANDLE digest, u32 *r_expiredate, + int *r_expired, int *r_revoked, PKT_public_key *ret_pk ) +{ + PKT_public_key *pk = xmalloc_clear( sizeof *pk ); + int rc=0; + + if( (rc=check_digest_algo(sig->digest_algo)) ) + ; /* we don't have this digest */ + else if((rc=check_pubkey_algo(sig->pubkey_algo))) + ; /* we don't have this pubkey algo */ + else if(!md_algo_present(digest,sig->digest_algo)) + { + /* Sanity check that the md has a context for the hash that the + sig is expecting. This can happen if a onepass sig header does + not match the actual sig, and also if the clearsign "Hash:" + header is missing or does not match the actual sig. */ + + log_info(_("WARNING: signature digest conflict in message\n")); + rc=G10ERR_GENERAL; + } + else if( get_pubkey( pk, sig->keyid ) ) + rc = G10ERR_NO_PUBKEY; + else if(!pk->is_valid && !pk->is_primary) + rc=G10ERR_BAD_PUBKEY; /* you cannot have a good sig from an + invalid subkey */ + else + { + if(r_expiredate) + *r_expiredate = pk->expiredate; + + rc = do_check( pk, sig, digest, r_expired, r_revoked, ret_pk ); + + /* Check the backsig. This is a 0x19 signature from the + subkey on the primary key. The idea here is that it should + not be possible for someone to "steal" subkeys and claim + them as their own. The attacker couldn't actually use the + subkey, but they could try and claim ownership of any + signaures issued by it. */ + if(rc==0 && !pk->is_primary && pk->backsig<2) + { + if(pk->backsig==0) + { + log_info(_("WARNING: signing subkey %s is not" + " cross-certified\n"),keystr_from_pk(pk)); + log_info(_("please see %s for more information\n"), + "http://www.gnupg.org/faq/subkey-cross-certify.html"); + /* --require-cross-certification makes this warning an + error. TODO: change the default to require this + after more keys have backsigs. */ + if(opt.flags.require_cross_cert) + rc=G10ERR_GENERAL; + } + else if(pk->backsig==1) + { + log_info(_("WARNING: signing subkey %s has an invalid" + " cross-certification\n"),keystr_from_pk(pk)); + rc=G10ERR_GENERAL; + } + } + } + + free_public_key( pk ); + + if( !rc && sig->sig_class < 2 && is_status_enabled() ) { + /* This signature id works best with DLP algorithms because + * they use a random parameter for every signature. Instead of + * this sig-id we could have also used the hash of the document + * and the timestamp, but the drawback of this is, that it is + * not possible to sign more than one identical document within + * one second. Some remote batch processing applications might + * like this feature here */ + MD_HANDLE md; + u32 a = sig->timestamp; + int i, nsig = pubkey_get_nsig( sig->pubkey_algo ); + byte *p, *buffer; + + md = md_open( DIGEST_ALGO_RMD160, 0); + md_putc( digest, sig->pubkey_algo ); + md_putc( digest, sig->digest_algo ); + md_putc( digest, (a >> 24) & 0xff ); + md_putc( digest, (a >> 16) & 0xff ); + md_putc( digest, (a >> 8) & 0xff ); + md_putc( digest, a & 0xff ); + for(i=0; i < nsig; i++ ) { + unsigned n = mpi_get_nbits( sig->data[i]); + + md_putc( md, n>>8); + md_putc( md, n ); + p = mpi_get_buffer( sig->data[i], &n, NULL ); + md_write( md, p, n ); + xfree(p); + } + md_final( md ); + p = make_radix64_string( md_read( md, 0 ), 20 ); + buffer = xmalloc( strlen(p) + 60 ); + sprintf( buffer, "%s %s %lu", + p, strtimestamp( sig->timestamp ), (ulong)sig->timestamp ); + write_status_text( STATUS_SIG_ID, buffer ); + xfree(buffer); + xfree(p); + md_close(md); + } + + return rc; +} + + +static int +do_check_messages( PKT_public_key *pk, PKT_signature *sig, + int *r_expired, int *r_revoked ) +{ + u32 cur_time; + + if(r_expired) + *r_expired = 0; + if(r_revoked) + *r_revoked = 0; + + if( pk->timestamp > sig->timestamp ) + { + ulong d = pk->timestamp - sig->timestamp; + log_info(d==1 + ?_("public key %s is %lu second newer than the signature\n") + :_("public key %s is %lu seconds newer than the signature\n"), + keystr_from_pk(pk),d ); + if( !opt.ignore_time_conflict ) + return G10ERR_TIME_CONFLICT; /* pubkey newer than signature */ + } + + cur_time = make_timestamp(); + if( pk->timestamp > cur_time ) + { + ulong d = pk->timestamp - cur_time; + log_info( d==1 + ? _("key %s was created %lu second" + " in the future (time warp or clock problem)\n") + : _("key %s was created %lu seconds" + " in the future (time warp or clock problem)\n"), + keystr_from_pk(pk),d ); + if( !opt.ignore_time_conflict ) + return G10ERR_TIME_CONFLICT; + } + + if( pk->expiredate && pk->expiredate < cur_time ) { + char buf[11]; + if (opt.verbose) + log_info(_("NOTE: signature key %s expired %s\n"), + keystr_from_pk(pk), asctimestamp( pk->expiredate ) ); + /* SIGEXPIRED is deprecated. Use KEYEXPIRED. */ + sprintf(buf,"%lu",(ulong)pk->expiredate); + write_status_text(STATUS_KEYEXPIRED,buf); + write_status(STATUS_SIGEXPIRED); + if(r_expired) + *r_expired = 1; + } + + if(pk->is_revoked && r_revoked) + *r_revoked=1; + + return 0; +} + + +static int +do_check( PKT_public_key *pk, PKT_signature *sig, MD_HANDLE digest, + int *r_expired, int *r_revoked, PKT_public_key *ret_pk ) +{ + MPI result = NULL; + int rc=0; + struct cmp_help_context_s ctx; + + if( (rc=do_check_messages(pk,sig,r_expired,r_revoked)) ) + return rc; + + /* make sure the digest algo is enabled (in case of a detached signature)*/ + md_enable( digest, sig->digest_algo ); + + /* complete the digest */ + if( sig->version >= 4 ) + md_putc( digest, sig->version ); + md_putc( digest, sig->sig_class ); + if( sig->version < 4 ) { + u32 a = sig->timestamp; + md_putc( digest, (a >> 24) & 0xff ); + md_putc( digest, (a >> 16) & 0xff ); + md_putc( digest, (a >> 8) & 0xff ); + md_putc( digest, a & 0xff ); + } + else { + byte buf[6]; + size_t n; + md_putc( digest, sig->pubkey_algo ); + md_putc( digest, sig->digest_algo ); + if( sig->hashed ) { + n = sig->hashed->len; + md_putc (digest, (n >> 8) ); + md_putc (digest, n ); + md_write (digest, sig->hashed->data, n); + n += 6; + } + else { + /* Two octets for the (empty) length of the hashed + section. */ + md_putc (digest, 0); + md_putc (digest, 0); + n = 6; + } + /* add some magic */ + buf[0] = sig->version; + buf[1] = 0xff; + buf[2] = n >> 24; + buf[3] = n >> 16; + buf[4] = n >> 8; + buf[5] = n; + md_write( digest, buf, 6 ); + } + md_final( digest ); + + result = encode_md_value( pk, NULL, digest, sig->digest_algo ); + if (!result) + return G10ERR_GENERAL; + ctx.sig = sig; + ctx.md = digest; + rc = pubkey_verify( pk->pubkey_algo, result, sig->data, pk->pkey ); + mpi_free( result ); + + if( !rc && sig->flags.unknown_critical ) + { + log_info(_("assuming bad signature from key %s" + " due to an unknown critical bit\n"),keystr_from_pk(pk)); + rc = G10ERR_BAD_SIGN; + } + + if(!rc && ret_pk) + copy_public_key(ret_pk,pk); + + return rc; +} + + +static void +hash_uid_node( KBNODE unode, MD_HANDLE md, PKT_signature *sig ) +{ + PKT_user_id *uid = unode->pkt->pkt.user_id; + + assert( unode->pkt->pkttype == PKT_USER_ID ); + if( uid->attrib_data ) { + if( sig->version >=4 ) { + byte buf[5]; + buf[0] = 0xd1; /* packet of type 17 */ + buf[1] = uid->attrib_len >> 24; /* always use 4 length bytes */ + buf[2] = uid->attrib_len >> 16; + buf[3] = uid->attrib_len >> 8; + buf[4] = uid->attrib_len; + md_write( md, buf, 5 ); + } + md_write( md, uid->attrib_data, uid->attrib_len ); + } + else { + if( sig->version >=4 ) { + byte buf[5]; + buf[0] = 0xb4; /* indicates a userid packet */ + buf[1] = uid->len >> 24; /* always use 4 length bytes */ + buf[2] = uid->len >> 16; + buf[3] = uid->len >> 8; + buf[4] = uid->len; + md_write( md, buf, 5 ); + } + md_write( md, uid->name, uid->len ); + } +} + +static void +cache_sig_result ( PKT_signature *sig, int result ) +{ + if ( !result ) { + sig->flags.checked = 1; + sig->flags.valid = 1; + } + else if ( result == G10ERR_BAD_SIGN ) { + sig->flags.checked = 1; + sig->flags.valid = 0; + } + else { + sig->flags.checked = 0; + sig->flags.valid = 0; + } +} + +/* Check the revocation keys to see if any of them have revoked our + pk. sig is the revocation sig. pk is the key it is on. This code + will need to be modified if gpg ever becomes multi-threaded. Note + that this guarantees that a designated revocation sig will never be + considered valid unless it is actually valid, as well as being + issued by a revocation key in a valid direct signature. Note also + that this is written so that a revoked revoker can still issue + revocations: i.e. If A revokes B, but A is revoked, B is still + revoked. I'm not completely convinced this is the proper behavior, + but it matches how PGP does it. -dms */ + +/* Returns 0 if sig is valid (i.e. pk is revoked), non-0 if not + revoked. It is important that G10ERR_NO_PUBKEY is only returned + when a revocation signature is from a valid revocation key + designated in a revkey subpacket, but the revocation key itself + isn't present. */ +int +check_revocation_keys(PKT_public_key *pk,PKT_signature *sig) +{ + static int busy=0; + int i,rc=G10ERR_GENERAL; + + assert(IS_KEY_REV(sig)); + assert((sig->keyid[0]!=pk->keyid[0]) || (sig->keyid[0]!=pk->keyid[1])); + + if(busy) + { + /* return an error (i.e. not revoked), but mark the pk as + uncacheable as we don't really know its revocation status + until it is checked directly. */ + + pk->dont_cache=1; + return rc; + } + + busy=1; + + /* printf("looking at %08lX with a sig from %08lX\n",(ulong)pk->keyid[1], + (ulong)sig->keyid[1]); */ + + /* is the issuer of the sig one of our revokers? */ + if( !pk->revkey && pk->numrevkeys ) + BUG(); + else + for(i=0;i<pk->numrevkeys;i++) + { + u32 keyid[2]; + + keyid_from_fingerprint(pk->revkey[i].fpr,MAX_FINGERPRINT_LEN,keyid); + + if(keyid[0]==sig->keyid[0] && keyid[1]==sig->keyid[1]) + { + MD_HANDLE md; + + md=md_open(sig->digest_algo,0); + hash_public_key(md,pk); + rc=signature_check(sig,md); + cache_sig_result(sig,rc); + break; + } + } + + busy=0; + + return rc; +} + +/* Backsigs (0x19) have the same format as binding sigs (0x18), but + this function is simpler than check_key_signature in a few ways. + For example, there is no support for expiring backsigs since it is + questionable what such a thing actually means. Note also that the + sig cache check here, unlike other sig caches in GnuPG, is not + persistent. */ +int +check_backsig(PKT_public_key *main_pk,PKT_public_key *sub_pk, + PKT_signature *backsig) +{ + MD_HANDLE md; + int rc; + + if(!opt.no_sig_cache && backsig->flags.checked) + { + if((rc=check_digest_algo(backsig->digest_algo))) + return rc; + + return backsig->flags.valid? 0 : G10ERR_BAD_SIGN; + } + + md=md_open(backsig->digest_algo,0); + hash_public_key(md,main_pk); + hash_public_key(md,sub_pk); + rc=do_check(sub_pk,backsig,md,NULL,NULL,NULL); + cache_sig_result(backsig,rc); + md_close(md); + + return rc; +} + + +/**************** + * check the signature pointed to by NODE. This is a key signature. + * If the function detects a self-signature, it uses the PK from + * ROOT and does not read any public key. + */ +int +check_key_signature( KBNODE root, KBNODE node, int *is_selfsig ) +{ + return check_key_signature2(root, node, NULL, NULL, is_selfsig, NULL, NULL ); +} + +/* If check_pk is set, then use it to check the signature in node + rather than getting it from root or the keydb. If ret_pk is set, + fill in the public key that was used to verify the signature. + ret_pk is only meaningful when the verification was successful. */ +/* TODO: add r_revoked here as well. It has the same problems as + r_expiredate and r_expired and the cache. */ +int +check_key_signature2( KBNODE root, KBNODE node, PKT_public_key *check_pk, + PKT_public_key *ret_pk, int *is_selfsig, + u32 *r_expiredate, int *r_expired ) +{ + MD_HANDLE md; + PKT_public_key *pk; + PKT_signature *sig; + int algo; + int rc; + + if( is_selfsig ) + *is_selfsig = 0; + if( r_expiredate ) + *r_expiredate = 0; + if( r_expired ) + *r_expired = 0; + assert( node->pkt->pkttype == PKT_SIGNATURE ); + assert( root->pkt->pkttype == PKT_PUBLIC_KEY ); + + pk = root->pkt->pkt.public_key; + sig = node->pkt->pkt.signature; + algo = sig->digest_algo; + + /* Check whether we have cached the result of a previous signature + check. Note that we may no longer have the pubkey or hash + needed to verify a sig, but can still use the cached value. A + cache refresh detects and clears these cases. */ + if ( !opt.no_sig_cache ) { + if (sig->flags.checked) { /*cached status available*/ + if( is_selfsig ) { + u32 keyid[2]; + + keyid_from_pk( pk, keyid ); + if( keyid[0] == sig->keyid[0] && keyid[1] == sig->keyid[1] ) + *is_selfsig = 1; + } + /* BUG: This is wrong for non-self-sigs.. needs to be the + actual pk */ + if((rc=do_check_messages(pk,sig,r_expired,NULL))) + return rc; + return sig->flags.valid? 0 : G10ERR_BAD_SIGN; + } + } + + if( (rc=check_pubkey_algo(sig->pubkey_algo)) ) + return rc; + if( (rc=check_digest_algo(algo)) ) + return rc; + + if( sig->sig_class == 0x20 ) { /* key revocation */ + u32 keyid[2]; + keyid_from_pk( pk, keyid ); + + /* is it a designated revoker? */ + if(keyid[0]!=sig->keyid[0] || keyid[1]!=sig->keyid[1]) + rc=check_revocation_keys(pk,sig); + else + { + md = md_open( algo, 0 ); + hash_public_key( md, pk ); + rc = do_check( pk, sig, md, r_expired, NULL, ret_pk ); + cache_sig_result ( sig, rc ); + md_close(md); + } + } + else if( sig->sig_class == 0x28 ) { /* subkey revocation */ + KBNODE snode = find_prev_kbnode( root, node, PKT_PUBLIC_SUBKEY ); + + if( snode ) { + md = md_open( algo, 0 ); + hash_public_key( md, pk ); + hash_public_key( md, snode->pkt->pkt.public_key ); + rc = do_check( pk, sig, md, r_expired, NULL, ret_pk ); + cache_sig_result ( sig, rc ); + md_close(md); + } + else + { + if (opt.verbose) + log_info (_("key %s: no subkey for subkey" + " revocation signature\n"),keystr_from_pk(pk)); + rc = G10ERR_SIG_CLASS; + } + } + else if( sig->sig_class == 0x18 ) { /* key binding */ + KBNODE snode = find_prev_kbnode( root, node, PKT_PUBLIC_SUBKEY ); + + if( snode ) { + if( is_selfsig ) { /* does this make sense????? */ + u32 keyid[2]; /* it should always be a selfsig */ + + keyid_from_pk( pk, keyid ); + if( keyid[0] == sig->keyid[0] && keyid[1] == sig->keyid[1] ) + *is_selfsig = 1; + } + md = md_open( algo, 0 ); + hash_public_key( md, pk ); + hash_public_key( md, snode->pkt->pkt.public_key ); + rc = do_check( pk, sig, md, r_expired, NULL, ret_pk ); + cache_sig_result ( sig, rc ); + md_close(md); + } + else + { + if (opt.verbose) + log_info(_("key %s: no subkey for subkey" + " binding signature\n"),keystr_from_pk(pk)); + rc = G10ERR_SIG_CLASS; + } + } + else if( sig->sig_class == 0x1f ) { /* direct key signature */ + md = md_open( algo, 0 ); + hash_public_key( md, pk ); + rc = do_check( pk, sig, md, r_expired, NULL, ret_pk ); + cache_sig_result ( sig, rc ); + md_close(md); + } + else { /* all other classes */ + KBNODE unode = find_prev_kbnode( root, node, PKT_USER_ID ); + + if( unode ) { + u32 keyid[2]; + + keyid_from_pk( pk, keyid ); + md = md_open( algo, 0 ); + hash_public_key( md, pk ); + hash_uid_node( unode, md, sig ); + if( keyid[0] == sig->keyid[0] && keyid[1] == sig->keyid[1] ) + { + if( is_selfsig ) + *is_selfsig = 1; + rc = do_check( pk, sig, md, r_expired, NULL, ret_pk ); + } + else if (check_pk) + rc=do_check(check_pk,sig,md,r_expired,NULL,ret_pk); + else + rc=signature_check2(sig,md,r_expiredate,r_expired,NULL,ret_pk); + + cache_sig_result ( sig, rc ); + md_close(md); + } + else + { + if (!opt.quiet) + log_info ("key %s: no user ID for key signature packet" + " of class %02x\n",keystr_from_pk(pk),sig->sig_class); + rc = G10ERR_SIG_CLASS; + } + } + + return rc; +} diff --git a/g10/sign.c b/g10/sign.c new file mode 100644 index 0000000..82076d7 --- /dev/null +++ b/g10/sign.c @@ -0,0 +1,1578 @@ +/* sign.c - sign data + * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, + * 2006 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <assert.h> +#include <unistd.h> /* need sleep() */ + +#include "options.h" +#include "packet.h" +#include "errors.h" +#include "iobuf.h" +#include "keydb.h" +#include "memory.h" +#include "util.h" +#include "main.h" +#include "filter.h" +#include "ttyio.h" +#include "trustdb.h" +#include "status.h" +#include "i18n.h" +#include "cardglue.h" + + +#ifdef HAVE_DOSISH_SYSTEM +#define LF "\r\n" +void __stdcall Sleep(ulong); +#define sleep(a) Sleep((a)*1000) +#else +#define LF "\n" +#endif + +static int recipient_digest_algo=0; + +/**************** + * Create notations and other stuff. It is assumed that the stings in + * STRLIST are already checked to contain only printable data and have + * a valid NAME=VALUE format. + */ +static void +mk_notation_policy_etc( PKT_signature *sig, + PKT_public_key *pk, PKT_secret_key *sk ) +{ + const char *string; + char *s=NULL; + STRLIST pu=NULL; + struct notation *nd=NULL; + struct expando_args args; + + memset(&args,0,sizeof(args)); + args.pk=pk; + args.sk=sk; + + /* It is actually impossible to get here when making a v3 key + signature since keyedit.c:sign_uids will automatically bump a + signature with a notation or policy url up to v4, but it is + good to do these checks anyway. */ + + /* notation data */ + if(IS_SIG(sig) && opt.sig_notations) + { + if(sig->version<4) + log_error(_("can't put notation data into v3 (PGP 2.x style) " + "signatures\n")); + else + nd=opt.sig_notations; + } + else if( IS_CERT(sig) && opt.cert_notations ) + { + if(sig->version<4) + log_error(_("can't put notation data into v3 (PGP 2.x style) " + "key signatures\n")); + else + nd=opt.cert_notations; + } + + if(nd) + { + struct notation *i; + + for(i=nd;i;i=i->next) + { + i->altvalue=pct_expando(i->value,&args); + if(!i->altvalue) + log_error(_("WARNING: unable to %%-expand notation " + "(too large). Using unexpanded.\n")); + } + + keygen_add_notations(sig,nd); + + for(i=nd;i;i=i->next) + { + xfree(i->altvalue); + i->altvalue=NULL; + } + } + + /* set policy URL */ + if( IS_SIG(sig) && opt.sig_policy_url ) + { + if(sig->version<4) + log_error(_("can't put a policy URL into v3 (PGP 2.x style) " + "signatures\n")); + else + pu=opt.sig_policy_url; + } + else if( IS_CERT(sig) && opt.cert_policy_url ) + { + if(sig->version<4) + log_error(_("can't put a policy URL into v3 key (PGP 2.x style) " + "signatures\n")); + else + pu=opt.cert_policy_url; + } + + for(;pu;pu=pu->next) + { + string = pu->d; + + s=pct_expando(string,&args); + if(!s) + { + log_error(_("WARNING: unable to %%-expand policy URL " + "(too large). Using unexpanded.\n")); + s=xstrdup(string); + } + + build_sig_subpkt(sig,SIGSUBPKT_POLICY| + ((pu->flags & 1)?SIGSUBPKT_FLAG_CRITICAL:0), + s,strlen(s)); + + xfree(s); + } + + /* preferred keyserver URL */ + if( IS_SIG(sig) && opt.sig_keyserver_url ) + { + if(sig->version<4) + log_info("can't put a preferred keyserver URL into v3 signatures\n"); + else + pu=opt.sig_keyserver_url; + } + + for(;pu;pu=pu->next) + { + string = pu->d; + + s=pct_expando(string,&args); + if(!s) + { + log_error(_("WARNING: unable to %%-expand preferred keyserver URL" + " (too large). Using unexpanded.\n")); + s=xstrdup(string); + } + + build_sig_subpkt(sig,SIGSUBPKT_PREF_KS| + ((pu->flags & 1)?SIGSUBPKT_FLAG_CRITICAL:0), + s,strlen(s)); + + xfree(s); + } +} + + +/* + * Helper to hash a user ID packet. + */ +static void +hash_uid (MD_HANDLE md, int sigversion, const PKT_user_id *uid) +{ + if ( sigversion >= 4 ) { + byte buf[5]; + + if(uid->attrib_data) { + buf[0] = 0xd1; /* indicates an attribute packet */ + buf[1] = uid->attrib_len >> 24; /* always use 4 length bytes */ + buf[2] = uid->attrib_len >> 16; + buf[3] = uid->attrib_len >> 8; + buf[4] = uid->attrib_len; + } + else { + buf[0] = 0xb4; /* indicates a userid packet */ + buf[1] = uid->len >> 24; /* always use 4 length bytes */ + buf[2] = uid->len >> 16; + buf[3] = uid->len >> 8; + buf[4] = uid->len; + } + md_write( md, buf, 5 ); + } + + if(uid->attrib_data) + md_write (md, uid->attrib_data, uid->attrib_len ); + else + md_write (md, uid->name, uid->len ); +} + + +/* + * Helper to hash some parts from the signature + */ +static void +hash_sigversion_to_magic (MD_HANDLE md, const PKT_signature *sig) +{ + if (sig->version >= 4) + md_putc (md, sig->version); + md_putc (md, sig->sig_class); + if (sig->version < 4) { + u32 a = sig->timestamp; + md_putc (md, (a >> 24) & 0xff ); + md_putc (md, (a >> 16) & 0xff ); + md_putc (md, (a >> 8) & 0xff ); + md_putc (md, a & 0xff ); + } + else { + byte buf[6]; + size_t n; + + md_putc (md, sig->pubkey_algo); + md_putc (md, sig->digest_algo); + if (sig->hashed) { + n = sig->hashed->len; + md_putc (md, (n >> 8) ); + md_putc (md, n ); + md_write (md, sig->hashed->data, n ); + n += 6; + } + else { + md_putc (md, 0); /* always hash the length of the subpacket*/ + md_putc (md, 0); + n = 6; + } + /* add some magic */ + buf[0] = sig->version; + buf[1] = 0xff; + buf[2] = n >> 24; /* hmmm, n is only 16 bit, so this is always 0 */ + buf[3] = n >> 16; + buf[4] = n >> 8; + buf[5] = n; + md_write (md, buf, 6); + } +} + + +static int +do_sign( PKT_secret_key *sk, PKT_signature *sig, + MD_HANDLE md, int digest_algo ) +{ + MPI frame; + byte *dp; + int rc; + + if( sk->timestamp > sig->timestamp ) { + ulong d = sk->timestamp - sig->timestamp; + log_info( d==1 ? _("key has been created %lu second " + "in future (time warp or clock problem)\n") + : _("key has been created %lu seconds " + "in future (time warp or clock problem)\n"), d ); + if( !opt.ignore_time_conflict ) + return G10ERR_TIME_CONFLICT; + } + + + print_pubkey_algo_note(sk->pubkey_algo); + + if( !digest_algo ) + digest_algo = md_get_algo(md); + + print_digest_algo_note( digest_algo ); + dp = md_read( md, digest_algo ); + sig->digest_algo = digest_algo; + sig->digest_start[0] = dp[0]; + sig->digest_start[1] = dp[1]; + if (sk->is_protected && sk->protect.s2k.mode == 1002) + { +#ifdef ENABLE_CARD_SUPPORT + unsigned char *rbuf; + size_t rbuflen; + char *snbuf; + + snbuf = serialno_and_fpr_from_sk (sk->protect.iv, + sk->protect.ivlen, sk); + rc = agent_scd_pksign (snbuf, digest_algo, + md_read (md, digest_algo), + md_digest_length (digest_algo), + &rbuf, &rbuflen); + xfree (snbuf); + if (!rc) + { + sig->data[0] = mpi_alloc ( (rbuflen+BYTES_PER_MPI_LIMB-1) + / BYTES_PER_MPI_LIMB ); + mpi_set_buffer (sig->data[0], rbuf, rbuflen, 0); + xfree (rbuf); + } +#else + return G10ERR_UNSUPPORTED; +#endif /* ENABLE_CARD_SUPPORT */ + } + else + { + frame = encode_md_value( NULL, sk, md, digest_algo ); + if (!frame) + return G10ERR_GENERAL; + rc = pubkey_sign( sk->pubkey_algo, sig->data, frame, sk->skey ); + mpi_free(frame); + } + + if (!rc && !opt.no_sig_create_check) { + /* check that the signature verification worked and nothing is + * fooling us e.g. by a bug in the signature create + * code or by deliberately introduced faults. */ + PKT_public_key *pk = xmalloc_clear (sizeof *pk); + + if( get_pubkey( pk, sig->keyid ) ) + rc = G10ERR_NO_PUBKEY; + else { + frame = encode_md_value (pk, NULL, md, sig->digest_algo ); + if (!frame) + rc = G10ERR_GENERAL; + else + rc = pubkey_verify (pk->pubkey_algo, frame, + sig->data, pk->pkey ); + mpi_free (frame); + } + if (rc) + log_error (_("checking created signature failed: %s\n"), + g10_errstr (rc)); + free_public_key (pk); + } + if( rc ) + log_error(_("signing failed: %s\n"), g10_errstr(rc) ); + else { + if( opt.verbose ) { + char *ustr = get_user_id_string_native (sig->keyid); + log_info(_("%s/%s signature from: \"%s\"\n"), + pubkey_algo_to_string(sk->pubkey_algo), + digest_algo_to_string(sig->digest_algo), + ustr ); + xfree(ustr); + } + } + return rc; +} + + +int +complete_sig( PKT_signature *sig, PKT_secret_key *sk, MD_HANDLE md ) +{ + int rc=0; + + if( !(rc=check_secret_key( sk, 0 )) ) + rc = do_sign( sk, sig, md, 0 ); + return rc; +} + +static int +match_dsa_hash(unsigned int qbytes) +{ + if(qbytes<=20) + return DIGEST_ALGO_SHA1; +#ifdef USE_SHA256 + if(qbytes<=28) + return DIGEST_ALGO_SHA224; + if(qbytes<=32) + return DIGEST_ALGO_SHA256; +#endif +#ifdef USE_SHA512 + if(qbytes<=48) + return DIGEST_ALGO_SHA384; + if(qbytes<=64) + return DIGEST_ALGO_SHA512; +#endif + return DEFAULT_DIGEST_ALGO; + /* DEFAULT_DIGEST_ALGO will certainly fail, but it's the best wrong + answer we have if the larger SHAs aren't there. */ +} + + +/* + First try --digest-algo. If that isn't set, see if the recipient + has a preferred algorithm (which is also filtered through + --preferred-digest-prefs). If we're making a signature without a + particular recipient (i.e. signing, rather than signing+encrypting) + then take the first algorithm in --preferred-digest-prefs that is + usable for the pubkey algorithm. If --preferred-digest-prefs isn't + set, then take the OpenPGP default (i.e. SHA-1). + + Possible improvement: Use the highest-ranked usable algorithm from + the signing key prefs either before or after using the personal + list? +*/ + +static int +hash_for(PKT_secret_key *sk) +{ + if( opt.def_digest_algo ) + return opt.def_digest_algo; + else if( recipient_digest_algo ) + return recipient_digest_algo; + else if(sk->pubkey_algo==PUBKEY_ALGO_DSA) + { + unsigned int qbytes=mpi_get_nbits(sk->skey[1])/8; + + /* It's a DSA key, so find a hash that is the same size as q or + larger. If q is 160, assume it is an old DSA key and use a + 160-bit hash unless --enable-dsa2 is set, in which case act + like a new DSA key that just happens to have a 160-bit q + (i.e. allow truncation). If q is not 160, by definition it + must be a new DSA key. */ + + if(opt.personal_digest_prefs) + { + prefitem_t *prefs; + + if(qbytes!=20 || opt.flags.dsa2) + { + for(prefs=opt.personal_digest_prefs;prefs->type;prefs++) + if(md_digest_length(prefs->value)>=qbytes) + return prefs->value; + } + else + { + for(prefs=opt.personal_digest_prefs;prefs->type;prefs++) + if(md_digest_length(prefs->value)==qbytes) + return prefs->value; + } + } + + return match_dsa_hash(qbytes); + } + else if(sk->is_protected && sk->protect.s2k.mode==1002) + { + /* The sk lives on a smartcard, and current smartcards only + handle SHA-1 and RIPEMD/160. This is correct now, but may + need revision as the cards add algorithms. */ + + if(opt.personal_digest_prefs) + { + prefitem_t *prefs; + + for(prefs=opt.personal_digest_prefs;prefs->type;prefs++) + if(prefs->value==DIGEST_ALGO_SHA1 + || prefs->value==DIGEST_ALGO_RMD160) + return prefs->value; + } + + return DIGEST_ALGO_SHA1; + } + else if(PGP2 && sk->pubkey_algo == PUBKEY_ALGO_RSA && sk->version < 4 ) + { + /* Old-style PGP only understands MD5 */ + return DIGEST_ALGO_MD5; + } + else if( opt.personal_digest_prefs ) + { + /* It's not DSA, so we can use whatever the first hash algorithm + is in the pref list */ + return opt.personal_digest_prefs[0].value; + } + else + return DEFAULT_DIGEST_ALGO; +} + +static int +only_old_style( SK_LIST sk_list ) +{ + SK_LIST sk_rover = NULL; + int old_style = 0; + + /* if there are only old style capable key we use the old sytle */ + for( sk_rover = sk_list; sk_rover; sk_rover = sk_rover->next ) { + PKT_secret_key *sk = sk_rover->sk; + if( sk->pubkey_algo == PUBKEY_ALGO_RSA && sk->version < 4 ) + old_style = 1; + else + return 0; + } + return old_style; +} + + +static void +print_status_sig_created ( PKT_secret_key *sk, PKT_signature *sig, int what ) +{ + byte array[MAX_FINGERPRINT_LEN], *p; + char buf[100+MAX_FINGERPRINT_LEN*2]; + size_t i, n; + + sprintf(buf, "%c %d %d %02x %lu ", + what, sig->pubkey_algo, sig->digest_algo, sig->sig_class, + (ulong)sig->timestamp ); + + fingerprint_from_sk( sk, array, &n ); + p = buf + strlen(buf); + for(i=0; i < n ; i++ ) + sprintf(p+2*i, "%02X", array[i] ); + + write_status_text( STATUS_SIG_CREATED, buf ); +} + + +/* + * Loop over the secret certificates in SK_LIST and build the one pass + * signature packets. OpenPGP says that the data should be bracket by + * the onepass-sig and signature-packet; so we build these onepass + * packet here in reverse order + */ +static int +write_onepass_sig_packets (SK_LIST sk_list, IOBUF out, int sigclass ) +{ + int skcount; + SK_LIST sk_rover; + + for (skcount=0, sk_rover=sk_list; sk_rover; sk_rover = sk_rover->next) + skcount++; + + for (; skcount; skcount--) { + PKT_secret_key *sk; + PKT_onepass_sig *ops; + PACKET pkt; + int i, rc; + + for (i=0, sk_rover = sk_list; sk_rover; sk_rover = sk_rover->next ) { + if (++i == skcount) + break; + } + + sk = sk_rover->sk; + ops = xmalloc_clear (sizeof *ops); + ops->sig_class = sigclass; + ops->digest_algo = hash_for (sk); + ops->pubkey_algo = sk->pubkey_algo; + keyid_from_sk (sk, ops->keyid); + ops->last = (skcount == 1); + + init_packet(&pkt); + pkt.pkttype = PKT_ONEPASS_SIG; + pkt.pkt.onepass_sig = ops; + rc = build_packet (out, &pkt); + free_packet (&pkt); + if (rc) { + log_error ("build onepass_sig packet failed: %s\n", + g10_errstr(rc)); + return rc; + } + } + + return 0; +} + +/* + * Helper to write the plaintext (literal data) packet + */ +static int +write_plaintext_packet (IOBUF out, IOBUF inp, const char *fname, int ptmode) +{ + PKT_plaintext *pt = NULL; + u32 filesize; + int rc = 0; + + if (!opt.no_literal) + pt=setup_plaintext_name(fname,inp); + + /* try to calculate the length of the data */ + if ( !iobuf_is_pipe_filename (fname) && *fname ) + { + off_t tmpsize; + int overflow; + + if( !(tmpsize = iobuf_get_filelength(inp, &overflow)) + && !overflow ) + log_info (_("WARNING: `%s' is an empty file\n"), fname); + + /* We can't encode the length of very large files because + OpenPGP uses only 32 bit for file sizes. So if the size of + a file is larger than 2^32 minus some bytes for packet + headers, we switch to partial length encoding. */ + if ( tmpsize < (IOBUF_FILELENGTH_LIMIT - 65536) ) + filesize = tmpsize; + else + filesize = 0; + + /* Because the text_filter modifies the length of the + * data, it is not possible to know the used length + * without a double read of the file - to avoid that + * we simple use partial length packets. */ + if ( ptmode == 't' ) + filesize = 0; + } + else + filesize = opt.set_filesize? opt.set_filesize : 0; /* stdin */ + + if (!opt.no_literal) { + PACKET pkt; + + pt->timestamp = make_timestamp (); + pt->mode = ptmode; + pt->len = filesize; + pt->new_ctb = !pt->len && !RFC1991; + pt->buf = inp; + init_packet(&pkt); + pkt.pkttype = PKT_PLAINTEXT; + pkt.pkt.plaintext = pt; + /*cfx.datalen = filesize? calc_packet_length( &pkt ) : 0;*/ + if( (rc = build_packet (out, &pkt)) ) + log_error ("build_packet(PLAINTEXT) failed: %s\n", + g10_errstr(rc) ); + pt->buf = NULL; + } + else { + byte copy_buffer[4096]; + int bytes_copied; + + while ((bytes_copied = iobuf_read(inp, copy_buffer, 4096)) != -1) + if (iobuf_write(out, copy_buffer, bytes_copied) == -1) { + rc = G10ERR_WRITE_FILE; + log_error ("copying input to output failed: %s\n", + g10_errstr(rc)); + break; + } + wipememory(copy_buffer,4096); /* burn buffer */ + } + /* fixme: it seems that we never freed pt/pkt */ + + return rc; +} + +/* + * Write the signatures from the SK_LIST to OUT. HASH must be a non-finalized + * hash which will not be changes here. + */ +static int +write_signature_packets (SK_LIST sk_list, IOBUF out, MD_HANDLE hash, + int sigclass, u32 timestamp, u32 duration, + int status_letter) +{ + SK_LIST sk_rover; + + /* loop over the secret certificates */ + for (sk_rover = sk_list; sk_rover; sk_rover = sk_rover->next) { + PKT_secret_key *sk; + PKT_signature *sig; + MD_HANDLE md; + int rc; + + sk = sk_rover->sk; + + /* build the signature packet */ + sig = xmalloc_clear (sizeof *sig); + if(opt.force_v3_sigs || RFC1991) + sig->version=3; + else if(duration || opt.sig_policy_url + || opt.sig_notations || opt.sig_keyserver_url) + sig->version=4; + else + sig->version=sk->version; + keyid_from_sk (sk, sig->keyid); + sig->digest_algo = hash_for(sk); + sig->pubkey_algo = sk->pubkey_algo; + if(timestamp) + sig->timestamp = timestamp; + else + sig->timestamp = make_timestamp(); + if(duration) + sig->expiredate = sig->timestamp+duration; + sig->sig_class = sigclass; + + md = md_copy (hash); + + if (sig->version >= 4) + build_sig_subpkt_from_sig (sig); + mk_notation_policy_etc (sig, NULL, sk); + + hash_sigversion_to_magic (md, sig); + md_final (md); + + rc = do_sign( sk, sig, md, hash_for (sk) ); + md_close (md); + + if( !rc ) { /* and write it */ + PACKET pkt; + + init_packet(&pkt); + pkt.pkttype = PKT_SIGNATURE; + pkt.pkt.signature = sig; + rc = build_packet (out, &pkt); + if (!rc && is_status_enabled()) { + print_status_sig_created ( sk, sig, status_letter); + } + free_packet (&pkt); + if (rc) + log_error ("build signature packet failed: %s\n", + g10_errstr(rc) ); + } + if( rc ) + return rc;; + } + + return 0; +} + +/**************** + * Sign the files whose names are in FILENAME. + * If DETACHED has the value true, + * make a detached signature. If FILENAMES->d is NULL read from stdin + * and ignore the detached mode. Sign the file with all secret keys + * which can be taken from LOCUSR, if this is NULL, use the default one + * If ENCRYPTFLAG is true, use REMUSER (or ask if it is NULL) to encrypt the + * signed data for these users. + * If OUTFILE is not NULL; this file is used for output and the function + * does not ask for overwrite permission; output is then always + * uncompressed, non-armored and in binary mode. + */ +int +sign_file( STRLIST filenames, int detached, STRLIST locusr, + int encryptflag, STRLIST remusr, const char *outfile ) +{ + const char *fname; + armor_filter_context_t afx; + compress_filter_context_t zfx; + md_filter_context_t mfx; + text_filter_context_t tfx; + progress_filter_context_t pfx; + encrypt_filter_context_t efx; + IOBUF inp = NULL, out = NULL; + PACKET pkt; + int rc = 0; + PK_LIST pk_list = NULL; + SK_LIST sk_list = NULL; + SK_LIST sk_rover = NULL; + int multifile = 0; + u32 duration=0; + + memset( &afx, 0, sizeof afx); + memset( &zfx, 0, sizeof zfx); + memset( &mfx, 0, sizeof mfx); + memset( &efx, 0, sizeof efx); + init_packet( &pkt ); + + if( filenames ) { + fname = filenames->d; + multifile = !!filenames->next; + } + else + fname = NULL; + + if( fname && filenames->next && (!detached || encryptflag) ) + log_bug("multiple files can only be detached signed"); + + if(encryptflag==2 + && (rc=setup_symkey(&efx.symkey_s2k,&efx.symkey_dek))) + goto leave; + + if(!opt.force_v3_sigs && !RFC1991) + { + if(opt.ask_sig_expire && !opt.batch) + duration=ask_expire_interval(1,opt.def_sig_expire); + else + duration=parse_expire_string(opt.def_sig_expire); + } + + if( (rc=build_sk_list( locusr, &sk_list, 1, PUBKEY_USAGE_SIG )) ) + goto leave; + + if(PGP2 && !only_old_style(sk_list)) + { + log_info(_("you can only detach-sign with PGP 2.x style keys " + "while in --pgp2 mode\n")); + compliance_failure(); + } + + if(encryptflag && (rc=build_pk_list( remusr, &pk_list, PUBKEY_USAGE_ENC ))) + goto leave; + + /* prepare iobufs */ + if( multifile ) /* have list of filenames */ + inp = NULL; /* we do it later */ + else { + inp = iobuf_open(fname); + if (inp && is_secured_file (iobuf_get_fd (inp))) + { + iobuf_close (inp); + inp = NULL; + errno = EPERM; + } + if( !inp ) { + log_error(_("can't open `%s': %s\n"), fname? fname: "[stdin]", + strerror(errno) ); + rc = G10ERR_OPEN_FILE; + goto leave; + } + + handle_progress (&pfx, inp, fname); + } + + if( outfile ) { + if (is_secured_filename ( outfile )) { + out = NULL; + errno = EPERM; + } + else + out = iobuf_create( outfile ); + if( !out ) + { + log_error(_("can't create `%s': %s\n"), outfile, strerror(errno) ); + rc = G10ERR_CREATE_FILE; + goto leave; + } + else if( opt.verbose ) + log_info(_("writing to `%s'\n"), outfile ); + } + else if( (rc = open_outfile( fname, opt.armor? 1: detached? 2:0, &out ))) + goto leave; + + /* prepare to calculate the MD over the input */ + if( opt.textmode && !outfile && !multifile ) + { + memset( &tfx, 0, sizeof tfx); + iobuf_push_filter( inp, text_filter, &tfx ); + } + + mfx.md = md_open(0, 0); + if (DBG_HASHING) + md_start_debug (mfx.md, "sign"); + + /* If we're encrypting and signing, it is reasonable to pick the + hash algorithm to use out of the recepient key prefs. This is + best effort only, as in a DSA2 and smartcard world there are + cases where we cannot please everyone with a single hash (DSA2 + wants >160 and smartcards want =160). In the future this could + be more complex with different hashes for each sk, but the + current design requires a single hash for all SKs. */ + if(pk_list) + { + if(opt.def_digest_algo) + { + if(!opt.expert && + select_algo_from_prefs(pk_list,PREFTYPE_HASH, + opt.def_digest_algo, + NULL)!=opt.def_digest_algo) + log_info(_("WARNING: forcing digest algorithm %s (%d)" + " violates recipient preferences\n"), + digest_algo_to_string(opt.def_digest_algo), + opt.def_digest_algo); + } + else + { + union pref_hint hint; + int algo,smartcard=0; + + hint.digest_length=0; + + /* Of course, if the recipient asks for something + unreasonable (like the wrong hash for a DSA key) then + don't do it. Check all sk's - if any are DSA or live + on a smartcard, then the hash has restrictions and we + may not be able to give the recipient what they want. + For DSA, pass a hint for the largest q we have. Note + that this means that a q>160 key will override a q=160 + key and force the use of truncation for the q=160 key. + The alternative would be to ignore the recipient prefs + completely and get a different hash for each DSA key in + hash_for(). The override behavior here is more or less + reasonable as it is under the control of the user which + keys they sign with for a given message and the fact + that the message with multiple signatures won't be + usable on an implementation that doesn't understand + DSA2 anyway. */ + + for( sk_rover = sk_list; sk_rover; sk_rover = sk_rover->next ) + { + if(sk_rover->sk->pubkey_algo==PUBKEY_ALGO_DSA) + { + int temp_hashlen=mpi_get_nbits(sk_rover->sk->skey[1])/8; + + /* Pick a hash that is large enough for our + largest q */ + + if(hint.digest_length<temp_hashlen) + hint.digest_length=temp_hashlen; + } + else if(sk_rover->sk->is_protected + && sk_rover->sk->protect.s2k.mode==1002) + smartcard=1; + } + + /* Current smartcards only do 160-bit hashes. If we have + to have a >160-bit hash, then we can't use the + recipient prefs as we'd need both =160 and >160 at the + same time and recipient prefs currently require a + single hash for all signatures. All this may well have + to change as the cards add algorithms. */ + + if(!smartcard || (smartcard && hint.digest_length==20)) + if((algo= + select_algo_from_prefs(pk_list,PREFTYPE_HASH,-1,&hint))>0) + recipient_digest_algo=algo; + } + } + + for( sk_rover = sk_list; sk_rover; sk_rover = sk_rover->next ) { + PKT_secret_key *sk = sk_rover->sk; + md_enable(mfx.md, hash_for(sk)); + } + + if( !multifile ) + iobuf_push_filter( inp, md_filter, &mfx ); + + if( detached && !encryptflag && !RFC1991 ) + afx.what = 2; + + if( opt.armor && !outfile ) + iobuf_push_filter( out, armor_filter, &afx ); + + if( encryptflag ) { + efx.pk_list = pk_list; + /* fixme: set efx.cfx.datalen if known */ + iobuf_push_filter( out, encrypt_filter, &efx ); + } + + if( opt.compress_algo && !outfile && ( !detached || opt.compress_sigs) ) + { + int compr_algo=opt.compress_algo; + + /* If not forced by user */ + if(compr_algo==-1) + { + /* If we're not encrypting, then select_algo_from_prefs + will fail and we'll end up with the default. If we are + encrypting, select_algo_from_prefs cannot fail since + there is an assumed preference for uncompressed data. + Still, if it did fail, we'll also end up with the + default. */ + + if((compr_algo= + select_algo_from_prefs(pk_list,PREFTYPE_ZIP,-1,NULL))==-1) + compr_algo=default_compress_algo(); + } + else if(!opt.expert && pk_list + && select_algo_from_prefs(pk_list,PREFTYPE_ZIP, + compr_algo,NULL)!=compr_algo) + log_info(_("WARNING: forcing compression algorithm %s (%d)" + " violates recipient preferences\n"), + compress_algo_to_string(compr_algo),compr_algo); + + /* algo 0 means no compression */ + if( compr_algo ) + push_compress_filter(out,&zfx,compr_algo); + } + + /* Write the one-pass signature packets if needed */ + if (!detached && !RFC1991) { + rc = write_onepass_sig_packets (sk_list, out, + opt.textmode && !outfile ? 0x01:0x00); + if (rc) + goto leave; + } + + write_status (STATUS_BEGIN_SIGNING); + + /* Setup the inner packet. */ + if( detached ) { + if( multifile ) { + STRLIST sl; + + if( opt.verbose ) + log_info(_("signing:") ); + /* must walk reverse trough this list */ + for( sl = strlist_last(filenames); sl; + sl = strlist_prev( filenames, sl ) ) { + inp = iobuf_open(sl->d); + if (inp && is_secured_file (iobuf_get_fd (inp))) + { + iobuf_close (inp); + inp = NULL; + errno = EPERM; + } + if( !inp ) + { + log_error(_("can't open `%s': %s\n"), + sl->d,strerror(errno)); + rc = G10ERR_OPEN_FILE; + goto leave; + } + handle_progress (&pfx, inp, sl->d); + if( opt.verbose ) + fprintf(stderr, " `%s'", sl->d ); + if(opt.textmode) + { + memset( &tfx, 0, sizeof tfx); + iobuf_push_filter( inp, text_filter, &tfx ); + } + iobuf_push_filter( inp, md_filter, &mfx ); + while( iobuf_get(inp) != -1 ) + ; + iobuf_close(inp); inp = NULL; + } + if( opt.verbose ) + putc( '\n', stderr ); + } + else { + /* read, so that the filter can calculate the digest */ + while( iobuf_get(inp) != -1 ) + ; + } + } + else { + rc = write_plaintext_packet (out, inp, fname, + opt.textmode && !outfile ? 't':'b'); + } + + /* catch errors from above */ + if (rc) + goto leave; + + /* write the signatures */ + rc = write_signature_packets (sk_list, out, mfx.md, + opt.textmode && !outfile? 0x01 : 0x00, + 0, duration, detached ? 'D':'S'); + if( rc ) + goto leave; + + + leave: + if( rc ) + iobuf_cancel(out); + else { + iobuf_close(out); + if (encryptflag) + write_status( STATUS_END_ENCRYPTION ); + } + iobuf_close(inp); + md_close( mfx.md ); + release_sk_list( sk_list ); + release_pk_list( pk_list ); + recipient_digest_algo=0; + return rc; +} + + + +/**************** + * make a clear signature. note that opt.armor is not needed + */ +int +clearsign_file( const char *fname, STRLIST locusr, const char *outfile ) +{ + armor_filter_context_t afx; + progress_filter_context_t pfx; + MD_HANDLE textmd = NULL; + IOBUF inp = NULL, out = NULL; + PACKET pkt; + int rc = 0; + SK_LIST sk_list = NULL; + SK_LIST sk_rover = NULL; + int old_style = RFC1991; + int only_md5 = 0; + u32 duration=0; + + memset( &afx, 0, sizeof afx); + init_packet( &pkt ); + + if(!opt.force_v3_sigs && !RFC1991) + { + if(opt.ask_sig_expire && !opt.batch) + duration=ask_expire_interval(1,opt.def_sig_expire); + else + duration=parse_expire_string(opt.def_sig_expire); + } + + if( (rc=build_sk_list( locusr, &sk_list, 1, PUBKEY_USAGE_SIG )) ) + goto leave; + + if( !old_style && !duration ) + old_style = only_old_style( sk_list ); + + if(PGP2 && !only_old_style(sk_list)) + { + log_info(_("you can only clearsign with PGP 2.x style keys " + "while in --pgp2 mode\n")); + compliance_failure(); + } + + /* prepare iobufs */ + inp = iobuf_open(fname); + if (inp && is_secured_file (iobuf_get_fd (inp))) + { + iobuf_close (inp); + inp = NULL; + errno = EPERM; + } + if( !inp ) { + log_error(_("can't open `%s': %s\n"), fname? fname: "[stdin]", + strerror(errno) ); + rc = G10ERR_OPEN_FILE; + goto leave; + } + handle_progress (&pfx, inp, fname); + + if( outfile ) { + if (is_secured_filename (outfile) ) { + outfile = NULL; + errno = EPERM; + } + else + out = iobuf_create( outfile ); + if( !out ) + { + log_error(_("can't create `%s': %s\n"), outfile, strerror(errno) ); + rc = G10ERR_CREATE_FILE; + goto leave; + } + else if( opt.verbose ) + log_info(_("writing to `%s'\n"), outfile ); + } + else if( (rc = open_outfile( fname, 1, &out )) ) + goto leave; + + iobuf_writestr(out, "-----BEGIN PGP SIGNED MESSAGE-----" LF ); + + for( sk_rover = sk_list; sk_rover; sk_rover = sk_rover->next ) { + PKT_secret_key *sk = sk_rover->sk; + if( hash_for(sk) == DIGEST_ALGO_MD5 ) + only_md5 = 1; + else { + only_md5 = 0; + break; + } + } + + if( !(old_style && only_md5) ) { + const char *s; + int any = 0; + byte hashs_seen[256]; + + memset( hashs_seen, 0, sizeof hashs_seen ); + iobuf_writestr(out, "Hash: " ); + for( sk_rover = sk_list; sk_rover; sk_rover = sk_rover->next ) { + PKT_secret_key *sk = sk_rover->sk; + int i = hash_for(sk); + + if( !hashs_seen[ i & 0xff ] ) { + s = digest_algo_to_string( i ); + if( s ) { + hashs_seen[ i & 0xff ] = 1; + if( any ) + iobuf_put(out, ',' ); + iobuf_writestr(out, s ); + any = 1; + } + } + } + assert(any); + iobuf_writestr(out, LF ); + } + + if( opt.not_dash_escaped ) + iobuf_writestr( out, + "NotDashEscaped: You need GnuPG to verify this message" LF ); + iobuf_writestr(out, LF ); + + textmd = md_open(0, 0); + for( sk_rover = sk_list; sk_rover; sk_rover = sk_rover->next ) { + PKT_secret_key *sk = sk_rover->sk; + md_enable(textmd, hash_for(sk)); + } + if ( DBG_HASHING ) + md_start_debug( textmd, "clearsign" ); + copy_clearsig_text( out, inp, textmd, !opt.not_dash_escaped, + opt.escape_from, (old_style && only_md5) ); + /* fixme: check for read errors */ + + /* now write the armor */ + afx.what = 2; + iobuf_push_filter( out, armor_filter, &afx ); + + /* write the signatures */ + rc=write_signature_packets (sk_list, out, textmd, 0x01, 0, duration, 'C'); + if( rc ) + goto leave; + + leave: + if( rc ) + iobuf_cancel(out); + else + iobuf_close(out); + iobuf_close(inp); + md_close( textmd ); + release_sk_list( sk_list ); + return rc; +} + +/* + * Sign and conventionally encrypt the given file. + * FIXME: Far too much code is duplicated - revamp the whole file. + */ +int +sign_symencrypt_file (const char *fname, STRLIST locusr) +{ + armor_filter_context_t afx; + progress_filter_context_t pfx; + compress_filter_context_t zfx; + md_filter_context_t mfx; + text_filter_context_t tfx; + cipher_filter_context_t cfx; + IOBUF inp = NULL, out = NULL; + PACKET pkt; + STRING2KEY *s2k = NULL; + int rc = 0; + SK_LIST sk_list = NULL; + SK_LIST sk_rover = NULL; + int algo; + u32 duration=0; + + memset( &afx, 0, sizeof afx); + memset( &zfx, 0, sizeof zfx); + memset( &mfx, 0, sizeof mfx); + memset( &tfx, 0, sizeof tfx); + memset( &cfx, 0, sizeof cfx); + init_packet( &pkt ); + + if(!opt.force_v3_sigs && !RFC1991) + { + if(opt.ask_sig_expire && !opt.batch) + duration=ask_expire_interval(1,opt.def_sig_expire); + else + duration=parse_expire_string(opt.def_sig_expire); + } + + rc = build_sk_list (locusr, &sk_list, 1, PUBKEY_USAGE_SIG); + if (rc) + goto leave; + + /* prepare iobufs */ + inp = iobuf_open(fname); + if (inp && is_secured_file (iobuf_get_fd (inp))) + { + iobuf_close (inp); + inp = NULL; + errno = EPERM; + } + if( !inp ) { + log_error(_("can't open `%s': %s\n"), + fname? fname: "[stdin]", strerror(errno) ); + rc = G10ERR_OPEN_FILE; + goto leave; + } + handle_progress (&pfx, inp, fname); + + /* prepare key */ + s2k = xmalloc_clear( sizeof *s2k ); + s2k->mode = RFC1991? 0:opt.s2k_mode; + s2k->hash_algo = S2K_DIGEST_ALGO; + + algo = default_cipher_algo(); + if (!opt.quiet || !opt.batch) + log_info (_("%s encryption will be used\n"), + cipher_algo_to_string(algo) ); + cfx.dek = passphrase_to_dek( NULL, 0, algo, s2k, 2, NULL, NULL); + + if (!cfx.dek || !cfx.dek->keylen) { + rc = G10ERR_PASSPHRASE; + log_error(_("error creating passphrase: %s\n"), g10_errstr(rc) ); + goto leave; + } + + /* We have no way to tell if the recipient can handle messages + with an MDC, so this defaults to no. Perhaps in a few years, + this can be defaulted to yes. Note that like regular + encrypting, --force-mdc overrides --disable-mdc. */ + if(opt.force_mdc) + cfx.dek->use_mdc=1; + + /* now create the outfile */ + rc = open_outfile (fname, opt.armor? 1:0, &out); + if (rc) + goto leave; + + /* prepare to calculate the MD over the input */ + if (opt.textmode) + iobuf_push_filter (inp, text_filter, &tfx); + mfx.md = md_open(0, 0); + if ( DBG_HASHING ) + md_start_debug (mfx.md, "symc-sign"); + + for (sk_rover = sk_list; sk_rover; sk_rover = sk_rover->next) { + PKT_secret_key *sk = sk_rover->sk; + md_enable (mfx.md, hash_for (sk)); + } + + iobuf_push_filter (inp, md_filter, &mfx); + + /* Push armor output filter */ + if (opt.armor) + iobuf_push_filter (out, armor_filter, &afx); + + /* Write the symmetric key packet */ + /*(current filters: armor)*/ + if (!RFC1991) { + PKT_symkey_enc *enc = xmalloc_clear( sizeof *enc ); + enc->version = 4; + enc->cipher_algo = cfx.dek->algo; + enc->s2k = *s2k; + pkt.pkttype = PKT_SYMKEY_ENC; + pkt.pkt.symkey_enc = enc; + if( (rc = build_packet( out, &pkt )) ) + log_error("build symkey packet failed: %s\n", g10_errstr(rc) ); + xfree(enc); + } + + /* Push the encryption filter */ + iobuf_push_filter( out, cipher_filter, &cfx ); + + /* Push the compress filter */ + if (default_compress_algo()) + push_compress_filter(out,&zfx,default_compress_algo()); + + /* Write the one-pass signature packets */ + /*(current filters: zip - encrypt - armor)*/ + if (!RFC1991) { + rc = write_onepass_sig_packets (sk_list, out, + opt.textmode? 0x01:0x00); + if (rc) + goto leave; + } + + write_status (STATUS_BEGIN_SIGNING); + + /* Pipe data through all filters; i.e. write the signed stuff */ + /*(current filters: zip - encrypt - armor)*/ + rc = write_plaintext_packet (out, inp, fname, opt.textmode ? 't':'b'); + if (rc) + goto leave; + + /* Write the signatures */ + /*(current filters: zip - encrypt - armor)*/ + rc = write_signature_packets (sk_list, out, mfx.md, + opt.textmode? 0x01 : 0x00, + 0, duration, 'S'); + if( rc ) + goto leave; + + + leave: + if( rc ) + iobuf_cancel(out); + else { + iobuf_close(out); + write_status( STATUS_END_ENCRYPTION ); + } + iobuf_close(inp); + release_sk_list( sk_list ); + md_close( mfx.md ); + xfree(cfx.dek); + xfree(s2k); + return rc; +} + + +/**************** + * Create a signature packet for the given public key certificate and + * the user id and return it in ret_sig. User signature class SIGCLASS + * user-id is not used (and may be NULL if sigclass is 0x20) If + * DIGEST_ALGO is 0 the function selects an appropriate one. + * SIGVERSION gives the minimal required signature packet version; + * this is needed so that special properties like local sign are not + * applied (actually: dropped) when a v3 key is used. TIMESTAMP is + * the timestamp to use for the signature. 0 means "now" */ +int +make_keysig_packet( PKT_signature **ret_sig, PKT_public_key *pk, + PKT_user_id *uid, PKT_public_key *subpk, + PKT_secret_key *sk, + int sigclass, int digest_algo, + int sigversion, u32 timestamp, u32 duration, + int (*mksubpkt)(PKT_signature *, void *), void *opaque + ) +{ + PKT_signature *sig; + int rc=0; + MD_HANDLE md; + + assert( (sigclass >= 0x10 && sigclass <= 0x13) || sigclass == 0x1F + || sigclass == 0x20 || sigclass == 0x18 || sigclass == 0x19 + || sigclass == 0x30 || sigclass == 0x28 ); + + if (opt.force_v4_certs) + sigversion = 4; + + if (sigversion < sk->version) + sigversion = sk->version; + + /* If you are making a signature on a v4 key using your v3 key, it + doesn't make sense to generate a v3 sig. After all, no v3-only + PGP implementation could understand the v4 key in the first + place. Note that this implies that a signature on an attribute + uid is usually going to be v4 as well, since they are not + generally found on v3 keys. */ + if (sigversion < pk->version) + sigversion = pk->version; + + if( !digest_algo ) + { + /* Basically, this means use SHA1 always unless it's a v3 RSA + key making a v3 cert (use MD5), or the user specified + something (use whatever they said), or it's DSA (use the + best match). They still can't pick an inappropriate hash + for DSA or the signature will fail. Note that this still + allows the caller of make_keysig_packet to override the + user setting if it must. */ + + if(opt.cert_digest_algo) + digest_algo=opt.cert_digest_algo; + else if(sk->pubkey_algo==PUBKEY_ALGO_RSA + && pk->version<4 && sigversion<4) + digest_algo = DIGEST_ALGO_MD5; + else if(sk->pubkey_algo==PUBKEY_ALGO_DSA) + digest_algo = match_dsa_hash(mpi_get_nbits(sk->skey[1])/8); + else + digest_algo = DIGEST_ALGO_SHA1; + } + + md = md_open( digest_algo, 0 ); + + /* hash the public key certificate */ + hash_public_key( md, pk ); + + if( sigclass == 0x18 || sigclass == 0x19 || sigclass == 0x28 ) + { + /* hash the subkey binding/backsig/revocation */ + hash_public_key( md, subpk ); + } + else if( sigclass != 0x1F && sigclass != 0x20 ) + { + /* hash the user id */ + hash_uid (md, sigversion, uid); + } + /* and make the signature packet */ + sig = xmalloc_clear( sizeof *sig ); + sig->version = sigversion; + sig->flags.exportable=1; + sig->flags.revocable=1; + keyid_from_sk( sk, sig->keyid ); + sig->pubkey_algo = sk->pubkey_algo; + sig->digest_algo = digest_algo; + if(timestamp) + sig->timestamp=timestamp; + else + sig->timestamp=make_timestamp(); + if(duration) + sig->expiredate=sig->timestamp+duration; + sig->sig_class = sigclass; + if( sig->version >= 4 ) + build_sig_subpkt_from_sig( sig ); + mk_notation_policy_etc( sig, pk, sk ); + + /* Crucial that the call to mksubpkt comes LAST before the calls + to finalize the sig as that makes it possible for the mksubpkt + function to get a reliable pointer to the subpacket area. */ + if( sig->version >= 4 && mksubpkt ) + rc = (*mksubpkt)( sig, opaque ); + + if( !rc ) { + hash_sigversion_to_magic (md, sig); + md_final(md); + + rc = complete_sig( sig, sk, md ); + } + + md_close( md ); + if( rc ) + free_seckey_enc( sig ); + else + *ret_sig = sig; + return rc; +} + + + +/**************** + * Create a new signature packet based on an existing one. + * Only user ID signatures are supported for now. + * TODO: Merge this with make_keysig_packet. + */ +int +update_keysig_packet( PKT_signature **ret_sig, + PKT_signature *orig_sig, + PKT_public_key *pk, + PKT_user_id *uid, + PKT_public_key *subpk, + PKT_secret_key *sk, + int (*mksubpkt)(PKT_signature *, void *), + void *opaque ) +{ + PKT_signature *sig; + int rc=0; + MD_HANDLE md; + + if ((!orig_sig || !pk || !sk) + || (orig_sig->sig_class >= 0x10 && orig_sig->sig_class <= 0x13 && !uid) + || (orig_sig->sig_class == 0x18 && !subpk)) + return G10ERR_GENERAL; + + md = md_open( orig_sig->digest_algo, 0 ); + + /* hash the public key certificate and the user id */ + hash_public_key( md, pk ); + + if( orig_sig->sig_class == 0x18 ) + hash_public_key( md, subpk ); + else + hash_uid (md, orig_sig->version, uid); + + /* create a new signature packet */ + sig = copy_signature (NULL, orig_sig); + + /* We need to create a new timestamp so that new sig expiration + calculations are done correctly... */ + sig->timestamp=make_timestamp(); + + /* ... but we won't make a timestamp earlier than the existing + one. */ + while(sig->timestamp<=orig_sig->timestamp) + { + sleep(1); + sig->timestamp=make_timestamp(); + } + + /* Note that already expired sigs will remain expired (with a + duration of 1) since build-packet.c:build_sig_subpkt_from_sig + detects this case. */ + + if( sig->version >= 4 ) + { + /* Put the updated timestamp into the sig. Note that this + will automagically lower any sig expiration dates to + correctly correspond to the differences in the timestamps + (i.e. the duration will shrink). */ + build_sig_subpkt_from_sig( sig ); + + if (mksubpkt) + rc = (*mksubpkt)(sig, opaque); + } + + if (!rc) { + hash_sigversion_to_magic (md, sig); + md_final(md); + + rc = complete_sig( sig, sk, md ); + } + + md_close (md); + if( rc ) + free_seckey_enc (sig); + else + *ret_sig = sig; + return rc; +} diff --git a/g10/signal.c b/g10/signal.c new file mode 100644 index 0000000..2e40af1 --- /dev/null +++ b/g10/signal.c @@ -0,0 +1,238 @@ +/* signal.c - signal handling + * Copyright (C) 1998, 1999, 2000, 2001, 2002, 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 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <signal.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <assert.h> +#ifdef HAVE_LIBREADLINE +#include <readline/readline.h> +#include <readline/history.h> +#endif + +#include "options.h" +#include "errors.h" +#include "memory.h" +#include "util.h" +#include "main.h" +#include "ttyio.h" + +#ifdef HAVE_DOSISH_SYSTEM +void init_signals(void) {} +void pause_on_sigusr(int which) {} +#else +static volatile int caught_fatal_sig = 0; +static volatile int caught_sigusr1 = 0; + +static void +init_one_signal (int sig, RETSIGTYPE (*handler)(int), int check_ign ) +{ +#if defined(HAVE_SIGACTION) && defined(HAVE_STRUCT_SIGACTION) + struct sigaction oact, nact; + + if (check_ign) { + /* we don't want to change an IGN handler */ + sigaction (sig, NULL, &oact ); + if (oact.sa_handler == SIG_IGN ) + return; + } + + nact.sa_handler = handler; + sigemptyset (&nact.sa_mask); + nact.sa_flags = 0; + sigaction ( sig, &nact, NULL); +#else + RETSIGTYPE (*ohandler)(int); + + ohandler = signal (sig, handler); + if (check_ign && ohandler == SIG_IGN) { + /* Change it back if it was already set to IGN */ + signal (sig, SIG_IGN); + } +#endif +} + +static RETSIGTYPE +got_fatal_signal( int sig ) +{ + const char *s; + + if( caught_fatal_sig ) + raise( sig ); + caught_fatal_sig = 1; + + secmem_term(); + +#ifdef HAVE_LIBREADLINE + rl_free_line_state (); + rl_cleanup_after_signal (); +#endif + + /* Better don't translate these messages. */ + write(2, "\n", 1 ); + s = log_get_name(); if( s ) write(2, s, strlen(s) ); + write(2, ": ", 2 ); + +#if HAVE_DECL_SYS_SIGLIST && defined(NSIG) + s = (sig >= 0 && sig < NSIG) ? sys_siglist[sig] : "?"; + write (2, s, strlen(s) ); +#else + write (2, "signal ", 7 ); + if (sig < 0 || sig >=100) + write (2, "?", 1); + else { + if (sig >= 10) + write (2, "0123456789"+(sig/10), 1 ); + write (2, "0123456789"+(sig%10), 1 ); + } +#endif + write(2, " caught ... exiting\n", 20 ); + + /* Reset action to default action and raise signal again. */ + init_one_signal (sig, SIG_DFL, 0); + remove_lockfiles (); +#ifdef __riscos__ + riscos_close_fds (); +#endif /* __riscos__ */ + raise( sig ); +} + + +static RETSIGTYPE +got_usr_signal( int sig ) +{ + caught_sigusr1 = 1; +} + + +void +init_signals() +{ + init_one_signal (SIGINT, got_fatal_signal, 1 ); + init_one_signal (SIGHUP, got_fatal_signal, 1 ); + init_one_signal (SIGTERM, got_fatal_signal, 1 ); + init_one_signal (SIGQUIT, got_fatal_signal, 1 ); + init_one_signal (SIGSEGV, got_fatal_signal, 1 ); + init_one_signal (SIGUSR1, got_usr_signal, 0 ); + init_one_signal (SIGPIPE, SIG_IGN, 0 ); +} + + +void +pause_on_sigusr( int which ) +{ +#if defined(HAVE_SIGPROCMASK) && defined(HAVE_SIGSET_T) + sigset_t mask, oldmask; + + assert( which == 1 ); + sigemptyset( &mask ); + sigaddset( &mask, SIGUSR1 ); + + sigprocmask( SIG_BLOCK, &mask, &oldmask ); + while( !caught_sigusr1 ) + sigsuspend( &oldmask ); + caught_sigusr1 = 0; + sigprocmask( SIG_UNBLOCK, &mask, NULL ); +#else + assert (which == 1); + sighold (SIGUSR1); + while (!caught_sigusr1) + sigpause(SIGUSR1); + caught_sigusr1 = 0; + sigrelse(SIGUSR1); +#endif /*! HAVE_SIGPROCMASK && HAVE_SIGSET_T */ +} + +/* Disabled - see comment in tdbio.c:tdbio_begin_transaction() */ +#if 0 +static void +do_block( int block ) +{ + static int is_blocked; +#if defined(HAVE_SIGPROCMASK) && defined(HAVE_SIGSET_T) + static sigset_t oldmask; + + if( block ) { + sigset_t newmask; + + if( is_blocked ) + log_bug("signals are already blocked\n"); + sigfillset( &newmask ); + sigprocmask( SIG_BLOCK, &newmask, &oldmask ); + is_blocked = 1; + } + else { + if( !is_blocked ) + log_bug("signals are not blocked\n"); + sigprocmask( SIG_SETMASK, &oldmask, NULL ); + is_blocked = 0; + } +#else /*! HAVE_SIGPROCMASK && HAVE_SIGSET_T */ + +#if defined(NSIG) +#define SIGSMAX (NSIG) +#elif defined(MAXSIG) +#define SIGSMAX (MAXSIG+1) +#else +#error "define SIGSMAX to the number of signals on your platform plus one" +#endif + + static void (*disposition[SIGSMAX])(int); + int sig; + + if( block ) { + if( is_blocked ) + log_bug("signals are already blocked\n"); + for (sig=1; sig < SIGSMAX; sig++) { + disposition[sig] = sigset (sig, SIG_HOLD); + } + is_blocked = 1; + } + else { + if( !is_blocked ) + log_bug("signals are not blocked\n"); + for (sig=1; sig < SIGSMAX; sig++) { + sigset (sig, disposition[sig]); + } + is_blocked = 0; + } +#endif /*! HAVE_SIGPROCMASK && HAVE_SIGSET_T */ +} + +void +block_all_signals() +{ + do_block(1); +} + +void +unblock_all_signals() +{ + do_block(0); +} +#endif + +#endif /* !HAVE_DOSISH_SYSTEM */ diff --git a/g10/skclist.c b/g10/skclist.c new file mode 100644 index 0000000..5e55e24 --- /dev/null +++ b/g10/skclist.c @@ -0,0 +1,223 @@ +/* skclist.c + * Copyright (C) 1998, 1999, 2000, 2001 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <assert.h> + +#include "options.h" +#include "packet.h" +#include "errors.h" +#include "keydb.h" +#include "memory.h" +#include "util.h" +#include "i18n.h" +#include "cipher.h" + + +void +release_sk_list( SK_LIST sk_list ) +{ + SK_LIST sk_rover; + + for( ; sk_list; sk_list = sk_rover ) { + sk_rover = sk_list->next; + free_secret_key( sk_list->sk ); + xfree( sk_list ); + } +} + + +/* Check that we are only using keys which don't have + * the string "(insecure!)" or "not secure" or "do not use" + * in one of the user ids + */ +static int +is_insecure( PKT_secret_key *sk ) +{ + u32 keyid[2]; + KBNODE node = NULL, u; + int insecure = 0; + + keyid_from_sk( sk, keyid ); + node = get_pubkeyblock( keyid ); + for ( u = node; u; u = u->next ) { + if ( u->pkt->pkttype == PKT_USER_ID ) { + PKT_user_id *id = u->pkt->pkt.user_id; + if ( id->attrib_data ) + continue; /* skip attribute packets */ + if ( strstr( id->name, "(insecure!)" ) + || strstr( id->name, "not secure" ) + || strstr( id->name, "do not use" ) + || strstr( id->name, "(INSECURE!)" ) ) { + insecure = 1; + break; + } + } + } + release_kbnode( node ); + + return insecure; +} + +static int +key_present_in_sk_list(SK_LIST sk_list, PKT_secret_key *sk) +{ + for (; sk_list; sk_list = sk_list->next) { + if ( !cmp_secret_keys(sk_list->sk, sk) ) + return 0; + } + return -1; +} + +static int +is_duplicated_entry (STRLIST list, STRLIST item) +{ + for(; list && list != item; list = list->next) { + if ( !strcmp (list->d, item->d) ) + return 1; + } + return 0; +} + + +int +build_sk_list( STRLIST locusr, SK_LIST *ret_sk_list, + int unlock, unsigned int use ) +{ + SK_LIST sk_list = NULL; + int rc; + + if( !locusr ) + { /* use the default one */ + PKT_secret_key *sk; + + sk = xmalloc_clear( sizeof *sk ); + sk->req_usage = use; + if( (rc = get_seckey_byname( sk, NULL, unlock )) ) { + free_secret_key( sk ); sk = NULL; + log_error("no default secret key: %s\n", g10_errstr(rc) ); + } + else if( !(rc=check_pubkey_algo2(sk->pubkey_algo, use)) ) + { + SK_LIST r; + + if( random_is_faked() && !is_insecure( sk ) ) + { + log_info(_("key is not flagged as insecure - " + "can't use it with the faked RNG!\n")); + free_secret_key( sk ); sk = NULL; + } + else + { + r = xmalloc( sizeof *r ); + r->sk = sk; sk = NULL; + r->next = sk_list; + r->mark = 0; + sk_list = r; + } + } + else + { + free_secret_key( sk ); sk = NULL; + log_error("invalid default secret key: %s\n", g10_errstr(rc) ); + } + } + else { + STRLIST locusr_orig = locusr; + for(; locusr; locusr = locusr->next ) { + PKT_secret_key *sk; + + rc = 0; + /* Do an early check agains duplicated entries. However this + * won't catch all duplicates because the user IDs may be + * specified in different ways. + */ + if ( is_duplicated_entry ( locusr_orig, locusr ) ) + { + log_error(_("skipped \"%s\": duplicated\n"), locusr->d ); + continue; + } + sk = xmalloc_clear( sizeof *sk ); + sk->req_usage = use; + if( (rc = get_seckey_byname( sk, locusr->d, 0 )) ) + { + free_secret_key( sk ); sk = NULL; + log_error(_("skipped \"%s\": %s\n"), + locusr->d, g10_errstr(rc) ); + } + else if ( key_present_in_sk_list(sk_list, sk) == 0) { + free_secret_key(sk); sk = NULL; + log_info(_("skipped: secret key already present\n")); + } + else if ( unlock && (rc = check_secret_key( sk, 0 )) ) + { + free_secret_key( sk ); sk = NULL; + log_error(_("skipped \"%s\": %s\n"), + locusr->d, g10_errstr(rc) ); + } + else if( !(rc=check_pubkey_algo2(sk->pubkey_algo, use)) ) { + SK_LIST r; + + if( sk->version == 4 && (use & PUBKEY_USAGE_SIG) + && sk->pubkey_algo == PUBKEY_ALGO_ELGAMAL_E ) + { + log_info(_("skipped \"%s\": %s\n"),locusr->d, + _("this is a PGP generated Elgamal key which" + " is not secure for signatures!")); + free_secret_key( sk ); sk = NULL; + } + else if( random_is_faked() && !is_insecure( sk ) ) { + log_info(_("key is not flagged as insecure - " + "can't use it with the faked RNG!\n")); + free_secret_key( sk ); sk = NULL; + } + else { + r = xmalloc( sizeof *r ); + r->sk = sk; sk = NULL; + r->next = sk_list; + r->mark = 0; + sk_list = r; + } + } + else { + free_secret_key( sk ); sk = NULL; + log_error("skipped \"%s\": %s\n", locusr->d, g10_errstr(rc) ); + } + } + } + + + if( !rc && !sk_list ) { + log_error("no valid signators\n"); + rc = G10ERR_NO_USER_ID; + } + + if( rc ) + release_sk_list( sk_list ); + else + *ret_sk_list = sk_list; + return rc; +} + diff --git a/g10/status.c b/g10/status.c new file mode 100644 index 0000000..2044820 --- /dev/null +++ b/g10/status.c @@ -0,0 +1,790 @@ +/* status.c + * Copyright (C) 1998, 1999, 2000, 2001, 2002, 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 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <signal.h> +#ifdef USE_SHM_COPROCESSING +#ifdef USE_CAPABILITIES +#include <sys/capability.h> +#endif +#ifdef HAVE_SYS_IPC_H +#include <sys/types.h> +#include <sys/ipc.h> +#endif +#ifdef HAVE_SYS_SHM_H +#include <sys/shm.h> +#endif +#if defined(HAVE_MLOCK) +#include <sys/mman.h> +#endif +#endif +#include "util.h" +#include "status.h" +#include "ttyio.h" +#include "options.h" +#include "main.h" +#include "i18n.h" +#include "cipher.h" /* for progress functions */ + +#define CONTROL_D ('D' - 'A' + 1) + + + +static FILE *statusfp; + +#ifdef USE_SHM_COPROCESSING + static int shm_id = -1; + static volatile char *shm_area; + static size_t shm_size; + static int shm_is_locked; +#endif /*USE_SHM_COPROCESSING*/ + + +static void +progress_cb ( void *ctx, int c ) +{ + char buf[50]; + + if ( c == '\n' ) + sprintf ( buf, "%.20s X 100 100", (char*)ctx ); + else + sprintf ( buf, "%.20s %c 0 0", (char*)ctx, c ); + write_status_text ( STATUS_PROGRESS, buf ); +} + +static const char * +get_status_string ( int no ) +{ + const char *s; + + switch( no ) + { + case STATUS_ENTER : s = "ENTER"; break; + case STATUS_LEAVE : s = "LEAVE"; break; + case STATUS_ABORT : s = "ABORT"; break; + case STATUS_NEWSIG : s = "NEWSIG"; break; + case STATUS_GOODSIG: s = "GOODSIG"; break; + case STATUS_KEYEXPIRED: s = "KEYEXPIRED"; break; + case STATUS_KEYREVOKED: s = "KEYREVOKED"; break; + case STATUS_BADSIG : s = "BADSIG"; break; + case STATUS_ERRSIG : s = "ERRSIG"; break; + case STATUS_BADARMOR : s = "BADARMOR"; break; + case STATUS_RSA_OR_IDEA : s= "RSA_OR_IDEA"; break; + case STATUS_TRUST_UNDEFINED: s = "TRUST_UNDEFINED"; break; + case STATUS_TRUST_NEVER : s = "TRUST_NEVER"; break; + case STATUS_TRUST_MARGINAL : s = "TRUST_MARGINAL"; break; + case STATUS_TRUST_FULLY : s = "TRUST_FULLY"; break; + case STATUS_TRUST_ULTIMATE : s = "TRUST_ULTIMATE"; break; + case STATUS_GET_BOOL : s = "GET_BOOL"; break; + case STATUS_GET_LINE : s = "GET_LINE"; break; + case STATUS_GET_HIDDEN : s = "GET_HIDDEN"; break; + case STATUS_GOT_IT : s = "GOT_IT"; break; + case STATUS_SHM_INFO : s = "SHM_INFO"; break; + case STATUS_SHM_GET : s = "SHM_GET"; break; + case STATUS_SHM_GET_BOOL : s = "SHM_GET_BOOL"; break; + case STATUS_SHM_GET_HIDDEN : s = "SHM_GET_HIDDEN"; break; + case STATUS_NEED_PASSPHRASE: s = "NEED_PASSPHRASE"; break; + case STATUS_VALIDSIG : s = "VALIDSIG"; break; + case STATUS_SIG_ID : s = "SIG_ID"; break; + case STATUS_ENC_TO : s = "ENC_TO"; break; + case STATUS_NODATA : s = "NODATA"; break; + case STATUS_BAD_PASSPHRASE : s = "BAD_PASSPHRASE"; break; + case STATUS_NO_PUBKEY : s = "NO_PUBKEY"; break; + case STATUS_NO_SECKEY : s = "NO_SECKEY"; break; + case STATUS_NEED_PASSPHRASE_SYM: s = "NEED_PASSPHRASE_SYM"; break; + case STATUS_NEED_PASSPHRASE_PIN: s = "NEED_PASSPHRASE_PIN"; break; + case STATUS_DECRYPTION_FAILED: s = "DECRYPTION_FAILED"; break; + case STATUS_DECRYPTION_OKAY: s = "DECRYPTION_OKAY"; break; + case STATUS_MISSING_PASSPHRASE: s = "MISSING_PASSPHRASE"; break; + case STATUS_GOOD_PASSPHRASE : s = "GOOD_PASSPHRASE"; break; + case STATUS_GOODMDC : s = "GOODMDC"; break; + case STATUS_BADMDC : s = "BADMDC"; break; + case STATUS_ERRMDC : s = "ERRMDC"; break; + case STATUS_IMPORTED : s = "IMPORTED"; break; + case STATUS_IMPORT_OK : s = "IMPORT_OK"; break; + case STATUS_IMPORT_CHECK : s = "IMPORT_CHECK"; break; + case STATUS_IMPORT_RES : s = "IMPORT_RES"; break; + case STATUS_FILE_START : s = "FILE_START"; break; + case STATUS_FILE_DONE : s = "FILE_DONE"; break; + case STATUS_FILE_ERROR : s = "FILE_ERROR"; break; + case STATUS_BEGIN_DECRYPTION:s = "BEGIN_DECRYPTION"; break; + case STATUS_END_DECRYPTION : s = "END_DECRYPTION"; break; + case STATUS_BEGIN_ENCRYPTION:s = "BEGIN_ENCRYPTION"; break; + case STATUS_END_ENCRYPTION : s = "END_ENCRYPTION"; break; + case STATUS_DELETE_PROBLEM : s = "DELETE_PROBLEM"; break; + case STATUS_PROGRESS : s = "PROGRESS"; break; + case STATUS_SIG_CREATED : s = "SIG_CREATED"; break; + case STATUS_SESSION_KEY : s = "SESSION_KEY"; break; + case STATUS_NOTATION_NAME : s = "NOTATION_NAME" ; break; + case STATUS_NOTATION_DATA : s = "NOTATION_DATA" ; break; + case STATUS_POLICY_URL : s = "POLICY_URL" ; break; + case STATUS_BEGIN_STREAM : s = "BEGIN_STREAM"; break; + case STATUS_END_STREAM : s = "END_STREAM"; break; + case STATUS_KEY_CREATED : s = "KEY_CREATED"; break; + case STATUS_KEY_NOT_CREATED: s = "KEY_NOT_CREATED"; break; + case STATUS_USERID_HINT : s = "USERID_HINT"; break; + case STATUS_UNEXPECTED : s = "UNEXPECTED"; break; + case STATUS_INV_RECP : s = "INV_RECP"; break; + case STATUS_NO_RECP : s = "NO_RECP"; break; + case STATUS_ALREADY_SIGNED : s = "ALREADY_SIGNED"; break; + case STATUS_SIGEXPIRED : s = "SIGEXPIRED deprecated-use-keyexpired-instead"; break; + case STATUS_EXPSIG : s = "EXPSIG"; break; + case STATUS_EXPKEYSIG : s = "EXPKEYSIG"; break; + case STATUS_REVKEYSIG : s = "REVKEYSIG"; break; + case STATUS_ATTRIBUTE : s = "ATTRIBUTE"; break; + case STATUS_CARDCTRL : s = "CARDCTRL"; break; + case STATUS_PLAINTEXT : s = "PLAINTEXT"; break; + case STATUS_PLAINTEXT_LENGTH:s = "PLAINTEXT_LENGTH"; break; + case STATUS_SIG_SUBPACKET : s = "SIG_SUBPACKET"; break; + case STATUS_SC_OP_SUCCESS : s = "SC_OP_SUCCESS"; break; + case STATUS_SC_OP_FAILURE : s = "SC_OP_FAILURE"; break; + case STATUS_BACKUP_KEY_CREATED:s="BACKUP_KEY_CREATED"; break; + case STATUS_PKA_TRUST_BAD : s = "PKA_TRUST_BAD"; break; + case STATUS_PKA_TRUST_GOOD : s = "PKA_TRUST_GOOD"; break; + case STATUS_BEGIN_SIGNING : s = "BEGIN_SIGNING"; break; + default: s = "?"; break; + } + return s; +} + + +/* Return true if the status message NO may currently be issued. We + need this to avoid syncronisation problem while auto retrieving a + key. There it may happen that a status NODATA is issued for a non + available key and the user may falsely interpret this has a missing + signature. */ +static int +status_currently_allowed (int no) +{ + if (!glo_ctrl.in_auto_key_retrieve) + return 1; /* Yes. */ + + /* We allow some statis anyway, so that import statistics are + correct and to avoid problems if the retriebval subsystem will + prompt the user. */ + switch (no) + { + case STATUS_GET_BOOL: + case STATUS_GET_LINE: + case STATUS_GET_HIDDEN: + case STATUS_GOT_IT: + case STATUS_IMPORTED: + case STATUS_IMPORT_OK: + case STATUS_IMPORT_CHECK: + case STATUS_IMPORT_RES: + return 1; /* Yes. */ + default: + break; + } + return 0; /* No. */ +} + + +void +set_status_fd ( int fd ) +{ + static int last_fd = -1; + + if ( fd != -1 && last_fd == fd ) + return; + + if ( statusfp && statusfp != stdout && statusfp != stderr ) + fclose (statusfp); + statusfp = NULL; + if ( fd == -1 ) + return; + + if( fd == 1 ) + statusfp = stdout; + else if( fd == 2 ) + statusfp = stderr; + else + statusfp = fdopen( fd, "w" ); + if( !statusfp ) { + log_fatal("can't open fd %d for status output: %s\n", + fd, strerror(errno)); + } + last_fd = fd; + register_primegen_progress ( progress_cb, "primegen" ); + register_pk_dsa_progress ( progress_cb, "pk_dsa" ); + register_pk_elg_progress ( progress_cb, "pk_elg" ); +} + +int +is_status_enabled() +{ + return !!statusfp; +} + +void +write_status ( int no ) +{ + write_status_text( no, NULL ); +} + +void +write_status_text ( int no, const char *text) +{ + if( !statusfp || !status_currently_allowed (no) ) + return; /* Not enabled or allowed. */ + + fputs ( "[GNUPG:] ", statusfp ); + fputs ( get_status_string (no), statusfp ); + if( text ) { + putc ( ' ', statusfp ); + for (; *text; text++) { + if (*text == '\n') + fputs ( "\\n", statusfp ); + else if (*text == '\r') + fputs ( "\\r", statusfp ); + else + putc ( *(const byte *)text, statusfp ); + } + } + putc ('\n',statusfp); + if ( fflush (statusfp) && opt.exit_on_status_write_error ) + g10_exit (0); +} + + +/* + * Write a status line with a buffer using %XX escapes. If WRAP is > + * 0 wrap the line after this length. If STRING is not NULL it will + * be prepended to the buffer, no escaping is done for string. + * A wrap of -1 forces spaces not to be encoded as %20. + */ +void +write_status_text_and_buffer ( int no, const char *string, + const char *buffer, size_t len, int wrap ) +{ + const char *s, *text; + int esc, first; + int lower_limit = ' '; + size_t n, count, dowrap; + + if( !statusfp || !status_currently_allowed (no) ) + return; /* Not enabled or allowed. */ + + if (wrap == -1) { + lower_limit--; + wrap = 0; + } + + text = get_status_string (no); + count = dowrap = first = 1; + do { + if (dowrap) { + fprintf (statusfp, "[GNUPG:] %s ", text ); + count = dowrap = 0; + if (first && string) { + fputs (string, statusfp); + count += strlen (string); + } + first = 0; + } + for (esc=0, s=buffer, n=len; n && !esc; s++, n-- ) { + if ( *s == '%' || *(const byte*)s <= lower_limit + || *(const byte*)s == 127 ) + esc = 1; + if ( wrap && ++count > wrap ) { + dowrap=1; + break; + } + } + if (esc) { + s--; n++; + } + if (s != buffer) + fwrite (buffer, s-buffer, 1, statusfp ); + if ( esc ) { + fprintf (statusfp, "%%%02X", *(const byte*)s ); + s++; n--; + } + buffer = s; + len = n; + if ( dowrap && len ) + putc ( '\n', statusfp ); + } while ( len ); + + putc ('\n',statusfp); + if ( fflush (statusfp) && opt.exit_on_status_write_error ) + g10_exit (0); +} + +void +write_status_buffer ( int no, const char *buffer, size_t len, int wrap ) +{ + write_status_text_and_buffer (no, NULL, buffer, len, wrap); +} + + +#ifdef USE_SHM_COPROCESSING + +#ifndef IPC_RMID_DEFERRED_RELEASE +static void +remove_shmid( void ) +{ + if( shm_id != -1 ) { + shmctl ( shm_id, IPC_RMID, 0); + shm_id = -1; + } +} +#endif + +void +init_shm_coprocessing ( ulong requested_shm_size, int lock_mem ) +{ + char buf[100]; + struct shmid_ds shmds; + +#ifndef IPC_RMID_DEFERRED_RELEASE + atexit( remove_shmid ); +#endif + requested_shm_size = (requested_shm_size + 4095) & ~4095; + if ( requested_shm_size > 2 * 4096 ) + log_fatal("too much shared memory requested; only 8k are allowed\n"); + shm_size = 4096 /* one page for us */ + requested_shm_size; + + shm_id = shmget( IPC_PRIVATE, shm_size, IPC_CREAT | 0700 ); + if ( shm_id == -1 ) + log_fatal("can't get %uk of shared memory: %s\n", + (unsigned)shm_size/1024, strerror(errno)); + +#if !defined(IPC_HAVE_SHM_LOCK) \ + && defined(HAVE_MLOCK) && !defined(HAVE_BROKEN_MLOCK) + /* part of the old code which uses mlock */ + shm_area = shmat( shm_id, 0, 0 ); + if ( shm_area == (char*)-1 ) + log_fatal("can't attach %uk shared memory: %s\n", + (unsigned)shm_size/1024, strerror(errno)); + log_debug("mapped %uk shared memory at %p, id=%d\n", + (unsigned)shm_size/1024, shm_area, shm_id ); + if( lock_mem ) { +#ifdef USE_CAPABILITIES + cap_set_proc( cap_from_text("cap_ipc_lock+ep") ); +#endif + /* (need the cast for Solaris with Sun's workshop compilers) */ + if ( mlock ( (char*)shm_area, shm_size) ) + log_info("locking shared memory %d failed: %s\n", + shm_id, strerror(errno)); + else + shm_is_locked = 1; +#ifdef USE_CAPABILITIES + cap_set_proc( cap_from_text("cap_ipc_lock+p") ); +#endif + } + +#ifdef IPC_RMID_DEFERRED_RELEASE + if( shmctl( shm_id, IPC_RMID, 0) ) + log_fatal("shmctl IPC_RMDID of %d failed: %s\n", + shm_id, strerror(errno)); +#endif + + if( shmctl( shm_id, IPC_STAT, &shmds ) ) + log_fatal("shmctl IPC_STAT of %d failed: %s\n", + shm_id, strerror(errno)); + if( shmds.shm_perm.uid != getuid() ) { + shmds.shm_perm.uid = getuid(); + if( shmctl( shm_id, IPC_SET, &shmds ) ) + log_fatal("shmctl IPC_SET of %d failed: %s\n", + shm_id, strerror(errno)); + } + +#else /* this is the new code which handles the changes in the SHM + * semantics introduced with Linux 2.4. The changes is that we + * now change the permissions and then attach to the memory. + */ + + if( lock_mem ) { +#ifdef USE_CAPABILITIES + cap_set_proc( cap_from_text("cap_ipc_lock+ep") ); +#endif +#ifdef IPC_HAVE_SHM_LOCK + if ( shmctl (shm_id, SHM_LOCK, 0) ) + log_info("locking shared memory %d failed: %s\n", + shm_id, strerror(errno)); + else + shm_is_locked = 1; +#else + log_info("Locking shared memory %d failed: No way to do it\n", shm_id ); +#endif +#ifdef USE_CAPABILITIES + cap_set_proc( cap_from_text("cap_ipc_lock+p") ); +#endif + } + + if( shmctl( shm_id, IPC_STAT, &shmds ) ) + log_fatal("shmctl IPC_STAT of %d failed: %s\n", + shm_id, strerror(errno)); + if( shmds.shm_perm.uid != getuid() ) { + shmds.shm_perm.uid = getuid(); + if( shmctl( shm_id, IPC_SET, &shmds ) ) + log_fatal("shmctl IPC_SET of %d failed: %s\n", + shm_id, strerror(errno)); + } + + shm_area = shmat( shm_id, 0, 0 ); + if ( shm_area == (char*)-1 ) + log_fatal("can't attach %uk shared memory: %s\n", + (unsigned)shm_size/1024, strerror(errno)); + log_debug("mapped %uk shared memory at %p, id=%d\n", + (unsigned)shm_size/1024, shm_area, shm_id ); + +#ifdef IPC_RMID_DEFERRED_RELEASE + if( shmctl( shm_id, IPC_RMID, 0) ) + log_fatal("shmctl IPC_RMDID of %d failed: %s\n", + shm_id, strerror(errno)); +#endif + +#endif + /* write info; Protocol version, id, size, locked size */ + sprintf( buf, "pv=1 pid=%d shmid=%d sz=%u lz=%u", (int)getpid(), + shm_id, (unsigned)shm_size, shm_is_locked? (unsigned)shm_size:0 ); + write_status_text( STATUS_SHM_INFO, buf ); +} + +/**************** + * Request a string from client + * If bool, returns static string on true (do not free) or NULL for false + */ +static char * +do_shm_get( const char *keyword, int hidden, int bool ) +{ + size_t n; + byte *p; + char *string; + + if( !shm_area ) + BUG(); + + shm_area[0] = 0; /* msb of length of control block */ + shm_area[1] = 32; /* and lsb */ + shm_area[2] = 1; /* indicate that we are waiting on a reply */ + shm_area[3] = 0; /* clear data available flag */ + + write_status_text( bool? STATUS_SHM_GET_BOOL : + hidden? STATUS_SHM_GET_HIDDEN : STATUS_SHM_GET, keyword ); + + do { + pause_on_sigusr(1); + if( shm_area[0] || shm_area[1] != 32 || shm_area[2] != 1 ) + log_fatal("client modified shm control block - abort\n"); + } while( !shm_area[3] ); + shm_area[2] = 0; /* reset request flag */ + p = (byte*)shm_area+32; + n = p[0] << 8 | p[1]; + p += 2; + if( n+32+2+1 > 4095 ) + log_fatal("client returns too large data (%u bytes)\n", (unsigned)n ); + + if( bool ) + return p[0]? "" : NULL; + + string = hidden? xmalloc_secure( n+1 ) : xmalloc( n+1 ); + memcpy(string, p, n ); + string[n] = 0; /* make sure it is a string */ + if( hidden ) /* invalidate the memory */ + memset( p, 0, n ); + + return string; +} + +#endif /* USE_SHM_COPROCESSING */ + +static int +myread(int fd, void *buf, size_t count) +{ + int rc; + do { + rc = read( fd, buf, count ); + } while ( rc == -1 && errno == EINTR ); + if ( !rc && count ) { + static int eof_emmited=0; + if ( eof_emmited < 3 ) { + *(char*)buf = CONTROL_D; + rc = 1; + eof_emmited++; + } + else { /* Ctrl-D not caught - do something reasonable */ +#ifdef HAVE_DOSISH_SYSTEM + raise (SIGINT); /* nothing to hangup under DOS */ +#else + raise (SIGHUP); /* no more input data */ +#endif + } + } + return rc; +} + + + +/**************** + * Request a string from the client over the command-fd + * If bool, returns static string on true (do not free) or NULL for false + */ +static char * +do_get_from_fd( const char *keyword, int hidden, int bool ) +{ + int i, len; + char *string; + + if(statusfp!=stdout) + fflush(stdout); + + write_status_text( bool? STATUS_GET_BOOL : + hidden? STATUS_GET_HIDDEN : STATUS_GET_LINE, keyword ); + + for( string = NULL, i = len = 200; ; i++ ) { + if( i >= len-1 ) { + char *save = string; + len += 100; + string = hidden? xmalloc_secure ( len ) : xmalloc ( len ); + if( save ) + memcpy(string, save, i ); + else + i=0; + } + /* Hmmm: why not use our read_line function here */ + if( myread( opt.command_fd, string+i, 1) != 1 || string[i] == '\n' ) + break; + else if ( string[i] == CONTROL_D ) { + /* found ETX - cancel the line and return a sole ETX */ + string[0] = CONTROL_D; + i=1; + break; + } + } + string[i] = 0; + + write_status( STATUS_GOT_IT ); + + if( bool ) /* Fixme: is this correct??? */ + return (string[0] == 'Y' || string[0] == 'y') ? "" : NULL; + + return string; +} + + + +int +cpr_enabled() +{ + if( opt.command_fd != -1 ) + return 1; +#ifdef USE_SHM_COPROCESSING + if( opt.shm_coprocess ) + return 1; +#endif + return 0; +} + +char * +cpr_get_no_help( const char *keyword, const char *prompt ) +{ + char *p; + + if( opt.command_fd != -1 ) + return do_get_from_fd ( keyword, 0, 0 ); +#ifdef USE_SHM_COPROCESSING + if( opt.shm_coprocess ) + return do_shm_get( keyword, 0, 0 ); +#endif + for(;;) { + p = tty_get( prompt ); + return p; + } +} + +char * +cpr_get( const char *keyword, const char *prompt ) +{ + char *p; + + if( opt.command_fd != -1 ) + return do_get_from_fd ( keyword, 0, 0 ); +#ifdef USE_SHM_COPROCESSING + if( opt.shm_coprocess ) + return do_shm_get( keyword, 0, 0 ); +#endif + for(;;) { + p = tty_get( prompt ); + if( *p=='?' && !p[1] && !(keyword && !*keyword)) { + xfree(p); + display_online_help( keyword ); + } + else + return p; + } +} + + +char * +cpr_get_utf8( const char *keyword, const char *prompt ) +{ + char *p; + p = cpr_get( keyword, prompt ); + if( p ) { + char *utf8 = native_to_utf8( p ); + xfree( p ); + p = utf8; + } + return p; +} + +char * +cpr_get_hidden( const char *keyword, const char *prompt ) +{ + char *p; + + if( opt.command_fd != -1 ) + return do_get_from_fd ( keyword, 1, 0 ); +#ifdef USE_SHM_COPROCESSING + if( opt.shm_coprocess ) + return do_shm_get( keyword, 1, 0 ); +#endif + for(;;) { + p = tty_get_hidden( prompt ); + if( *p == '?' && !p[1] ) { + xfree(p); + display_online_help( keyword ); + } + else + return p; + } +} + +void +cpr_kill_prompt(void) +{ + if( opt.command_fd != -1 ) + return; +#ifdef USE_SHM_COPROCESSING + if( opt.shm_coprocess ) + return; +#endif + tty_kill_prompt(); + return; +} + +int +cpr_get_answer_is_yes( const char *keyword, const char *prompt ) +{ + int yes; + char *p; + + if( opt.command_fd != -1 ) + return !!do_get_from_fd ( keyword, 0, 1 ); +#ifdef USE_SHM_COPROCESSING + if( opt.shm_coprocess ) + return !!do_shm_get( keyword, 0, 1 ); +#endif + for(;;) { + p = tty_get( prompt ); + trim_spaces(p); /* it is okay to do this here */ + if( *p == '?' && !p[1] ) { + xfree(p); + display_online_help( keyword ); + } + else { + tty_kill_prompt(); + yes = answer_is_yes(p); + xfree(p); + return yes; + } + } +} + +int +cpr_get_answer_yes_no_quit( const char *keyword, const char *prompt ) +{ + int yes; + char *p; + + if( opt.command_fd != -1 ) + return !!do_get_from_fd ( keyword, 0, 1 ); +#ifdef USE_SHM_COPROCESSING + if( opt.shm_coprocess ) + return !!do_shm_get( keyword, 0, 1 ); +#endif + for(;;) { + p = tty_get( prompt ); + trim_spaces(p); /* it is okay to do this here */ + if( *p == '?' && !p[1] ) { + xfree(p); + display_online_help( keyword ); + } + else { + tty_kill_prompt(); + yes = answer_is_yes_no_quit(p); + xfree(p); + return yes; + } + } +} + + +int +cpr_get_answer_okay_cancel (const char *keyword, + const char *prompt, + int def_answer) +{ + int yes; + char *answer = NULL; + char *p; + + if( opt.command_fd != -1 ) + answer = do_get_from_fd ( keyword, 0, 0 ); +#ifdef USE_SHM_COPROCESSING + else if( opt.shm_coprocess ) + answer = do_shm_get( keyword, 0, 0 ); +#endif + + if (answer) + { + yes = answer_is_okay_cancel (answer, def_answer); + xfree (answer); + return yes; + } + + for(;;) + { + p = tty_get( prompt ); + trim_spaces(p); /* it is okay to do this here */ + if (*p == '?' && !p[1]) + { + xfree(p); + display_online_help (keyword); + } + else + { + tty_kill_prompt(); + yes = answer_is_okay_cancel (p, def_answer); + xfree(p); + return yes; + } + } +} diff --git a/g10/status.h b/g10/status.h new file mode 100644 index 0000000..bc7271d --- /dev/null +++ b/g10/status.h @@ -0,0 +1,150 @@ +/* status.h + * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, + * 2004 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ +#ifndef G10_STATUS_H +#define G10_STATUS_H + +#define STATUS_ENTER 1 +#define STATUS_LEAVE 2 +#define STATUS_ABORT 3 + +#define STATUS_GOODSIG 4 +#define STATUS_BADSIG 5 +#define STATUS_ERRSIG 6 + +#define STATUS_BADARMOR 7 + +#define STATUS_RSA_OR_IDEA 8 +#define STATUS_KEYEXPIRED 9 +#define STATUS_KEYREVOKED 10 + +#define STATUS_TRUST_UNDEFINED 11 +#define STATUS_TRUST_NEVER 12 +#define STATUS_TRUST_MARGINAL 13 +#define STATUS_TRUST_FULLY 14 +#define STATUS_TRUST_ULTIMATE 15 + +#define STATUS_SHM_INFO 16 +#define STATUS_SHM_GET 17 +#define STATUS_SHM_GET_BOOL 18 +#define STATUS_SHM_GET_HIDDEN 19 + +#define STATUS_NEED_PASSPHRASE 20 +#define STATUS_VALIDSIG 21 +#define STATUS_SIG_ID 22 +#define STATUS_ENC_TO 23 +#define STATUS_NODATA 24 +#define STATUS_BAD_PASSPHRASE 25 +#define STATUS_NO_PUBKEY 26 +#define STATUS_NO_SECKEY 27 +#define STATUS_NEED_PASSPHRASE_SYM 28 +#define STATUS_DECRYPTION_FAILED 29 +#define STATUS_DECRYPTION_OKAY 30 +#define STATUS_MISSING_PASSPHRASE 31 +#define STATUS_GOOD_PASSPHRASE 32 +#define STATUS_GOODMDC 33 +#define STATUS_BADMDC 34 +#define STATUS_ERRMDC 35 +#define STATUS_IMPORTED 36 +#define STATUS_IMPORT_RES 37 +#define STATUS_FILE_START 38 +#define STATUS_FILE_DONE 39 +#define STATUS_FILE_ERROR 40 + +#define STATUS_BEGIN_DECRYPTION 41 +#define STATUS_END_DECRYPTION 42 +#define STATUS_BEGIN_ENCRYPTION 43 +#define STATUS_END_ENCRYPTION 44 + +#define STATUS_DELETE_PROBLEM 45 +#define STATUS_GET_BOOL 46 +#define STATUS_GET_LINE 47 +#define STATUS_GET_HIDDEN 48 +#define STATUS_GOT_IT 49 +#define STATUS_PROGRESS 50 +#define STATUS_SIG_CREATED 51 +#define STATUS_SESSION_KEY 52 +#define STATUS_NOTATION_NAME 53 +#define STATUS_NOTATION_DATA 54 +#define STATUS_POLICY_URL 55 +#define STATUS_BEGIN_STREAM 56 +#define STATUS_END_STREAM 57 +#define STATUS_KEY_CREATED 58 +#define STATUS_USERID_HINT 59 +#define STATUS_UNEXPECTED 60 +#define STATUS_INV_RECP 61 +#define STATUS_NO_RECP 62 +#define STATUS_ALREADY_SIGNED 63 +#define STATUS_SIGEXPIRED 64 +#define STATUS_EXPSIG 65 +#define STATUS_EXPKEYSIG 66 +#define STATUS_ATTRIBUTE 67 +#define STATUS_IMPORT_OK 68 +#define STATUS_IMPORT_CHECK 69 +#define STATUS_REVKEYSIG 70 +#define STATUS_CARDCTRL 71 +#define STATUS_NEWSIG 72 +#define STATUS_PLAINTEXT 73 +#define STATUS_PLAINTEXT_LENGTH 74 +#define STATUS_KEY_NOT_CREATED 75 +#define STATUS_NEED_PASSPHRASE_PIN 76 +#define STATUS_SIG_SUBPACKET 77 + +/* Extra status codes for certain smartcard operations. Primary + useful to double check that change PIN worked as expected. */ +#define STATUS_SC_OP_FAILURE 79 +#define STATUS_SC_OP_SUCCESS 80 + +#define STATUS_BACKUP_KEY_CREATED 81 + +#define STATUS_PKA_TRUST_BAD 82 +#define STATUS_PKA_TRUST_GOOD 83 + +#define STATUS_BEGIN_SIGNING 84 + + +/*-- status.c --*/ +void set_status_fd ( int fd ); +int is_status_enabled ( void ); +void write_status ( int no ); +void write_status_text ( int no, const char *text ); +void write_status_buffer ( int no, + const char *buffer, size_t len, int wrap ); +void write_status_text_and_buffer ( int no, const char *text, + const char *buffer, size_t len, int wrap ); + +#ifdef USE_SHM_COPROCESSING + void init_shm_coprocessing ( ulong requested_shm_size, int lock_mem ); +#endif /*USE_SHM_COPROCESSING*/ + +int cpr_enabled(void); +char *cpr_get( const char *keyword, const char *prompt ); +char *cpr_get_no_help( const char *keyword, const char *prompt ); +char *cpr_get_utf8( const char *keyword, const char *prompt ); +char *cpr_get_hidden( const char *keyword, const char *prompt ); +void cpr_kill_prompt(void); +int cpr_get_answer_is_yes( const char *keyword, const char *prompt ); +int cpr_get_answer_yes_no_quit( const char *keyword, const char *prompt ); +int cpr_get_answer_okay_cancel (const char *keyword, + const char *prompt, + int def_answer); + +#endif /*G10_STATUS_H*/ diff --git a/g10/tdbdump.c b/g10/tdbdump.c new file mode 100644 index 0000000..09ce119 --- /dev/null +++ b/g10/tdbdump.c @@ -0,0 +1,233 @@ +/* tdbdump.c + * Copyright (C) 1998, 1999, 2000, 2001 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <ctype.h> +#include <assert.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> + +#include "errors.h" +#include "iobuf.h" +#include "keydb.h" +#include "memory.h" +#include "util.h" +#include "trustdb.h" +#include "options.h" +#include "packet.h" +#include "main.h" +#include "i18n.h" +#include "tdbio.h" + + +#define HEXTOBIN(x) ( (x) >= '0' && (x) <= '9' ? ((x)-'0') : \ + (x) >= 'A' && (x) <= 'F' ? ((x)-'A'+10) : ((x)-'a'+10)) + + +/**************** + * Wirte a record but die on error + */ +static void +write_record( TRUSTREC *rec ) +{ + int rc = tdbio_write_record( rec ); + if( !rc ) + return; + log_error(_("trust record %lu, type %d: write failed: %s\n"), + rec->recnum, rec->rectype, g10_errstr(rc) ); + tdbio_invalid(); +} + + +/**************** + * Dump the entire trustdb or only the entries of one key. + */ +void +list_trustdb( const char *username ) +{ + TRUSTREC rec; + + init_trustdb(); + /* for now we ignore the user ID */ + if (1) { + ulong recnum; + int i; + + printf("TrustDB: %s\n", tdbio_get_dbname() ); + for(i=9+strlen(tdbio_get_dbname()); i > 0; i-- ) + putchar('-'); + putchar('\n'); + for(recnum=0; !tdbio_read_record( recnum, &rec, 0); recnum++ ) + tdbio_dump_record( &rec, stdout ); + } +} + + + + + +/**************** + * Print a list of all defined owner trust value. + */ +void +export_ownertrust() +{ + TRUSTREC rec; + ulong recnum; + int i; + byte *p; + + init_trustdb(); + printf(_("# List of assigned trustvalues, created %s\n" + "# (Use \"gpg --import-ownertrust\" to restore them)\n"), + asctimestamp( make_timestamp() ) ); + for(recnum=0; !tdbio_read_record( recnum, &rec, 0); recnum++ ) { + if( rec.rectype == RECTYPE_TRUST ) { + if( !rec.r.trust.ownertrust ) + continue; + p = rec.r.trust.fingerprint; + for(i=0; i < 20; i++, p++ ) + printf("%02X", *p ); + printf(":%u:\n", (unsigned int)rec.r.trust.ownertrust ); + } + } +} + + +void +import_ownertrust( const char *fname ) +{ + FILE *fp; + int is_stdin=0; + char line[256]; + char *p; + size_t n, fprlen; + unsigned int otrust; + byte fpr[20]; + int any = 0; + int rc; + + init_trustdb(); + if( iobuf_is_pipe_filename (fname) ) { + fp = stdin; + fname = "[stdin]"; + is_stdin = 1; + } + else if( !(fp = fopen( fname, "r" )) ) { + log_error ( _("can't open `%s': %s\n"), fname, strerror(errno) ); + return; + } + + if (is_secured_file (fileno (fp))) + { + fclose (fp); + errno = EPERM; + log_error (_("can't open `%s': %s\n"), fname, strerror(errno) ); + return; + } + + while( fgets( line, DIM(line)-1, fp ) ) { + TRUSTREC rec; + + if( !*line || *line == '#' ) + continue; + n = strlen(line); + if( line[n-1] != '\n' ) { + log_error (_("error in `%s': %s\n"), fname, _("line too long") ); + /* ... or last line does not have a LF */ + break; /* can't continue */ + } + for(p = line; *p && *p != ':' ; p++ ) + if( !hexdigitp(p) ) + break; + if( *p != ':' ) { + log_error (_("error in `%s': %s\n"), fname, _("colon missing") ); + continue; + } + fprlen = p - line; + if( fprlen != 32 && fprlen != 40 ) { + log_error (_("error in `%s': %s\n"), + fname, _("invalid fingerprint") ); + continue; + } + if( sscanf(p, ":%u:", &otrust ) != 1 ) { + log_error (_("error in `%s': %s\n"), + fname, _("ownertrust value missing")); + continue; + } + if( !otrust ) + continue; /* no otrust defined - no need to update or insert */ + /* convert the ascii fingerprint to binary */ + for(p=line, fprlen=0; fprlen < 20 && *p != ':'; p += 2 ) + fpr[fprlen++] = HEXTOBIN(p[0]) * 16 + HEXTOBIN(p[1]); + while (fprlen < 20) + fpr[fprlen++] = 0; + + rc = tdbio_search_trust_byfpr (fpr, &rec); + if( !rc ) { /* found: update */ + if (rec.r.trust.ownertrust != otrust) + { + if( rec.r.trust.ownertrust ) + log_info("changing ownertrust from %u to %u\n", + rec.r.trust.ownertrust, otrust ); + else + log_info("setting ownertrust to %u\n", otrust ); + rec.r.trust.ownertrust = otrust; + write_record (&rec ); + any = 1; + } + } + else if( rc == -1 ) { /* not found: insert */ + log_info("inserting ownertrust of %u\n", otrust ); + memset (&rec, 0, sizeof rec); + rec.recnum = tdbio_new_recnum (); + rec.rectype = RECTYPE_TRUST; + memcpy (rec.r.trust.fingerprint, fpr, 20); + rec.r.trust.ownertrust = otrust; + write_record (&rec ); + any = 1; + } + else /* error */ + log_error (_("error finding trust record in `%s': %s\n"), + fname, g10_errstr(rc)); + } + if( ferror(fp) ) + log_error ( _("read error in `%s': %s\n"), fname, strerror(errno) ); + if( !is_stdin ) + fclose(fp); + + if (any) + { + revalidation_mark (); + rc = tdbio_sync (); + if (rc) + log_error (_("trustdb: sync failed: %s\n"), g10_errstr(rc) ); + } + +} + + diff --git a/g10/tdbio.c b/g10/tdbio.c new file mode 100644 index 0000000..fbd569d --- /dev/null +++ b/g10/tdbio.c @@ -0,0 +1,1633 @@ +/* tdbio.c + * Copyright (C) 1998, 1999, 2000, 2001, 2002 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <assert.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> + +#include "errors.h" +#include "iobuf.h" +#include "memory.h" +#include "util.h" +#include "options.h" +#include "main.h" +#include "i18n.h" +#include "trustdb.h" +#include "tdbio.h" + +#if defined(HAVE_DOSISH_SYSTEM) +#define ftruncate chsize +#endif + +#if defined(HAVE_DOSISH_SYSTEM) || defined(__CYGWIN__) +#define MY_O_BINARY O_BINARY +#else +#define MY_O_BINARY 0 +#endif + + +/**************** + * Yes, this is a very simple implementation. We should really + * use a page aligned buffer and read complete pages. + * To implement a simple trannsaction system, this is sufficient. + */ +typedef struct cache_ctrl_struct *CACHE_CTRL; +struct cache_ctrl_struct { + CACHE_CTRL next; + struct { + unsigned used:1; + unsigned dirty:1; + } flags; + ulong recno; + char data[TRUST_RECORD_LEN]; +}; + +#define MAX_CACHE_ENTRIES_SOFT 200 /* may be increased while in a */ +#define MAX_CACHE_ENTRIES_HARD 10000 /* transaction to this one */ +static CACHE_CTRL cache_list; +static int cache_entries; +static int cache_is_dirty; + +/* a type used to pass infomation to cmp_krec_fpr */ +struct cmp_krec_fpr_struct { + int pubkey_algo; + const char *fpr; + int fprlen; +}; + +/* a type used to pass infomation to cmp_[s]dir */ +struct cmp_xdir_struct { + int pubkey_algo; + u32 keyid[2]; +}; + + +static char *db_name; +static DOTLOCK lockhandle; +static int is_locked; +static int db_fd = -1; +static int in_transaction; + +static void open_db(void); +static void migrate_from_v2 (void); + + + +/************************************* + ************* record cache ********** + *************************************/ + +/**************** + * Get the data from therecord cache and return a + * pointer into that cache. Caller should copy + * the return data. NULL is returned on a cache miss. + */ +static const char * +get_record_from_cache( ulong recno ) +{ + CACHE_CTRL r; + + for( r = cache_list; r; r = r->next ) { + if( r->flags.used && r->recno == recno ) + return r->data; + } + return NULL; +} + + +static int +write_cache_item( CACHE_CTRL r ) +{ + int n; + + if( lseek( db_fd, r->recno * TRUST_RECORD_LEN, SEEK_SET ) == -1 ) { + log_error(_("trustdb rec %lu: lseek failed: %s\n"), + r->recno, strerror(errno) ); + return G10ERR_WRITE_FILE; + } + n = write( db_fd, r->data, TRUST_RECORD_LEN); + if( n != TRUST_RECORD_LEN ) { + log_error(_("trustdb rec %lu: write failed (n=%d): %s\n"), + r->recno, n, strerror(errno) ); + return G10ERR_WRITE_FILE; + } + r->flags.dirty = 0; + return 0; +} + +/**************** + * Put data into the cache. This function may flush the + * some cache entries if there is not enough space available. + */ +int +put_record_into_cache( ulong recno, const char *data ) +{ + CACHE_CTRL r, unused; + int dirty_count = 0; + int clean_count = 0; + + /* see whether we already cached this one */ + for( unused = NULL, r = cache_list; r; r = r->next ) { + if( !r->flags.used ) { + if( !unused ) + unused = r; + } + else if( r->recno == recno ) { + if( !r->flags.dirty ) { + /* Hmmm: should we use a a copy and compare? */ + if( memcmp(r->data, data, TRUST_RECORD_LEN ) ) { + r->flags.dirty = 1; + cache_is_dirty = 1; + } + } + memcpy( r->data, data, TRUST_RECORD_LEN ); + return 0; + } + if( r->flags.used ) { + if( r->flags.dirty ) + dirty_count++; + else + clean_count++; + } + } + /* not in the cache: add a new entry */ + if( unused ) { /* reuse this entry */ + r = unused; + r->flags.used = 1; + r->recno = recno; + memcpy( r->data, data, TRUST_RECORD_LEN ); + r->flags.dirty = 1; + cache_is_dirty = 1; + cache_entries++; + return 0; + } + /* see whether we reached the limit */ + if( cache_entries < MAX_CACHE_ENTRIES_SOFT ) { /* no */ + r = xmalloc( sizeof *r ); + r->flags.used = 1; + r->recno = recno; + memcpy( r->data, data, TRUST_RECORD_LEN ); + r->flags.dirty = 1; + r->next = cache_list; + cache_list = r; + cache_is_dirty = 1; + cache_entries++; + return 0; + } + /* cache is full: discard some clean entries */ + if( clean_count ) { + int n = clean_count / 3; /* discard a third of the clean entries */ + if( !n ) + n = 1; + for( unused = NULL, r = cache_list; r; r = r->next ) { + if( r->flags.used && !r->flags.dirty ) { + if( !unused ) + unused = r; + r->flags.used = 0; + cache_entries--; + if( !--n ) + break; + } + } + assert( unused ); + r = unused; + r->flags.used = 1; + r->recno = recno; + memcpy( r->data, data, TRUST_RECORD_LEN ); + r->flags.dirty = 1; + cache_is_dirty = 1; + cache_entries++; + return 0; + } + /* no clean entries: have to flush some dirty entries */ + if( in_transaction ) { + /* but we can't do this while in a transaction + * we increase the cache size instead */ + if( cache_entries < MAX_CACHE_ENTRIES_HARD ) { /* no */ + if( opt.debug && !(cache_entries % 100) ) + log_debug("increasing tdbio cache size\n"); + r = xmalloc( sizeof *r ); + r->flags.used = 1; + r->recno = recno; + memcpy( r->data, data, TRUST_RECORD_LEN ); + r->flags.dirty = 1; + r->next = cache_list; + cache_list = r; + cache_is_dirty = 1; + cache_entries++; + return 0; + } + log_info(_("trustdb transaction too large\n")); + return G10ERR_RESOURCE_LIMIT; + } + if( dirty_count ) { + int n = dirty_count / 5; /* discard some dirty entries */ + if( !n ) + n = 1; + if( !is_locked ) { + if( make_dotlock( lockhandle, -1 ) ) + log_fatal("can't acquire lock - giving up\n"); + else + is_locked = 1; + } + for( unused = NULL, r = cache_list; r; r = r->next ) { + if( r->flags.used && r->flags.dirty ) { + int rc = write_cache_item( r ); + if( rc ) + return rc; + if( !unused ) + unused = r; + r->flags.used = 0; + cache_entries--; + if( !--n ) + break; + } + } + if( !opt.lock_once ) { + if( !release_dotlock( lockhandle ) ) + is_locked = 0; + } + assert( unused ); + r = unused; + r->flags.used = 1; + r->recno = recno; + memcpy( r->data, data, TRUST_RECORD_LEN ); + r->flags.dirty = 1; + cache_is_dirty = 1; + cache_entries++; + return 0; + } + BUG(); +} + + +int +tdbio_is_dirty() +{ + return cache_is_dirty; +} + + +/**************** + * Flush the cache. This cannot be used while in a transaction. + */ +int +tdbio_sync() +{ + CACHE_CTRL r; + int did_lock = 0; + + if( db_fd == -1 ) + open_db(); + if( in_transaction ) + log_bug("tdbio: syncing while in transaction\n"); + + if( !cache_is_dirty ) + return 0; + + if( !is_locked ) { + if( make_dotlock( lockhandle, -1 ) ) + log_fatal("can't acquire lock - giving up\n"); + else + is_locked = 1; + did_lock = 1; + } + for( r = cache_list; r; r = r->next ) { + if( r->flags.used && r->flags.dirty ) { + int rc = write_cache_item( r ); + if( rc ) + return rc; + } + } + cache_is_dirty = 0; + if( did_lock && !opt.lock_once ) { + if( !release_dotlock( lockhandle ) ) + is_locked = 0; + } + + return 0; +} + +#if 0 +/* The transaction code is disabled in the 1.2.x branch, as it is not + yet used. It will be enabled in 1.3.x. */ + +/**************** + * Simple transactions system: + * Everything between begin_transaction and end/cancel_transaction + * is not immediatly written but at the time of end_transaction. + * + */ +int +tdbio_begin_transaction() +{ + int rc; + + if( in_transaction ) + log_bug("tdbio: nested transactions\n"); + /* flush everything out */ + rc = tdbio_sync(); + if( rc ) + return rc; + in_transaction = 1; + return 0; +} + +int +tdbio_end_transaction() +{ + int rc; + + if( !in_transaction ) + log_bug("tdbio: no active transaction\n"); + if( !is_locked ) { + if( make_dotlock( lockhandle, -1 ) ) + log_fatal("can't acquire lock - giving up\n"); + else + is_locked = 1; + } + block_all_signals(); + in_transaction = 0; + rc = tdbio_sync(); + unblock_all_signals(); + if( !opt.lock_once ) { + if( !release_dotlock( lockhandle ) ) + is_locked = 0; + } + return rc; +} + +int +tdbio_cancel_transaction() +{ + CACHE_CTRL r; + + if( !in_transaction ) + log_bug("tdbio: no active transaction\n"); + + /* remove all dirty marked entries, so that the original ones + * are read back the next time */ + if( cache_is_dirty ) { + for( r = cache_list; r; r = r->next ) { + if( r->flags.used && r->flags.dirty ) { + r->flags.used = 0; + cache_entries--; + } + } + cache_is_dirty = 0; + } + + in_transaction = 0; + return 0; +} +#endif + + +/******************************************************** + **************** cached I/O functions ****************** + ********************************************************/ + +static void +cleanup(void) +{ + if( is_locked ) { + if( !release_dotlock(lockhandle) ) + is_locked = 0; + } +} + +/* Caller must sync */ +int +tdbio_update_version_record (void) +{ + TRUSTREC rec; + int rc; + + memset( &rec, 0, sizeof rec ); + + rc=tdbio_read_record( 0, &rec, RECTYPE_VER); + if(rc==0) + { + rec.r.ver.created = make_timestamp(); + rec.r.ver.marginals = opt.marginals_needed; + rec.r.ver.completes = opt.completes_needed; + rec.r.ver.cert_depth = opt.max_cert_depth; + rec.r.ver.trust_model = opt.trust_model; + rc=tdbio_write_record(&rec); + } + + return rc; +} + +static int +create_version_record (void) +{ + TRUSTREC rec; + int rc; + + memset( &rec, 0, sizeof rec ); + rec.r.ver.version = 3; + rec.r.ver.created = make_timestamp(); + rec.r.ver.marginals = opt.marginals_needed; + rec.r.ver.completes = opt.completes_needed; + rec.r.ver.cert_depth = opt.max_cert_depth; + if(opt.trust_model==TM_PGP || opt.trust_model==TM_CLASSIC) + rec.r.ver.trust_model = opt.trust_model; + else + rec.r.ver.trust_model = TM_PGP; + rec.rectype = RECTYPE_VER; + rec.recnum = 0; + rc = tdbio_write_record( &rec ); + if( !rc ) + tdbio_sync(); + return rc; +} + + + +int +tdbio_set_dbname( const char *new_dbname, int create ) +{ + char *fname; + static int initialized = 0; + + if( !initialized ) { + atexit( cleanup ); + initialized = 1; + } + + if(new_dbname==NULL) + fname=make_filename(opt.homedir,"trustdb" EXTSEP_S "gpg", NULL); + else if (*new_dbname != DIRSEP_C ) + { + if (strchr(new_dbname, DIRSEP_C) ) + fname = make_filename (new_dbname, NULL); + else + fname = make_filename (opt.homedir, new_dbname, NULL); + } + else + fname = xstrdup (new_dbname); + + if( access( fname, R_OK ) ) { + if( errno != ENOENT ) { + log_error( _("can't access `%s': %s\n"), fname, strerror(errno) ); + xfree(fname); + return G10ERR_TRUSTDB; + } + if( create ) { + FILE *fp; + TRUSTREC rec; + int rc; + char *p = strrchr( fname, DIRSEP_C ); + mode_t oldmask; + + assert(p); + *p = 0; + if( access( fname, F_OK ) ) { + try_make_homedir( fname ); + log_fatal( _("%s: directory does not exist!\n"), fname ); + } + *p = DIRSEP_C; + + xfree(db_name); + db_name = fname; +#ifdef __riscos__ + if( !lockhandle ) + lockhandle = create_dotlock( db_name ); + if( !lockhandle ) + log_fatal( _("can't create lock for `%s'\n"), db_name ); + if( make_dotlock( lockhandle, -1 ) ) + log_fatal( _("can't lock `%s'\n"), db_name ); +#endif /* __riscos__ */ + oldmask=umask(077); + if (is_secured_filename (fname)) { + fp = NULL; + errno = EPERM; + } + else + fp =fopen( fname, "wb" ); + umask(oldmask); + if( !fp ) + log_fatal( _("can't create `%s': %s\n"), fname, strerror(errno) ); + fclose(fp); + db_fd = open( db_name, O_RDWR | MY_O_BINARY ); + if( db_fd == -1 ) + log_fatal( _("can't open `%s': %s\n"), db_name, strerror(errno) ); + +#ifndef __riscos__ + if( !lockhandle ) + lockhandle = create_dotlock( db_name ); + if( !lockhandle ) + log_fatal( _("can't create lock for `%s'\n"), db_name ); +#endif /* !__riscos__ */ + + rc = create_version_record (); + if( rc ) + log_fatal( _("%s: failed to create version record: %s"), + fname, g10_errstr(rc)); + /* and read again to check that we are okay */ + if( tdbio_read_record( 0, &rec, RECTYPE_VER ) ) + log_fatal( _("%s: invalid trustdb created\n"), db_name ); + + if( !opt.quiet ) + log_info(_("%s: trustdb created\n"), db_name); + + return 0; + } + } + xfree(db_name); + db_name = fname; + return 0; +} + + +const char * +tdbio_get_dbname() +{ + return db_name; +} + + + +static void +open_db() +{ + byte buf[10]; + int n; + TRUSTREC rec; + + assert( db_fd == -1 ); + + if (!lockhandle ) + lockhandle = create_dotlock( db_name ); + if (!lockhandle ) + log_fatal( _("can't create lock for `%s'\n"), db_name ); +#ifdef __riscos__ + if (make_dotlock( lockhandle, -1 ) ) + log_fatal( _("can't lock `%s'\n"), db_name ); +#endif /* __riscos__ */ + db_fd = open (db_name, O_RDWR | MY_O_BINARY ); + if (db_fd == -1 && (errno == EACCES +#ifdef EROFS + || errno == EROFS) +#endif + ) { + db_fd = open (db_name, O_RDONLY | MY_O_BINARY ); + if (db_fd != -1) + log_info (_("NOTE: trustdb not writable\n")); + } + if ( db_fd == -1 ) + log_fatal( _("can't open `%s': %s\n"), db_name, strerror(errno) ); + register_secured_file (db_name); + + /* check whether we need to do a version migration */ + do + n = read (db_fd, buf, 5); + while (n==-1 && errno == EINTR); + if (n == 5 && !memcmp (buf, "\x01gpg\x02", 5)) + { + migrate_from_v2 (); + } + + /* read the version record */ + if (tdbio_read_record (0, &rec, RECTYPE_VER ) ) + log_fatal( _("%s: invalid trustdb\n"), db_name ); +} + + +/**************** + * Make a hashtable: type 0 = trust hash + */ +static void +create_hashtable( TRUSTREC *vr, int type ) +{ + TRUSTREC rec; + off_t offset; + ulong recnum; + int i, n, rc; + + offset = lseek( db_fd, 0, SEEK_END ); + if( offset == -1 ) + log_fatal("trustdb: lseek to end failed: %s\n", strerror(errno) ); + recnum = offset / TRUST_RECORD_LEN; + assert(recnum); /* this is will never be the first record */ + + if( !type ) + vr->r.ver.trusthashtbl = recnum; + + /* Now write the records */ + n = (256+ITEMS_PER_HTBL_RECORD-1) / ITEMS_PER_HTBL_RECORD; + for(i=0; i < n; i++, recnum++ ) { + memset( &rec, 0, sizeof rec ); + rec.rectype = RECTYPE_HTBL; + rec.recnum = recnum; + rc = tdbio_write_record( &rec ); + if( rc ) + log_fatal( _("%s: failed to create hashtable: %s\n"), + db_name, g10_errstr(rc)); + } + /* update the version record */ + rc = tdbio_write_record( vr ); + if( !rc ) + rc = tdbio_sync(); + if( rc ) + log_fatal( _("%s: error updating version record: %s\n"), + db_name, g10_errstr(rc)); +} + + +int +tdbio_db_matches_options() +{ + static int yes_no = -1; + + if( yes_no == -1 ) + { + TRUSTREC vr; + int rc; + + rc = tdbio_read_record( 0, &vr, RECTYPE_VER ); + if( rc ) + log_fatal( _("%s: error reading version record: %s\n"), + db_name, g10_errstr(rc) ); + + yes_no = vr.r.ver.marginals == opt.marginals_needed + && vr.r.ver.completes == opt.completes_needed + && vr.r.ver.cert_depth == opt.max_cert_depth + && vr.r.ver.trust_model == opt.trust_model; + } + + return yes_no; +} + +byte +tdbio_read_model(void) +{ + TRUSTREC vr; + int rc; + + rc = tdbio_read_record( 0, &vr, RECTYPE_VER ); + if( rc ) + log_fatal( _("%s: error reading version record: %s\n"), + db_name, g10_errstr(rc) ); + return vr.r.ver.trust_model; +} + +/**************** + * Return the nextstamp value. + */ +ulong +tdbio_read_nextcheck () +{ + TRUSTREC vr; + int rc; + + rc = tdbio_read_record( 0, &vr, RECTYPE_VER ); + if( rc ) + log_fatal( _("%s: error reading version record: %s\n"), + db_name, g10_errstr(rc) ); + return vr.r.ver.nextcheck; +} + +/* Return true when the stamp was actually changed. */ +int +tdbio_write_nextcheck (ulong stamp) +{ + TRUSTREC vr; + int rc; + + rc = tdbio_read_record( 0, &vr, RECTYPE_VER ); + if( rc ) + log_fatal( _("%s: error reading version record: %s\n"), + db_name, g10_errstr(rc) ); + + if (vr.r.ver.nextcheck == stamp) + return 0; + + vr.r.ver.nextcheck = stamp; + rc = tdbio_write_record( &vr ); + if( rc ) + log_fatal( _("%s: error writing version record: %s\n"), + db_name, g10_errstr(rc) ); + return 1; +} + + + +/**************** + * Return the record number of the trusthash tbl or create a new one. + */ +static ulong +get_trusthashrec(void) +{ + static ulong trusthashtbl; /* record number of the trust hashtable */ + + if( !trusthashtbl ) { + TRUSTREC vr; + int rc; + + rc = tdbio_read_record( 0, &vr, RECTYPE_VER ); + if( rc ) + log_fatal( _("%s: error reading version record: %s\n"), + db_name, g10_errstr(rc) ); + if( !vr.r.ver.trusthashtbl ) + create_hashtable( &vr, 0 ); + + trusthashtbl = vr.r.ver.trusthashtbl; + } + return trusthashtbl; +} + + + +/**************** + * Update a hashtable. + * table gives the start of the table, key and keylen is the key, + * newrecnum is the record number to insert. + */ +static int +upd_hashtable( ulong table, byte *key, int keylen, ulong newrecnum ) +{ + TRUSTREC lastrec, rec; + ulong hashrec, item; + int msb; + int level=0; + int rc, i; + + hashrec = table; + next_level: + msb = key[level]; + hashrec += msb / ITEMS_PER_HTBL_RECORD; + rc = tdbio_read_record( hashrec, &rec, RECTYPE_HTBL ); + if( rc ) { + log_error("upd_hashtable: read failed: %s\n", g10_errstr(rc) ); + return rc; + } + + item = rec.r.htbl.item[msb % ITEMS_PER_HTBL_RECORD]; + if( !item ) { /* insert a new item into the hash table */ + rec.r.htbl.item[msb % ITEMS_PER_HTBL_RECORD] = newrecnum; + rc = tdbio_write_record( &rec ); + if( rc ) { + log_error("upd_hashtable: write htbl failed: %s\n", + g10_errstr(rc) ); + return rc; + } + } + else if( item != newrecnum ) { /* must do an update */ + lastrec = rec; + rc = tdbio_read_record( item, &rec, 0 ); + if( rc ) { + log_error( "upd_hashtable: read item failed: %s\n", + g10_errstr(rc) ); + return rc; + } + + if( rec.rectype == RECTYPE_HTBL ) { + hashrec = item; + level++; + if( level >= keylen ) { + log_error( "hashtable has invalid indirections.\n"); + return G10ERR_TRUSTDB; + } + goto next_level; + } + else if( rec.rectype == RECTYPE_HLST ) { /* extend list */ + /* see whether the key is already in this list */ + for(;;) { + for(i=0; i < ITEMS_PER_HLST_RECORD; i++ ) { + if( rec.r.hlst.rnum[i] == newrecnum ) { + return 0; /* okay, already in the list */ + } + } + if( rec.r.hlst.next ) { + rc = tdbio_read_record( rec.r.hlst.next, + &rec, RECTYPE_HLST); + if( rc ) { + log_error( "upd_hashtable: read hlst failed: %s\n", + g10_errstr(rc) ); + return rc; + } + } + else + break; /* not there */ + } + /* find the next free entry and put it in */ + for(;;) { + for(i=0; i < ITEMS_PER_HLST_RECORD; i++ ) { + if( !rec.r.hlst.rnum[i] ) { + rec.r.hlst.rnum[i] = newrecnum; + rc = tdbio_write_record( &rec ); + if( rc ) + log_error( "upd_hashtable: write hlst failed: %s\n", + g10_errstr(rc) ); + return rc; /* done */ + } + } + if( rec.r.hlst.next ) { + rc = tdbio_read_record( rec.r.hlst.next, + &rec, RECTYPE_HLST ); + if( rc ) { + log_error( "upd_hashtable: read hlst failed: %s\n", + g10_errstr(rc) ); + return rc; + } + } + else { /* add a new list record */ + rec.r.hlst.next = item = tdbio_new_recnum(); + rc = tdbio_write_record( &rec ); + if( rc ) { + log_error( "upd_hashtable: write hlst failed: %s\n", + g10_errstr(rc) ); + return rc; + } + memset( &rec, 0, sizeof rec ); + rec.rectype = RECTYPE_HLST; + rec.recnum = item; + rec.r.hlst.rnum[0] = newrecnum; + rc = tdbio_write_record( &rec ); + if( rc ) + log_error( "upd_hashtable: write ext hlst failed: %s\n", + g10_errstr(rc) ); + return rc; /* done */ + } + } /* end loop over hlst slots */ + } + else if( rec.rectype == RECTYPE_TRUST ) { /* insert a list record */ + if( rec.recnum == newrecnum ) { + return 0; + } + item = rec.recnum; /* save number of key record */ + memset( &rec, 0, sizeof rec ); + rec.rectype = RECTYPE_HLST; + rec.recnum = tdbio_new_recnum(); + rec.r.hlst.rnum[0] = item; /* old keyrecord */ + rec.r.hlst.rnum[1] = newrecnum; /* and new one */ + rc = tdbio_write_record( &rec ); + if( rc ) { + log_error( "upd_hashtable: write new hlst failed: %s\n", + g10_errstr(rc) ); + return rc; + } + /* update the hashtable record */ + lastrec.r.htbl.item[msb % ITEMS_PER_HTBL_RECORD] = rec.recnum; + rc = tdbio_write_record( &lastrec ); + if( rc ) + log_error( "upd_hashtable: update htbl failed: %s\n", + g10_errstr(rc) ); + return rc; /* ready */ + } + else { + log_error( "hashtbl %lu: %lu/%d points to an invalid record %lu\n", + table, hashrec, (msb % ITEMS_PER_HTBL_RECORD), item); + list_trustdb(NULL); + return G10ERR_TRUSTDB; + } + } + + return 0; +} + + +/**************** + * Drop an entry from a hashtable + * table gives the start of the table, key and keylen is the key, + */ +static int +drop_from_hashtable( ulong table, byte *key, int keylen, ulong recnum ) +{ + TRUSTREC rec; + ulong hashrec, item; + int msb; + int level=0; + int rc, i; + + hashrec = table; + next_level: + msb = key[level]; + hashrec += msb / ITEMS_PER_HTBL_RECORD; + rc = tdbio_read_record( hashrec, &rec, RECTYPE_HTBL ); + if( rc ) { + log_error("drop_from_hashtable: read failed: %s\n", + g10_errstr(rc) ); + return rc; + } + + item = rec.r.htbl.item[msb % ITEMS_PER_HTBL_RECORD]; + if( !item ) /* not found - forget about it */ + return 0; + + if( item == recnum ) { /* tables points direct to the record */ + rec.r.htbl.item[msb % ITEMS_PER_HTBL_RECORD] = 0; + rc = tdbio_write_record( &rec ); + if( rc ) + log_error("drop_from_hashtable: write htbl failed: %s\n", + g10_errstr(rc) ); + return rc; + } + + rc = tdbio_read_record( item, &rec, 0 ); + if( rc ) { + log_error( "drop_from_hashtable: read item failed: %s\n", + g10_errstr(rc) ); + return rc; + } + + if( rec.rectype == RECTYPE_HTBL ) { + hashrec = item; + level++; + if( level >= keylen ) { + log_error( "hashtable has invalid indirections.\n"); + return G10ERR_TRUSTDB; + } + goto next_level; + } + + if( rec.rectype == RECTYPE_HLST ) { + for(;;) { + for(i=0; i < ITEMS_PER_HLST_RECORD; i++ ) { + if( rec.r.hlst.rnum[i] == recnum ) { + rec.r.hlst.rnum[i] = 0; /* drop */ + rc = tdbio_write_record( &rec ); + if( rc ) + log_error("drop_from_hashtable: write htbl failed: %s\n", + g10_errstr(rc) ); + return rc; + } + } + if( rec.r.hlst.next ) { + rc = tdbio_read_record( rec.r.hlst.next, + &rec, RECTYPE_HLST); + if( rc ) { + log_error( "drop_from_hashtable: read hlst failed: %s\n", + g10_errstr(rc) ); + return rc; + } + } + else + return 0; /* key not in table */ + } + } + + log_error( "hashtbl %lu: %lu/%d points to wrong record %lu\n", + table, hashrec, (msb % ITEMS_PER_HTBL_RECORD), item); + return G10ERR_TRUSTDB; +} + + + +/**************** + * Lookup a record via the hashtable tablewith key/keylen and return the + * result in rec. cmp() should return if the record is the desired one. + * Returns -1 if not found, 0 if found or another errocode + */ +static int +lookup_hashtable( ulong table, const byte *key, size_t keylen, + int (*cmpfnc)(void*, const TRUSTREC *), void *cmpdata, + TRUSTREC *rec ) +{ + int rc; + ulong hashrec, item; + int msb; + int level=0; + + hashrec = table; + next_level: + msb = key[level]; + hashrec += msb / ITEMS_PER_HTBL_RECORD; + rc = tdbio_read_record( hashrec, rec, RECTYPE_HTBL ); + if( rc ) { + log_error("lookup_hashtable failed: %s\n", g10_errstr(rc) ); + return rc; + } + + item = rec->r.htbl.item[msb % ITEMS_PER_HTBL_RECORD]; + if( !item ) + return -1; /* not found */ + + rc = tdbio_read_record( item, rec, 0 ); + if( rc ) { + log_error( "hashtable read failed: %s\n", g10_errstr(rc) ); + return rc; + } + if( rec->rectype == RECTYPE_HTBL ) { + hashrec = item; + level++; + if( level >= keylen ) { + log_error("hashtable has invalid indirections\n"); + return G10ERR_TRUSTDB; + } + goto next_level; + } + else if( rec->rectype == RECTYPE_HLST ) { + for(;;) { + int i; + + for(i=0; i < ITEMS_PER_HLST_RECORD; i++ ) { + if( rec->r.hlst.rnum[i] ) { + TRUSTREC tmp; + + rc = tdbio_read_record( rec->r.hlst.rnum[i], &tmp, 0 ); + if( rc ) { + log_error( "lookup_hashtable: read item failed: %s\n", + g10_errstr(rc) ); + return rc; + } + if( (*cmpfnc)( cmpdata, &tmp ) ) { + *rec = tmp; + return 0; + } + } + } + if( rec->r.hlst.next ) { + rc = tdbio_read_record( rec->r.hlst.next, rec, RECTYPE_HLST ); + if( rc ) { + log_error( "lookup_hashtable: read hlst failed: %s\n", + g10_errstr(rc) ); + return rc; + } + } + else + return -1; /* not found */ + } + } + + + if( (*cmpfnc)( cmpdata, rec ) ) + return 0; /* really found */ + + return -1; /* no: not found */ +} + + +/**************** + * Update the trust hashtbl or create the table if it does not exist + */ +static int +update_trusthashtbl( TRUSTREC *tr ) +{ + return upd_hashtable( get_trusthashrec(), + tr->r.trust.fingerprint, 20, tr->recnum ); +} + + + +void +tdbio_dump_record( TRUSTREC *rec, FILE *fp ) +{ + int i; + ulong rnum = rec->recnum; + + fprintf(fp, "rec %5lu, ", rnum ); + + switch( rec->rectype ) { + case 0: fprintf(fp, "blank\n"); + break; + case RECTYPE_VER: fprintf(fp, + "version, td=%lu, f=%lu, m/c/d=%d/%d/%d tm=%d nc=%lu (%s)\n", + rec->r.ver.trusthashtbl, + rec->r.ver.firstfree, + rec->r.ver.marginals, + rec->r.ver.completes, + rec->r.ver.cert_depth, + rec->r.ver.trust_model, + rec->r.ver.nextcheck, + strtimestamp(rec->r.ver.nextcheck) + ); + break; + case RECTYPE_FREE: fprintf(fp, "free, next=%lu\n", rec->r.free.next ); + break; + case RECTYPE_HTBL: + fprintf(fp, "htbl,"); + for(i=0; i < ITEMS_PER_HTBL_RECORD; i++ ) + fprintf(fp, " %lu", rec->r.htbl.item[i] ); + putc('\n', fp); + break; + case RECTYPE_HLST: + fprintf(fp, "hlst, next=%lu,", rec->r.hlst.next ); + for(i=0; i < ITEMS_PER_HLST_RECORD; i++ ) + fprintf(fp, " %lu", rec->r.hlst.rnum[i] ); + putc('\n', fp); + break; + case RECTYPE_TRUST: + fprintf(fp, "trust "); + for(i=0; i < 20; i++ ) + fprintf(fp, "%02X", rec->r.trust.fingerprint[i] ); + fprintf (fp, ", ot=%d, d=%d, vl=%lu\n", rec->r.trust.ownertrust, + rec->r.trust.depth, rec->r.trust.validlist); + break; + case RECTYPE_VALID: + fprintf(fp, "valid "); + for(i=0; i < 20; i++ ) + fprintf(fp, "%02X", rec->r.valid.namehash[i] ); + fprintf (fp, ", v=%d, next=%lu\n", rec->r.valid.validity, + rec->r.valid.next); + break; + default: + fprintf(fp, "unknown type %d\n", rec->rectype ); + break; + } +} + +/**************** + * read the record with number recnum + * returns: -1 on error, 0 on success + */ +int +tdbio_read_record( ulong recnum, TRUSTREC *rec, int expected ) +{ + byte readbuf[TRUST_RECORD_LEN]; + const byte *buf, *p; + int rc = 0; + int n, i; + + if( db_fd == -1 ) + open_db(); + buf = get_record_from_cache( recnum ); + if( !buf ) { + if( lseek( db_fd, recnum * TRUST_RECORD_LEN, SEEK_SET ) == -1 ) { + log_error(_("trustdb: lseek failed: %s\n"), strerror(errno) ); + return G10ERR_READ_FILE; + } + n = read( db_fd, readbuf, TRUST_RECORD_LEN); + if( !n ) { + return -1; /* eof */ + } + else if( n != TRUST_RECORD_LEN ) { + log_error(_("trustdb: read failed (n=%d): %s\n"), n, + strerror(errno) ); + return G10ERR_READ_FILE; + } + buf = readbuf; + } + rec->recnum = recnum; + rec->dirty = 0; + p = buf; + rec->rectype = *p++; + if( expected && rec->rectype != expected ) { + log_error("%lu: read expected rec type %d, got %d\n", + recnum, expected, rec->rectype ); + return G10ERR_TRUSTDB; + } + p++; /* skip reserved byte */ + switch( rec->rectype ) { + case 0: /* unused (free) record */ + break; + case RECTYPE_VER: /* version record */ + if( memcmp(buf+1, "gpg", 3 ) ) { + log_error( _("%s: not a trustdb file\n"), db_name ); + rc = G10ERR_TRUSTDB; + } + p += 2; /* skip "gpg" */ + rec->r.ver.version = *p++; + rec->r.ver.marginals = *p++; + rec->r.ver.completes = *p++; + rec->r.ver.cert_depth = *p++; + rec->r.ver.trust_model = *p++; + p += 3; + rec->r.ver.created = buftoulong(p); p += 4; + rec->r.ver.nextcheck = buftoulong(p); p += 4; + p += 4; + p += 4; + rec->r.ver.firstfree =buftoulong(p); p += 4; + p += 4; + rec->r.ver.trusthashtbl =buftoulong(p); p += 4; + if( recnum ) { + log_error( _("%s: version record with recnum %lu\n"), db_name, + (ulong)recnum ); + rc = G10ERR_TRUSTDB; + } + else if( rec->r.ver.version != 3 ) { + log_error( _("%s: invalid file version %d\n"), db_name, + rec->r.ver.version ); + rc = G10ERR_TRUSTDB; + } + break; + case RECTYPE_FREE: + rec->r.free.next = buftoulong(p); p += 4; + break; + case RECTYPE_HTBL: + for(i=0; i < ITEMS_PER_HTBL_RECORD; i++ ) { + rec->r.htbl.item[i] = buftoulong(p); p += 4; + } + break; + case RECTYPE_HLST: + rec->r.hlst.next = buftoulong(p); p += 4; + for(i=0; i < ITEMS_PER_HLST_RECORD; i++ ) { + rec->r.hlst.rnum[i] = buftoulong(p); p += 4; + } + break; + case RECTYPE_TRUST: + memcpy( rec->r.trust.fingerprint, p, 20); p+=20; + rec->r.trust.ownertrust = *p++; + rec->r.trust.depth = *p++; + rec->r.trust.min_ownertrust = *p++; + p++; + rec->r.trust.validlist = buftoulong(p); p += 4; + break; + case RECTYPE_VALID: + memcpy( rec->r.valid.namehash, p, 20); p+=20; + rec->r.valid.validity = *p++; + rec->r.valid.next = buftoulong(p); p += 4; + rec->r.valid.full_count = *p++; + rec->r.valid.marginal_count = *p++; + break; + default: + log_error( "%s: invalid record type %d at recnum %lu\n", + db_name, rec->rectype, (ulong)recnum ); + rc = G10ERR_TRUSTDB; + break; + } + + return rc; +} + +/**************** + * Write the record at RECNUM + */ +int +tdbio_write_record( TRUSTREC *rec ) +{ + byte buf[TRUST_RECORD_LEN], *p; + int rc = 0; + int i; + ulong recnum = rec->recnum; + + if( db_fd == -1 ) + open_db(); + + memset(buf, 0, TRUST_RECORD_LEN); + p = buf; + *p++ = rec->rectype; p++; + switch( rec->rectype ) { + case 0: /* unused record */ + break; + case RECTYPE_VER: /* version record */ + if( recnum ) + BUG(); + memcpy(p-1, "gpg", 3 ); p += 2; + *p++ = rec->r.ver.version; + *p++ = rec->r.ver.marginals; + *p++ = rec->r.ver.completes; + *p++ = rec->r.ver.cert_depth; + *p++ = rec->r.ver.trust_model; + p += 3; + ulongtobuf(p, rec->r.ver.created); p += 4; + ulongtobuf(p, rec->r.ver.nextcheck); p += 4; + p += 4; + p += 4; + ulongtobuf(p, rec->r.ver.firstfree ); p += 4; + p += 4; + ulongtobuf(p, rec->r.ver.trusthashtbl ); p += 4; + break; + + case RECTYPE_FREE: + ulongtobuf(p, rec->r.free.next); p += 4; + break; + + + case RECTYPE_HTBL: + for(i=0; i < ITEMS_PER_HTBL_RECORD; i++ ) { + ulongtobuf( p, rec->r.htbl.item[i]); p += 4; + } + break; + + case RECTYPE_HLST: + ulongtobuf( p, rec->r.hlst.next); p += 4; + for(i=0; i < ITEMS_PER_HLST_RECORD; i++ ) { + ulongtobuf( p, rec->r.hlst.rnum[i]); p += 4; + } + break; + + case RECTYPE_TRUST: + memcpy( p, rec->r.trust.fingerprint, 20); p += 20; + *p++ = rec->r.trust.ownertrust; + *p++ = rec->r.trust.depth; + *p++ = rec->r.trust.min_ownertrust; + p++; + ulongtobuf( p, rec->r.trust.validlist); p += 4; + break; + + case RECTYPE_VALID: + memcpy( p, rec->r.valid.namehash, 20); p += 20; + *p++ = rec->r.valid.validity; + ulongtobuf( p, rec->r.valid.next); p += 4; + *p++ = rec->r.valid.full_count; + *p++ = rec->r.valid.marginal_count; + break; + + default: + BUG(); + } + + rc = put_record_into_cache( recnum, buf ); + if( rc ) + ; + else if( rec->rectype == RECTYPE_TRUST ) + rc = update_trusthashtbl( rec ); + + return rc; +} + +int +tdbio_delete_record( ulong recnum ) +{ + TRUSTREC vr, rec; + int rc; + + /* Must read the record fist, so we can drop it from the hash tables */ + rc = tdbio_read_record( recnum, &rec, 0 ); + if( rc ) + ; + else if( rec.rectype == RECTYPE_TRUST ) { + rc = drop_from_hashtable( get_trusthashrec(), + rec.r.trust.fingerprint, 20, rec.recnum ); + } + + if( rc ) + return rc; + + /* now we can chnage it to a free record */ + rc = tdbio_read_record( 0, &vr, RECTYPE_VER ); + if( rc ) + log_fatal( _("%s: error reading version record: %s\n"), + db_name, g10_errstr(rc) ); + + rec.recnum = recnum; + rec.rectype = RECTYPE_FREE; + rec.r.free.next = vr.r.ver.firstfree; + vr.r.ver.firstfree = recnum; + rc = tdbio_write_record( &rec ); + if( !rc ) + rc = tdbio_write_record( &vr ); + return rc; +} + +/**************** + * create a new record and return its record number + */ +ulong +tdbio_new_recnum() +{ + off_t offset; + ulong recnum; + TRUSTREC vr, rec; + int rc; + + /* look for unused records */ + rc = tdbio_read_record( 0, &vr, RECTYPE_VER ); + if( rc ) + log_fatal( _("%s: error reading version record: %s\n"), + db_name, g10_errstr(rc) ); + if( vr.r.ver.firstfree ) { + recnum = vr.r.ver.firstfree; + rc = tdbio_read_record( recnum, &rec, RECTYPE_FREE ); + if( rc ) { + log_error( _("%s: error reading free record: %s\n"), + db_name, g10_errstr(rc) ); + return rc; + } + /* update dir record */ + vr.r.ver.firstfree = rec.r.free.next; + rc = tdbio_write_record( &vr ); + if( rc ) { + log_error( _("%s: error writing dir record: %s\n"), + db_name, g10_errstr(rc) ); + return rc; + } + /*zero out the new record */ + memset( &rec, 0, sizeof rec ); + rec.rectype = 0; /* unused record */ + rec.recnum = recnum; + rc = tdbio_write_record( &rec ); + if( rc ) + log_fatal(_("%s: failed to zero a record: %s\n"), + db_name, g10_errstr(rc)); + } + else { /* not found, append a new record */ + offset = lseek( db_fd, 0, SEEK_END ); + if( offset == -1 ) + log_fatal("trustdb: lseek to end failed: %s\n", strerror(errno) ); + recnum = offset / TRUST_RECORD_LEN; + assert(recnum); /* this is will never be the first record */ + /* we must write a record, so that the next call to this function + * returns another recnum */ + memset( &rec, 0, sizeof rec ); + rec.rectype = 0; /* unused record */ + rec.recnum = recnum; + rc = 0; + if( lseek( db_fd, recnum * TRUST_RECORD_LEN, SEEK_SET ) == -1 ) { + log_error(_("trustdb rec %lu: lseek failed: %s\n"), + recnum, strerror(errno) ); + rc = G10ERR_WRITE_FILE; + } + else { + int n = write( db_fd, &rec, TRUST_RECORD_LEN); + if( n != TRUST_RECORD_LEN ) { + log_error(_("trustdb rec %lu: write failed (n=%d): %s\n"), + recnum, n, strerror(errno) ); + rc = G10ERR_WRITE_FILE; + } + } + + if( rc ) + log_fatal(_("%s: failed to append a record: %s\n"), + db_name, g10_errstr(rc)); + } + return recnum ; +} + + + +static int +cmp_trec_fpr ( void *fpr, const TRUSTREC *rec ) +{ + return rec->rectype == RECTYPE_TRUST + && !memcmp( rec->r.trust.fingerprint, fpr, 20); +} + + +int +tdbio_search_trust_byfpr( const byte *fingerprint, TRUSTREC *rec ) +{ + int rc; + + /* locate the trust record using the hash table */ + rc = lookup_hashtable( get_trusthashrec(), fingerprint, 20, + cmp_trec_fpr, (void*)fingerprint, rec ); + return rc; +} + +int +tdbio_search_trust_bypk (PKT_public_key *pk, TRUSTREC *rec) +{ + byte fingerprint[MAX_FINGERPRINT_LEN]; + size_t fingerlen; + + fingerprint_from_pk( pk, fingerprint, &fingerlen ); + for (; fingerlen < 20; fingerlen++ ) + fingerprint[fingerlen] = 0; + return tdbio_search_trust_byfpr (fingerprint, rec); +} + + + +void +tdbio_invalid(void) +{ + log_error(_( + "the trustdb is corrupted; please run \"gpg --fix-trustdb\".\n") ); + g10_exit(2); +} + +/* + * Migrate the trustdb as just up to gpg 1.0.6 (trustdb version 2) + * to the 2.1 version as used with 1.0.6b - This is pretty trivial as needs + * only to scan the tdb and insert new the new trust records. The old ones are + * obsolte from now on + */ +static void +migrate_from_v2 () +{ + TRUSTREC rec; + int i, n; + struct { + ulong keyrecno; + byte ot; + byte okay; + byte fpr[20]; + } *ottable; + int ottable_size, ottable_used; + byte oldbuf[40]; + ulong recno; + int rc, count; + + ottable_size = 5; + ottable = xmalloc (ottable_size * sizeof *ottable); + ottable_used = 0; + + /* We have some restrictions here. We can't use the version record + * and we can't use any of the old hashtables because we dropped the + * code. So we first collect all ownertrusts and then use a second + * pass fo find the associated keys. We have to do this all without using + * the regular record read functions. + */ + + /* get all the ownertrusts */ + if (lseek (db_fd, 0, SEEK_SET ) == -1 ) + log_fatal ("migrate_from_v2: lseek failed: %s\n", strerror (errno)); + for (recno=0;;recno++) + { + do + n = read (db_fd, oldbuf, 40); + while (n==-1 && errno == EINTR); + if (!n) + break; /* eof */ + if (n != 40) + log_fatal ("migrate_vfrom_v2: read error or short read\n"); + + if (*oldbuf != 2) + continue; + + /* v2 dir record */ + if (ottable_used == ottable_size) + { + ottable_size += 1000; + ottable = xrealloc (ottable, ottable_size * sizeof *ottable); + } + ottable[ottable_used].keyrecno = buftoulong (oldbuf+6); + ottable[ottable_used].ot = oldbuf[18]; + ottable[ottable_used].okay = 0; + memset (ottable[ottable_used].fpr,0, 20); + if (ottable[ottable_used].keyrecno && ottable[ottable_used].ot) + ottable_used++; + } + log_info ("found %d ownertrust records\n", ottable_used); + + /* Read again and find the fingerprints */ + if (lseek (db_fd, 0, SEEK_SET ) == -1 ) + log_fatal ("migrate_from_v2: lseek failed: %s\n", strerror (errno)); + for (recno=0;;recno++) + { + do + n = read (db_fd, oldbuf, 40); + while (n==-1 && errno == EINTR); + if (!n) + break; /* eof */ + if (n != 40) + log_fatal ("migrate_from_v2: read error or short read\n"); + + if (*oldbuf != 3) + continue; + + /* v2 key record */ + for (i=0; i < ottable_used; i++) + { + if (ottable[i].keyrecno == recno) + { + memcpy (ottable[i].fpr, oldbuf+20, 20); + ottable[i].okay = 1; + break; + } + } + } + + /* got everything - create the v3 trustdb */ + if (ftruncate (db_fd, 0)) + log_fatal ("can't truncate `%s': %s\n", db_name, strerror (errno) ); + if (create_version_record ()) + log_fatal ("failed to recreate version record of `%s'\n", db_name); + + /* access the hash table, so it is store just after the version record, + * this is not needed put a dump is more pretty */ + get_trusthashrec (); + + /* And insert the old ownertrust values */ + count = 0; + for (i=0; i < ottable_used; i++) + { + if (!ottable[i].okay) + continue; + + memset (&rec, 0, sizeof rec); + rec.recnum = tdbio_new_recnum (); + rec.rectype = RECTYPE_TRUST; + memcpy(rec.r.trust.fingerprint, ottable[i].fpr, 20); + rec.r.trust.ownertrust = ottable[i].ot; + if (tdbio_write_record (&rec)) + log_fatal ("failed to write trust record of `%s'\n", db_name); + count++; + } + + revalidation_mark (); + rc = tdbio_sync (); + if (rc) + log_fatal ("failed to sync `%s'\n", db_name); + log_info ("migrated %d version 2 ownertrusts\n", count); + xfree (ottable); +} diff --git a/g10/tdbio.h b/g10/tdbio.h new file mode 100644 index 0000000..80a70d9 --- /dev/null +++ b/g10/tdbio.h @@ -0,0 +1,118 @@ +/* tdbio.h - Trust database I/O functions + * Copyright (C) 1998, 1999, 2000, 2001, 2002 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#ifndef G10_TDBIO_H +#define G10_TDBIO_H + +#include "host2net.h" + +#define TRUST_RECORD_LEN 40 +#define SIGS_PER_RECORD ((TRUST_RECORD_LEN-10)/5) +#define ITEMS_PER_HTBL_RECORD ((TRUST_RECORD_LEN-2)/4) +#define ITEMS_PER_HLST_RECORD ((TRUST_RECORD_LEN-6)/5) +#define ITEMS_PER_PREF_RECORD (TRUST_RECORD_LEN-10) +#if ITEMS_PER_PREF_RECORD % 2 +#error ITEMS_PER_PREF_RECORD must be even +#endif +#define MAX_LIST_SIGS_DEPTH 20 + + +#define RECTYPE_VER 1 +#define RECTYPE_HTBL 10 +#define RECTYPE_HLST 11 +#define RECTYPE_TRUST 12 +#define RECTYPE_VALID 13 +#define RECTYPE_FREE 254 + + +struct trust_record { + int rectype; + int mark; + int dirty; /* for now only used internal by functions */ + struct trust_record *next; /* help pointer to build lists in memory */ + ulong recnum; + union { + struct { /* version record: */ + byte version; /* should be 3 */ + byte marginals; + byte completes; + byte cert_depth; + byte trust_model; + ulong created; /* timestamp of trustdb creation */ + ulong nextcheck; /* timestamp of next scheduled check */ + ulong reserved; + ulong reserved2; + ulong firstfree; + ulong reserved3; + ulong trusthashtbl; + } ver; + struct { /* free record */ + ulong next; + } free; + struct { + ulong item[ITEMS_PER_HTBL_RECORD]; + } htbl; + struct { + ulong next; + ulong rnum[ITEMS_PER_HLST_RECORD]; /* of another record */ + } hlst; + struct { + byte fingerprint[20]; + byte ownertrust; + byte depth; + ulong validlist; + byte min_ownertrust; + } trust; + struct { + byte namehash[20]; + ulong next; + byte validity; + byte full_count; + byte marginal_count; + } valid; + } r; +}; +typedef struct trust_record TRUSTREC; + +/*-- tdbio.c --*/ +int tdbio_update_version_record(void); +int tdbio_set_dbname( const char *new_dbname, int create ); +const char *tdbio_get_dbname(void); +void tdbio_dump_record( TRUSTREC *rec, FILE *fp ); +int tdbio_read_record( ulong recnum, TRUSTREC *rec, int expected ); +int tdbio_write_record( TRUSTREC *rec ); +int tdbio_db_matches_options(void); +byte tdbio_read_model(void); +ulong tdbio_read_nextcheck (void); +int tdbio_write_nextcheck (ulong stamp); +int tdbio_is_dirty(void); +int tdbio_sync(void); +int tdbio_begin_transaction(void); +int tdbio_end_transaction(void); +int tdbio_cancel_transaction(void); +int tdbio_delete_record( ulong recnum ); +ulong tdbio_new_recnum(void); +int tdbio_search_trust_byfpr(const byte *fingerprint, TRUSTREC *rec ); +int tdbio_search_trust_bypk(PKT_public_key *pk, TRUSTREC *rec ); + +void tdbio_invalid(void); + +#endif /*G10_TDBIO_H*/ diff --git a/g10/textfilter.c b/g10/textfilter.c new file mode 100644 index 0000000..8665a72 --- /dev/null +++ b/g10/textfilter.c @@ -0,0 +1,251 @@ +/* textfilter.c + * Copyright (C) 1998, 1999, 2000, 2001, 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 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <assert.h> + +#include "errors.h" +#include "iobuf.h" +#include "memory.h" +#include "util.h" +#include "filter.h" +#include "i18n.h" +#include "options.h" +#include "status.h" + +#ifdef HAVE_DOSISH_SYSTEM +#define LF "\r\n" +#else +#define LF "\n" +#endif + +#define MAX_LINELEN 19995 /* a little bit smaller than in armor.c */ + /* to make sure that a warning is displayed while */ + /* creating a message */ + +static unsigned +len_without_trailing_chars( byte *line, unsigned len, const char *trimchars ) +{ + byte *p, *mark; + unsigned n; + + for(mark=NULL, p=line, n=0; n < len; n++, p++ ) { + if( strchr( trimchars, *p ) ) { + if( !mark ) + mark = p; + } + else + mark = NULL; + } + + return mark? (mark - line) : len; +} + + +static int +standard( text_filter_context_t *tfx, IOBUF a, + byte *buf, size_t size, size_t *ret_len) +{ + int rc=0; + size_t len = 0; + unsigned maxlen; + + assert( size > 10 ); + size -= 2; /* reserve 2 bytes to append CR,LF */ + while( !rc && len < size ) { + int lf_seen; + + while( len < size && tfx->buffer_pos < tfx->buffer_len ) + buf[len++] = tfx->buffer[tfx->buffer_pos++]; + if( len >= size ) + continue; + + /* read the next line */ + maxlen = MAX_LINELEN; + tfx->buffer_pos = 0; + tfx->buffer_len = iobuf_read_line( a, &tfx->buffer, + &tfx->buffer_size, &maxlen ); + if( !maxlen ) + tfx->truncated++; + if( !tfx->buffer_len ) { + if( !len ) + rc = -1; /* eof */ + break; + } + lf_seen = tfx->buffer[tfx->buffer_len-1] == '\n'; + + /* The story behind this is that 2440 says that textmode + hashes should canonicalize line endings to CRLF and remove + spaces and tabs. 2440bis-12 says to just canonicalize to + CRLF. 1.4.0 was released using the bis-12 behavior, but it + was discovered that many mail clients do not canonicalize + PGP/MIME signature text appropriately (and were relying on + GnuPG to handle trailing spaces). So, we default to the + 2440 behavior, but use the 2440bis-12 behavior if the user + specifies --no-rfc2440-text. The default will be changed + at some point in the future when the mail clients have been + upgraded. Aside from PGP/MIME and broken mail clients, + this makes no difference to any signatures in the real + world except for a textmode detached signature. PGP always + used the 2440bis-12 behavior (ignoring 2440 itself), so + this actually makes us compatible with PGP textmode + detached signatures for the first time. */ + if(opt.rfc2440_text) + tfx->buffer_len=trim_trailing_chars(tfx->buffer,tfx->buffer_len, + " \t\r\n"); + else + tfx->buffer_len=trim_trailing_chars(tfx->buffer,tfx->buffer_len, + "\r\n"); + + if( lf_seen ) { + tfx->buffer[tfx->buffer_len++] = '\r'; + tfx->buffer[tfx->buffer_len++] = '\n'; + } + } + *ret_len = len; + return rc; +} + + +/**************** + * The filter is used to make canonical text: Lines are terminated by + * CR, LF, trailing white spaces are removed. + */ +int +text_filter( void *opaque, int control, + IOBUF a, byte *buf, size_t *ret_len) +{ + size_t size = *ret_len; + text_filter_context_t *tfx = opaque; + int rc=0; + + if( control == IOBUFCTRL_UNDERFLOW ) { + rc = standard( tfx, a, buf, size, ret_len ); + } + else if( control == IOBUFCTRL_FREE ) { + if( tfx->truncated ) + log_error(_("can't handle text lines longer than %d characters\n"), + MAX_LINELEN ); + xfree( tfx->buffer ); + tfx->buffer = NULL; + } + else if( control == IOBUFCTRL_DESC ) + *(char**)buf = "text_filter"; + return rc; +} + + +/**************** + * Copy data from INP to OUT and do some escaping if requested. + * md is updated as required by rfc2440 + */ +int +copy_clearsig_text( IOBUF out, IOBUF inp, MD_HANDLE md, + int escape_dash, int escape_from, int pgp2mode ) +{ + unsigned maxlen; + byte *buffer = NULL; /* malloced buffer */ + unsigned bufsize; /* and size of this buffer */ + unsigned n; + int truncated = 0; + int pending_lf = 0; + + if( !opt.pgp2_workarounds ) + pgp2mode = 0; + + if( !escape_dash ) + escape_from = 0; + + write_status (STATUS_BEGIN_SIGNING); + + for(;;) { + maxlen = MAX_LINELEN; + n = iobuf_read_line( inp, &buffer, &bufsize, &maxlen ); + if( !maxlen ) + truncated++; + + if( !n ) + break; /* read_line has returned eof */ + + /* update the message digest */ + if( escape_dash ) { + if( pending_lf ) { + md_putc( md, '\r' ); + md_putc( md, '\n' ); + } + md_write( md, buffer, + len_without_trailing_chars( buffer, n, + pgp2mode? " \r\n":" \t\r\n")); + } + else + md_write( md, buffer, n ); + pending_lf = buffer[n-1] == '\n'; + + /* write the output */ + if( ( escape_dash && *buffer == '-') + || ( escape_from && n > 4 && !memcmp(buffer, "From ", 5 ) ) ) { + iobuf_put( out, '-' ); + iobuf_put( out, ' ' ); + } + +#if 0 /*defined(HAVE_DOSISH_SYSTEM)*/ + /* We don't use this anymore because my interpretation of rfc2440 7.1 + * is that there is no conversion needed. If one decides to + * clearsign a unix file on a DOS box he will get a mixed line endings. + * If at some point it turns out, that a conversion is a nice feature + * we can make an option out of it. + */ + /* make sure the lines do end in CR,LF */ + if( n > 1 && ( (buffer[n-2] == '\r' && buffer[n-1] == '\n' ) + || (buffer[n-2] == '\n' && buffer[n-1] == '\r'))) { + iobuf_write( out, buffer, n-2 ); + iobuf_put( out, '\r'); + iobuf_put( out, '\n'); + } + else if( n && buffer[n-1] == '\n' ) { + iobuf_write( out, buffer, n-1 ); + iobuf_put( out, '\r'); + iobuf_put( out, '\n'); + } + else + iobuf_write( out, buffer, n ); + +#else + iobuf_write( out, buffer, n ); +#endif + } + + /* at eof */ + if( !pending_lf ) { /* make sure that the file ends with a LF */ + iobuf_writestr( out, LF ); + if( !escape_dash ) + md_putc( md, '\n' ); + } + + if( truncated ) + log_info(_("input line longer than %d characters\n"), MAX_LINELEN ); + + return 0; /* okay */ +} diff --git a/g10/tlv.c b/g10/tlv.c new file mode 100644 index 0000000..c7233e0 --- /dev/null +++ b/g10/tlv.c @@ -0,0 +1,306 @@ +/* tlv.c - Tag-Length-Value Utilities + * 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 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> + +#if GNUPG_MAJOR_VERSION == 1 +#define GPG_ERR_EOF (-1) +#define GPG_ERR_BAD_BER (1) /*G10ERR_GENERAL*/ +#define GPG_ERR_INV_SEXP (45) /*G10ERR_INV_ARG*/ +typedef int gpg_error_t; +#define gpg_error(n) (n) +#else +#include <gpg-error.h> +#endif + + +#include "tlv.h" + +static const unsigned char * +do_find_tlv (const unsigned char *buffer, size_t length, + int tag, size_t *nbytes, int nestlevel) +{ + const unsigned char *s = buffer; + size_t n = length; + size_t len; + int this_tag; + int composite; + + for (;;) + { + buffer = s; + if (n < 2) + return NULL; /* Buffer definitely too short for tag and length. */ + if (!*s || *s == 0xff) + { /* Skip optional filler between TLV objects. */ + s++; + n--; + continue; + } + composite = !!(*s & 0x20); + if ((*s & 0x1f) == 0x1f) + { /* more tag bytes to follow */ + s++; + n--; + if (n < 2) + return NULL; /* buffer definitely too short for tag and length. */ + if ((*s & 0x1f) == 0x1f) + return NULL; /* We support only up to 2 bytes. */ + this_tag = (s[-1] << 8) | (s[0] & 0x7f); + } + else + this_tag = s[0]; + len = s[1]; + s += 2; n -= 2; + if (len < 0x80) + ; + else if (len == 0x81) + { /* One byte length follows. */ + if (!n) + return NULL; /* we expected 1 more bytes with the length. */ + len = s[0]; + s++; n--; + } + else if (len == 0x82) + { /* Two byte length follows. */ + if (n < 2) + return NULL; /* We expected 2 more bytes with the length. */ + len = (s[0] << 8) | s[1]; + s += 2; n -= 2; + } + else + return NULL; /* APDU limit is 65535, thus it does not make + sense to assume longer length fields. */ + + if (composite && nestlevel < 100) + { /* Dive into this composite DO after checking for a too deep + nesting. */ + const unsigned char *tmp_s; + size_t tmp_len; + + tmp_s = do_find_tlv (s, len, tag, &tmp_len, nestlevel+1); + if (tmp_s) + { + *nbytes = tmp_len; + return tmp_s; + } + } + + if (this_tag == tag) + { + *nbytes = len; + return s; + } + if (len > n) + return NULL; /* Buffer too short to skip to the next tag. */ + s += len; n -= len; + } +} + + +/* Locate a TLV encoded data object in BUFFER of LENGTH and + return a pointer to value as well as its length in NBYTES. Return + NULL if it was not found or if the object does not fit into the buffer. */ +const unsigned char * +find_tlv (const unsigned char *buffer, size_t length, + int tag, size_t *nbytes) +{ + const unsigned char *p; + + p = do_find_tlv (buffer, length, tag, nbytes, 0); + if (p && *nbytes > (length - (p-buffer))) + p = NULL; /* Object longer than buffer. */ + return p; +} + + + +/* Locate a TLV encoded data object in BUFFER of LENGTH and + return a pointer to value as well as its length in NBYTES. Return + NULL if it was not found. Note, that the function does not check + whether the value fits into the provided buffer. */ +const unsigned char * +find_tlv_unchecked (const unsigned char *buffer, size_t length, + int tag, size_t *nbytes) +{ + return do_find_tlv (buffer, length, tag, nbytes, 0); +} + + +/* ASN.1 BER parser: Parse BUFFER of length SIZE and return the tag + and the length part from the TLV triplet. Update BUFFER and SIZE + on success. */ +gpg_error_t +parse_ber_header (unsigned char const **buffer, size_t *size, + int *r_class, int *r_tag, + int *r_constructed, int *r_ndef, + size_t *r_length, size_t *r_nhdr) +{ + int c; + unsigned long tag; + const unsigned char *buf = *buffer; + size_t length = *size; + + *r_ndef = 0; + *r_length = 0; + *r_nhdr = 0; + + /* Get the tag. */ + if (!length) + return gpg_error (GPG_ERR_EOF); + c = *buf++; length--; ++*r_nhdr; + + *r_class = (c & 0xc0) >> 6; + *r_constructed = !!(c & 0x20); + tag = c & 0x1f; + + if (tag == 0x1f) + { + tag = 0; + do + { + tag <<= 7; + if (!length) + return gpg_error (GPG_ERR_EOF); + c = *buf++; length--; ++*r_nhdr; + tag |= c & 0x7f; + + } + while (c & 0x80); + } + *r_tag = tag; + + /* Get the length. */ + if (!length) + return gpg_error (GPG_ERR_EOF); + c = *buf++; length--; ++*r_nhdr; + + if ( !(c & 0x80) ) + *r_length = c; + else if (c == 0x80) + *r_ndef = 1; + else if (c == 0xff) + return gpg_error (GPG_ERR_BAD_BER); + else + { + unsigned long len = 0; + int count = c & 0x7f; + + if (count > sizeof (len) || count > sizeof (size_t)) + return gpg_error (GPG_ERR_BAD_BER); + + for (; count; count--) + { + len <<= 8; + if (!length) + return gpg_error (GPG_ERR_EOF); + c = *buf++; length--; ++*r_nhdr; + len |= c & 0xff; + } + *r_length = len; + } + + /* Without this kludge some example certs can't be parsed. */ + if (*r_class == CLASS_UNIVERSAL && !*r_tag) + *r_length = 0; + + *buffer = buf; + *size = length; + return 0; +} + + +/* FIXME: The following function should not go into this file but for + now it is easier to keep it here. */ + +/* Return the next token of an canconical encoded S-expression. BUF + is the pointer to the S-expression and BUFLEN is a pointer to the + length of this S-expression (used to validate the syntax). Both + are updated to reflect the new position. The token itself is + returned as a pointer into the orginal buffer at TOK and TOKLEN. + If a parentheses is the next token, TOK will be set to NULL. + TOKLEN is checked to be within the bounds. On error a error code + is returned and all pointers should are not guaranteed to point to + a meanigful value. DEPTH should be initialized to 0 and will + reflect on return the actual depth of the tree. To detect the end + of the S-expression it is advisable to check DEPTH after a + successful return: + + depth = 0; + while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)) + && depth) + process_token (tok, toklen); + if (err) + handle_error (); + */ +gpg_error_t +parse_sexp (unsigned char const **buf, size_t *buflen, + int *depth, unsigned char const **tok, size_t *toklen) +{ + const unsigned char *s; + size_t n, vlen; + + s = *buf; + n = *buflen; + *tok = NULL; + *toklen = 0; + if (!n) + return *depth ? gpg_error (GPG_ERR_INV_SEXP) : 0; + if (*s == '(') + { + s++; n--; + (*depth)++; + *buf = s; + *buflen = n; + return 0; + } + if (*s == ')') + { + if (!*depth) + return gpg_error (GPG_ERR_INV_SEXP); + *toklen = 1; + s++; n--; + (*depth)--; + *buf = s; + *buflen = n; + return 0; + } + for (vlen=0; n && *s && *s != ':' && (*s >= '0' && *s <= '9'); s++, n--) + vlen = vlen*10 + (*s - '0'); + if (!n || *s != ':') + return gpg_error (GPG_ERR_INV_SEXP); + s++; n--; + if (vlen > n) + return gpg_error (GPG_ERR_INV_SEXP); + *tok = s; + *toklen = vlen; + s += vlen; + n -= vlen; + *buf = s; + *buflen = n; + return 0; +} + diff --git a/g10/tlv.h b/g10/tlv.h new file mode 100644 index 0000000..877573d --- /dev/null +++ b/g10/tlv.h @@ -0,0 +1,109 @@ +/* tlv.h - Tag-Length-Value Utilities + * 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 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#ifndef SCD_TLV_H +#define SCD_TLV_H 1 + + +enum tlv_tag_class { + CLASS_UNIVERSAL = 0, + CLASS_APPLICATION = 1, + CLASS_CONTEXT = 2, + CLASS_PRIVATE =3 +}; + +enum tlv_tag_type { + TAG_NONE = 0, + TAG_BOOLEAN = 1, + TAG_INTEGER = 2, + TAG_BIT_STRING = 3, + TAG_OCTET_STRING = 4, + TAG_NULL = 5, + TAG_OBJECT_ID = 6, + TAG_OBJECT_DESCRIPTOR = 7, + TAG_EXTERNAL = 8, + TAG_REAL = 9, + TAG_ENUMERATED = 10, + TAG_EMBEDDED_PDV = 11, + TAG_UTF8_STRING = 12, + TAG_REALTIVE_OID = 13, + TAG_SEQUENCE = 16, + TAG_SET = 17, + TAG_NUMERIC_STRING = 18, + TAG_PRINTABLE_STRING = 19, + TAG_TELETEX_STRING = 20, + TAG_VIDEOTEX_STRING = 21, + TAG_IA5_STRING = 22, + TAG_UTC_TIME = 23, + TAG_GENERALIZED_TIME = 24, + TAG_GRAPHIC_STRING = 25, + TAG_VISIBLE_STRING = 26, + TAG_GENERAL_STRING = 27, + TAG_UNIVERSAL_STRING = 28, + TAG_CHARACTER_STRING = 29, + TAG_BMP_STRING = 30 +}; + + +/* Locate a TLV encoded data object in BUFFER of LENGTH and return a + pointer to value as well as its length in NBYTES. Return NULL if + it was not found or if the object does not fit into the buffer. */ +const unsigned char *find_tlv (const unsigned char *buffer, size_t length, + int tag, size_t *nbytes); + + +/* Locate a TLV encoded data object in BUFFER of LENGTH and return a + pointer to value as well as its length in NBYTES. Return NULL if + it was not found. Note, that the function does not check whether + the value fits into the provided buffer.*/ +const unsigned char *find_tlv_unchecked (const unsigned char *buffer, + size_t length, + int tag, size_t *nbytes); + + +/* ASN.1 BER parser: Parse BUFFER of length SIZE and return the tag + and the length part from the TLV triplet. Update BUFFER and SIZE + on success. */ +gpg_error_t parse_ber_header (unsigned char const **buffer, size_t *size, + int *r_class, int *r_tag, + int *r_constructed, + int *r_ndef, size_t *r_length, size_t *r_nhdr); + + + +/* Return the next token of an canconical encoded S-expression. BUF + is the pointer to the S-expression and BUFLEN is a pointer to the + length of this S-expression (used to validate the syntax). Both + are updated to reflect the new position. The token itself is + returned as a pointer into the orginal buffer at TOK and TOKLEN. + If a parentheses is the next token, TOK will be set to NULL. + TOKLEN is checked to be within the bounds. On error a error code + is returned and all pointers should are not guaranteed to point to + a meanigful value. DEPTH should be initialized to 0 and will + reflect on return the actual depth of the tree. To detect the end + of the S-expression it is advisable to check DEPTH after a + successful return. */ +gpg_error_t parse_sexp (unsigned char const **buf, size_t *buflen, + int *depth, unsigned char const **tok, size_t *toklen); + + + +#endif /* SCD_TLV_H */ diff --git a/g10/trustdb.c b/g10/trustdb.c new file mode 100644 index 0000000..e372bf8 --- /dev/null +++ b/g10/trustdb.c @@ -0,0 +1,2368 @@ +/* trustdb.c + * Copyright (C) 1998, 1999, 2000, 2001, 2002, 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 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> + +#ifndef DISABLE_REGEX +#include <sys/types.h> +#ifdef USE_INTERNAL_REGEX +#include "_regex.h" +#else +#include <regex.h> +#endif +#endif /* !DISABLE_REGEX */ + +#include "errors.h" +#include "iobuf.h" +#include "keydb.h" +#include "memory.h" +#include "util.h" +#include "options.h" +#include "packet.h" +#include "main.h" +#include "i18n.h" +#include "tdbio.h" +#include "trustdb.h" + + +/* + * A structure to store key identification as well as some stuff needed + * for validation + */ +struct key_item { + struct key_item *next; + unsigned int ownertrust,min_ownertrust; + byte trust_depth; + byte trust_value; + char *trust_regexp; + u32 kid[2]; +}; + + +typedef struct key_item **KeyHashTable; /* see new_key_hash_table() */ + +/* + * Structure to keep track of keys, this is used as an array wherre + * the item right after the last one has a keyblock set to NULL. + * Maybe we can drop this thing and replace it by key_item + */ +struct key_array { + KBNODE keyblock; +}; + + +/* control information for the trust DB */ +static struct { + int init; + int level; + char *dbname; +} trustdb_args; + +/* some globals */ +static struct key_item *user_utk_list; /* temp. used to store --trusted-keys */ +static struct key_item *utk_list; /* all ultimately trusted keys */ + +static int pending_check_trustdb; + +static int validate_keys (int interactive); + + +/********************************************** + ************* some helpers ******************* + **********************************************/ + +static struct key_item * +new_key_item (void) +{ + struct key_item *k; + + k = xmalloc_clear (sizeof *k); + return k; +} + +static void +release_key_items (struct key_item *k) +{ + struct key_item *k2; + + for (; k; k = k2) + { + k2 = k->next; + xfree (k->trust_regexp); + xfree (k); + } +} + +/* + * For fast keylook up we need a hash table. Each byte of a KeyIDs + * should be distributed equally over the 256 possible values (except + * for v3 keyIDs but we consider them as not important here). So we + * can just use 10 bits to index a table of 1024 key items. + * Possible optimization: Don not use key_items but other hash_table when the + * duplicates lists gets too large. + */ +static KeyHashTable +new_key_hash_table (void) +{ + struct key_item **tbl; + + tbl = xmalloc_clear (1024 * sizeof *tbl); + return tbl; +} + +static void +release_key_hash_table (KeyHashTable tbl) +{ + int i; + + if (!tbl) + return; + for (i=0; i < 1024; i++) + release_key_items (tbl[i]); + xfree (tbl); +} + +/* + * Returns: True if the keyID is in the given hash table + */ +static int +test_key_hash_table (KeyHashTable tbl, u32 *kid) +{ + struct key_item *k; + + for (k = tbl[(kid[1] & 0x03ff)]; k; k = k->next) + if (k->kid[0] == kid[0] && k->kid[1] == kid[1]) + return 1; + return 0; +} + +/* + * Add a new key to the hash table. The key is identified by its key ID. + */ +static void +add_key_hash_table (KeyHashTable tbl, u32 *kid) +{ + struct key_item *k, *kk; + + for (k = tbl[(kid[1] & 0x03ff)]; k; k = k->next) + if (k->kid[0] == kid[0] && k->kid[1] == kid[1]) + return; /* already in table */ + + kk = new_key_item (); + kk->kid[0] = kid[0]; + kk->kid[1] = kid[1]; + kk->next = tbl[(kid[1] & 0x03ff)]; + tbl[(kid[1] & 0x03ff)] = kk; +} + +/* + * Release a key_array + */ +static void +release_key_array ( struct key_array *keys ) +{ + struct key_array *k; + + if (keys) { + for (k=keys; k->keyblock; k++) + release_kbnode (k->keyblock); + xfree (keys); + } +} + + +/********************************************* + ********** Initialization ***************** + *********************************************/ + + + +/* + * Used to register extra ultimately trusted keys - this has to be done + * before initializing the validation module. + * FIXME: Should be replaced by a function to add those keys to the trustdb. + */ +void +register_trusted_keyid(u32 *keyid) +{ + struct key_item *k; + + k = new_key_item (); + k->kid[0] = keyid[0]; + k->kid[1] = keyid[1]; + k->next = user_utk_list; + user_utk_list = k; +} + +void +register_trusted_key( const char *string ) +{ + KEYDB_SEARCH_DESC desc; + + if (classify_user_id (string, &desc) != KEYDB_SEARCH_MODE_LONG_KID ) + { + log_error(_("`%s' is not a valid long keyID\n"), string ); + return; + } + + register_trusted_keyid(desc.u.kid); +} + +/* + * Helper to add a key to the global list of ultimately trusted keys. + * Retruns: true = inserted, false = already in in list. + */ +static int +add_utk (u32 *kid) +{ + struct key_item *k; + + for (k = utk_list; k; k = k->next) + { + if (k->kid[0] == kid[0] && k->kid[1] == kid[1]) + { + return 0; + } + } + + k = new_key_item (); + k->kid[0] = kid[0]; + k->kid[1] = kid[1]; + k->ownertrust = TRUST_ULTIMATE; + k->next = utk_list; + utk_list = k; + if( opt.verbose > 1 ) + log_info(_("key %s: accepted as trusted key\n"), keystr(kid)); + return 1; +} + + +/**************** + * Verify that all our secret keys are usable and put them into the utk_list. + */ +static void +verify_own_keys(void) +{ + TRUSTREC rec; + ulong recnum; + int rc; + struct key_item *k; + + if (utk_list) + return; + + /* scan the trustdb to find all ultimately trusted keys */ + for (recnum=1; !tdbio_read_record (recnum, &rec, 0); recnum++ ) + { + if ( rec.rectype == RECTYPE_TRUST + && (rec.r.trust.ownertrust & TRUST_MASK) == TRUST_ULTIMATE) + { + byte *fpr = rec.r.trust.fingerprint; + int fprlen; + u32 kid[2]; + + /* Problem: We do only use fingerprints in the trustdb but + * we need the keyID here to indetify the key; we can only + * use that ugly hack to distinguish between 16 and 20 + * butes fpr - it does not work always so we better change + * the whole validation code to only work with + * fingerprints */ + fprlen = (!fpr[16] && !fpr[17] && !fpr[18] && !fpr[19])? 16:20; + keyid_from_fingerprint (fpr, fprlen, kid); + if (!add_utk (kid)) + log_info(_("key %s occurs more than once in the trustdb\n"), + keystr(kid)); + } + } + + /* Put any --trusted-key keys into the trustdb */ + for (k = user_utk_list; k; k = k->next) + { + if ( add_utk (k->kid) ) + { /* not yet in trustDB as ultimately trusted */ + PKT_public_key pk; + + memset (&pk, 0, sizeof pk); + rc = get_pubkey (&pk, k->kid); + if (rc) + log_info(_("key %s: no public key for trusted key - skipped\n"), + keystr(k->kid)); + else + { + update_ownertrust (&pk, + ((get_ownertrust (&pk) & ~TRUST_MASK) + | TRUST_ULTIMATE )); + release_public_key_parts (&pk); + } + + log_info (_("key %s marked as ultimately trusted\n"),keystr(k->kid)); + } + } + + /* release the helper table table */ + release_key_items (user_utk_list); + user_utk_list = NULL; + return; +} + + +/********************************************* + *********** TrustDB stuff ******************* + *********************************************/ + +/* + * Read a record but die if it does not exist + */ +static void +read_record (ulong recno, TRUSTREC *rec, int rectype ) +{ + int rc = tdbio_read_record (recno, rec, rectype); + if (rc) + { + log_error(_("trust record %lu, req type %d: read failed: %s\n"), + recno, rec->rectype, g10_errstr(rc) ); + tdbio_invalid(); + } + if (rectype != rec->rectype) + { + log_error(_("trust record %lu is not of requested type %d\n"), + rec->recnum, rectype); + tdbio_invalid(); + } +} + +/* + * Write a record and die on error + */ +static void +write_record (TRUSTREC *rec) +{ + int rc = tdbio_write_record (rec); + if (rc) + { + log_error(_("trust record %lu, type %d: write failed: %s\n"), + rec->recnum, rec->rectype, g10_errstr(rc) ); + tdbio_invalid(); + } +} + +/* + * sync the TrustDb and die on error + */ +static void +do_sync(void) +{ + int rc = tdbio_sync (); + if(rc) + { + log_error (_("trustdb: sync failed: %s\n"), g10_errstr(rc) ); + g10_exit(2); + } +} + +static const char * +trust_model_string(void) +{ + switch(opt.trust_model) + { + case TM_CLASSIC: return "classic"; + case TM_PGP: return "PGP"; + case TM_EXTERNAL: return "external"; + case TM_ALWAYS: return "always"; + case TM_DIRECT: return "direct"; + default: return "unknown"; + } +} + +/**************** + * Perform some checks over the trustdb + * level 0: only open the db + * 1: used for initial program startup + */ +int +setup_trustdb( int level, const char *dbname ) +{ + /* just store the args */ + if( trustdb_args.init ) + return 0; + trustdb_args.level = level; + trustdb_args.dbname = dbname? xstrdup(dbname): NULL; + return 0; +} + +void +init_trustdb() +{ + int level = trustdb_args.level; + const char* dbname = trustdb_args.dbname; + + if( trustdb_args.init ) + return; + + trustdb_args.init = 1; + + if(level==0 || level==1) + { + int rc = tdbio_set_dbname( dbname, !!level ); + if( rc ) + log_fatal("can't init trustdb: %s\n", g10_errstr(rc) ); + } + else + BUG(); + + if(opt.trust_model==TM_AUTO) + { + /* Try and set the trust model off of whatever the trustdb says + it is. */ + opt.trust_model=tdbio_read_model(); + + /* Sanity check this ;) */ + if(opt.trust_model!=TM_CLASSIC + && opt.trust_model!=TM_PGP + && opt.trust_model!=TM_EXTERNAL) + { + log_info(_("unable to use unknown trust model (%d) - " + "assuming %s trust model\n"),opt.trust_model,"PGP"); + opt.trust_model=TM_PGP; + } + + if(opt.verbose) + log_info(_("using %s trust model\n"),trust_model_string()); + } + + if(opt.trust_model==TM_PGP || opt.trust_model==TM_CLASSIC) + { + /* Verify the list of ultimately trusted keys and move the + --trusted-keys list there as well. */ + if(level==1) + verify_own_keys(); + + if(!tdbio_db_matches_options()) + pending_check_trustdb=1; + } +} + + +/*********************************************** + ************* Print helpers **************** + ***********************************************/ + +/**************** + * This function returns a letter for a trustvalue Trust flags + * are ignore. + */ +static int +trust_letter (unsigned int value) +{ + switch( (value & TRUST_MASK) ) + { + case TRUST_UNKNOWN: return '-'; + case TRUST_EXPIRED: return 'e'; + case TRUST_UNDEFINED: return 'q'; + case TRUST_NEVER: return 'n'; + case TRUST_MARGINAL: return 'm'; + case TRUST_FULLY: return 'f'; + case TRUST_ULTIMATE: return 'u'; + default: return '?'; + } +} + +/* NOTE TO TRANSLATOR: these strings are similar to those in + trust_value_to_string(), but are a fixed length. This is needed to + make attractive information listings where columns line up + properly. The value "10" should be the length of the strings you + choose to translate to. This is the length in printable columns. + It gets passed to atoi() so everything after the number is + essentially a comment and need not be translated. Either key and + uid are both NULL, or neither are NULL. */ +const char * +uid_trust_string_fixed(PKT_public_key *key,PKT_user_id *uid) +{ + if(!key && !uid) + return _("10 translator see trustdb.c:uid_trust_string_fixed"); + else if(uid->is_revoked || (key && key->is_revoked)) + return _("[ revoked]"); + else if(uid->is_expired) + return _("[ expired]"); + else if(key) + switch(get_validity(key,uid)&TRUST_MASK) + { + case TRUST_UNKNOWN: return _("[ unknown]"); + case TRUST_EXPIRED: return _("[ expired]"); + case TRUST_UNDEFINED: return _("[ undef ]"); + case TRUST_MARGINAL: return _("[marginal]"); + case TRUST_FULLY: return _("[ full ]"); + case TRUST_ULTIMATE: return _("[ultimate]"); + } + + return "err"; +} + +/* The strings here are similar to those in + pkclist.c:do_edit_ownertrust() */ +const char * +trust_value_to_string (unsigned int value) +{ + switch( (value & TRUST_MASK) ) + { + case TRUST_UNKNOWN: return _("unknown"); + case TRUST_EXPIRED: return _("expired"); + case TRUST_UNDEFINED: return _("undefined"); + case TRUST_NEVER: return _("never"); + case TRUST_MARGINAL: return _("marginal"); + case TRUST_FULLY: return _("full"); + case TRUST_ULTIMATE: return _("ultimate"); + default: return "err"; + } +} + +int +string_to_trust_value (const char *str) +{ + if(ascii_strcasecmp(str,"undefined")==0) + return TRUST_UNDEFINED; + else if(ascii_strcasecmp(str,"never")==0) + return TRUST_NEVER; + else if(ascii_strcasecmp(str,"marginal")==0) + return TRUST_MARGINAL; + else if(ascii_strcasecmp(str,"full")==0) + return TRUST_FULLY; + else if(ascii_strcasecmp(str,"ultimate")==0) + return TRUST_ULTIMATE; + else + return -1; +} + +/**************** + * Recreate the WoT but do not ask for new ownertrusts. Special + * feature: In batch mode and without a forced yes, this is only done + * when a check is due. This can be used to run the check from a crontab + */ +void +check_trustdb () +{ + init_trustdb(); + if(opt.trust_model==TM_PGP || opt.trust_model==TM_CLASSIC) + { + if (opt.batch && !opt.answer_yes) + { + ulong scheduled; + + scheduled = tdbio_read_nextcheck (); + if (!scheduled) + { + log_info (_("no need for a trustdb check\n")); + return; + } + + if (scheduled > make_timestamp ()) + { + log_info (_("next trustdb check due at %s\n"), + strtimestamp (scheduled)); + return; + } + } + + validate_keys (0); + } + else + log_info (_("no need for a trustdb check with `%s' trust model\n"), + trust_model_string()); +} + + +/* + * Recreate the WoT. + */ +void +update_trustdb() +{ + init_trustdb(); + if(opt.trust_model==TM_PGP || opt.trust_model==TM_CLASSIC) + validate_keys (1); + else + log_info (_("no need for a trustdb update with `%s' trust model\n"), + trust_model_string()); +} + +void +revalidation_mark (void) +{ + init_trustdb(); + /* we simply set the time for the next check to 1 (far back in 1970) + * so that a --update-trustdb will be scheduled */ + if (tdbio_write_nextcheck (1)) + do_sync (); + pending_check_trustdb = 1; +} + +int +trustdb_pending_check(void) +{ + return pending_check_trustdb; +} + +/* If the trustdb is dirty, and we're interactive, update it. + Otherwise, check it unless no-auto-check-trustdb is set. */ +void +trustdb_check_or_update(void) +{ + if(trustdb_pending_check()) + { + if(opt.interactive) + update_trustdb(); + else if(!opt.no_auto_check_trustdb) + check_trustdb(); + } +} + +void +read_trust_options(byte *trust_model,ulong *created,ulong *nextcheck, + byte *marginals,byte *completes,byte *cert_depth) +{ + TRUSTREC opts; + + init_trustdb(); + + read_record(0,&opts,RECTYPE_VER); + + if(trust_model) + *trust_model=opts.r.ver.trust_model; + if(created) + *created=opts.r.ver.created; + if(nextcheck) + *nextcheck=opts.r.ver.nextcheck; + if(marginals) + *marginals=opts.r.ver.marginals; + if(completes) + *completes=opts.r.ver.completes; + if(cert_depth) + *cert_depth=opts.r.ver.cert_depth; +} + +/*********************************************** + *********** Ownertrust et al. **************** + ***********************************************/ + +static int +read_trust_record (PKT_public_key *pk, TRUSTREC *rec) +{ + int rc; + + init_trustdb(); + rc = tdbio_search_trust_bypk (pk, rec); + if (rc == -1) + return -1; /* no record yet */ + if (rc) + { + log_error ("trustdb: searching trust record failed: %s\n", + g10_errstr (rc)); + return rc; + } + + if (rec->rectype != RECTYPE_TRUST) + { + log_error ("trustdb: record %lu is not a trust record\n", + rec->recnum); + return G10ERR_TRUSTDB; + } + + return 0; +} + +/**************** + * Return the assigned ownertrust value for the given public key. + * The key should be the primary key. + */ +unsigned int +get_ownertrust ( PKT_public_key *pk) +{ + TRUSTREC rec; + int rc; + + rc = read_trust_record (pk, &rec); + if (rc == -1) + return TRUST_UNKNOWN; /* no record yet */ + if (rc) + { + tdbio_invalid (); + return rc; /* actually never reached */ + } + + return rec.r.trust.ownertrust; +} + +unsigned int +get_min_ownertrust (PKT_public_key *pk) +{ + TRUSTREC rec; + int rc; + + rc = read_trust_record (pk, &rec); + if (rc == -1) + return TRUST_UNKNOWN; /* no record yet */ + if (rc) + { + tdbio_invalid (); + return rc; /* actually never reached */ + } + + return rec.r.trust.min_ownertrust; +} + +/* + * Same as get_ownertrust but this takes the minimum ownertrust value + * into into account, and will bump up the value as needed. + */ +static int +get_ownertrust_with_min (PKT_public_key *pk) +{ + unsigned int otrust,otrust_min; + + otrust = (get_ownertrust (pk) & TRUST_MASK); + otrust_min = get_min_ownertrust (pk); + if(otrust<otrust_min) + { + /* If the trust that the user has set is less than the trust + that was calculated from a trust signature chain, use the + higher of the two. We do this here and not in + get_ownertrust since the underlying ownertrust should not + really be set - just the appearance of the ownertrust. */ + + otrust=otrust_min; + } + + return otrust; +} + +/* + * Same as get_ownertrust but return a trust letter instead of an + * value. This takes the minimum ownertrust value into account. + */ +int +get_ownertrust_info (PKT_public_key *pk) +{ + return trust_letter(get_ownertrust_with_min(pk)); +} + +/* + * Same as get_ownertrust but return a trust string instead of an + * value. This takes the minimum ownertrust value into account. + */ +const char * +get_ownertrust_string (PKT_public_key *pk) +{ + return trust_value_to_string(get_ownertrust_with_min(pk)); +} + +/* + * Set the trust value of the given public key to the new value. + * The key should be a primary one. + */ +void +update_ownertrust (PKT_public_key *pk, unsigned int new_trust ) +{ + TRUSTREC rec; + int rc; + + rc = read_trust_record (pk, &rec); + if (!rc) + { + if (DBG_TRUST) + log_debug ("update ownertrust from %u to %u\n", + (unsigned int)rec.r.trust.ownertrust, new_trust ); + if (rec.r.trust.ownertrust != new_trust) + { + rec.r.trust.ownertrust = new_trust; + write_record( &rec ); + revalidation_mark (); + do_sync (); + } + } + else if (rc == -1) + { /* no record yet - create a new one */ + size_t dummy; + + if (DBG_TRUST) + log_debug ("insert ownertrust %u\n", new_trust ); + + memset (&rec, 0, sizeof rec); + rec.recnum = tdbio_new_recnum (); + rec.rectype = RECTYPE_TRUST; + fingerprint_from_pk (pk, rec.r.trust.fingerprint, &dummy); + rec.r.trust.ownertrust = new_trust; + write_record (&rec); + revalidation_mark (); + do_sync (); + rc = 0; + } + else + { + tdbio_invalid (); + } +} + +static void +update_min_ownertrust (u32 *kid, unsigned int new_trust ) +{ + PKT_public_key *pk; + TRUSTREC rec; + int rc; + + pk = xmalloc_clear (sizeof *pk); + rc = get_pubkey (pk, kid); + if (rc) + { + log_error(_("public key %s not found: %s\n"),keystr(kid),g10_errstr(rc)); + return; + } + + rc = read_trust_record (pk, &rec); + if (!rc) + { + if (DBG_TRUST) + log_debug ("key %08lX%08lX: update min_ownertrust from %u to %u\n", + (ulong)kid[0],(ulong)kid[1], + (unsigned int)rec.r.trust.min_ownertrust, + new_trust ); + if (rec.r.trust.min_ownertrust != new_trust) + { + rec.r.trust.min_ownertrust = new_trust; + write_record( &rec ); + revalidation_mark (); + do_sync (); + } + } + else if (rc == -1) + { /* no record yet - create a new one */ + size_t dummy; + + if (DBG_TRUST) + log_debug ("insert min_ownertrust %u\n", new_trust ); + + memset (&rec, 0, sizeof rec); + rec.recnum = tdbio_new_recnum (); + rec.rectype = RECTYPE_TRUST; + fingerprint_from_pk (pk, rec.r.trust.fingerprint, &dummy); + rec.r.trust.min_ownertrust = new_trust; + write_record (&rec); + revalidation_mark (); + do_sync (); + rc = 0; + } + else + { + tdbio_invalid (); + } +} + +/* Clear the ownertrust and min_ownertrust values. Return true if a + change actually happened. */ +int +clear_ownertrusts (PKT_public_key *pk) +{ + TRUSTREC rec; + int rc; + + rc = read_trust_record (pk, &rec); + if (!rc) + { + if (DBG_TRUST) + { + log_debug ("clearing ownertrust (old value %u)\n", + (unsigned int)rec.r.trust.ownertrust); + log_debug ("clearing min_ownertrust (old value %u)\n", + (unsigned int)rec.r.trust.min_ownertrust); + } + if (rec.r.trust.ownertrust || rec.r.trust.min_ownertrust) + { + rec.r.trust.ownertrust = 0; + rec.r.trust.min_ownertrust = 0; + write_record( &rec ); + revalidation_mark (); + do_sync (); + return 1; + } + } + else if (rc != -1) + { + tdbio_invalid (); + } + return 0; +} + +/* + * Note: Caller has to do a sync + */ +static void +update_validity (PKT_public_key *pk, PKT_user_id *uid, + int depth, int validity) +{ + TRUSTREC trec, vrec; + int rc; + ulong recno; + + namehash_from_uid(uid); + + rc = read_trust_record (pk, &trec); + if (rc && rc != -1) + { + tdbio_invalid (); + return; + } + if (rc == -1) /* no record yet - create a new one */ + { + size_t dummy; + + rc = 0; + memset (&trec, 0, sizeof trec); + trec.recnum = tdbio_new_recnum (); + trec.rectype = RECTYPE_TRUST; + fingerprint_from_pk (pk, trec.r.trust.fingerprint, &dummy); + trec.r.trust.ownertrust = 0; + } + + /* locate an existing one */ + recno = trec.r.trust.validlist; + while (recno) + { + read_record (recno, &vrec, RECTYPE_VALID); + if ( !memcmp (vrec.r.valid.namehash, uid->namehash, 20) ) + break; + recno = vrec.r.valid.next; + } + + if (!recno) /* insert a new validity record */ + { + memset (&vrec, 0, sizeof vrec); + vrec.recnum = tdbio_new_recnum (); + vrec.rectype = RECTYPE_VALID; + memcpy (vrec.r.valid.namehash, uid->namehash, 20); + vrec.r.valid.next = trec.r.trust.validlist; + trec.r.trust.validlist = vrec.recnum; + } + vrec.r.valid.validity = validity; + vrec.r.valid.full_count = uid->help_full_count; + vrec.r.valid.marginal_count = uid->help_marginal_count; + write_record (&vrec); + trec.r.trust.depth = depth; + write_record (&trec); +} + + +/*********************************************** + ********* Query trustdb values ************** + ***********************************************/ + +/* Return true if key is disabled */ +int +cache_disabled_value(PKT_public_key *pk) +{ + int rc; + TRUSTREC trec; + int disabled=0; + + if(pk->is_disabled) + return (pk->is_disabled==2); + + init_trustdb(); + + rc = read_trust_record (pk, &trec); + if (rc && rc != -1) + { + tdbio_invalid (); + goto leave; + } + if (rc == -1) /* no record found, so assume not disabled */ + goto leave; + + if(trec.r.trust.ownertrust & TRUST_FLAG_DISABLED) + disabled=1; + + /* Cache it for later so we don't need to look at the trustdb every + time */ + if(disabled) + pk->is_disabled=2; + else + pk->is_disabled=1; + + leave: + return disabled; +} + +void +check_trustdb_stale(void) +{ + static int did_nextcheck=0; + + init_trustdb (); + if (!did_nextcheck + && (opt.trust_model==TM_PGP || opt.trust_model==TM_CLASSIC)) + { + ulong scheduled; + + did_nextcheck = 1; + scheduled = tdbio_read_nextcheck (); + if (scheduled && scheduled <= make_timestamp ()) + { + if (opt.no_auto_check_trustdb) + { + pending_check_trustdb = 1; + log_info (_("please do a --check-trustdb\n")); + } + else + { + log_info (_("checking the trustdb\n")); + validate_keys (0); + } + } + } +} + +/* + * Return the validity information for PK. If the namehash is not + * NULL, the validity of the corresponsing user ID is returned, + * otherwise, a reasonable value for the entire key is returned. + */ +unsigned int +get_validity (PKT_public_key *pk, PKT_user_id *uid) +{ + TRUSTREC trec, vrec; + int rc; + ulong recno; + unsigned int validity; + u32 kid[2]; + PKT_public_key *main_pk; + + if(uid) + namehash_from_uid(uid); + + init_trustdb (); + check_trustdb_stale(); + + keyid_from_pk (pk, kid); + if (pk->main_keyid[0] != kid[0] || pk->main_keyid[1] != kid[1]) + { /* this is a subkey - get the mainkey */ + main_pk = xmalloc_clear (sizeof *main_pk); + rc = get_pubkey (main_pk, pk->main_keyid); + if (rc) + { + char *tempkeystr=xstrdup(keystr(pk->main_keyid)); + log_error ("error getting main key %s of subkey %s: %s\n", + tempkeystr, keystr(kid), g10_errstr(rc)); + xfree(tempkeystr); + validity = TRUST_UNKNOWN; + goto leave; + } + } + else + main_pk = pk; + + if(opt.trust_model==TM_DIRECT) + { + /* Note that this happens BEFORE any user ID stuff is checked. + The direct trust model applies to keys as a whole. */ + validity=get_ownertrust(main_pk); + goto leave; + } + + rc = read_trust_record (main_pk, &trec); + if (rc && rc != -1) + { + tdbio_invalid (); + return 0; + } + if (rc == -1) /* no record found */ + { + validity = TRUST_UNKNOWN; + goto leave; + } + + /* loop over all user IDs */ + recno = trec.r.trust.validlist; + validity = 0; + while (recno) + { + read_record (recno, &vrec, RECTYPE_VALID); + + if(uid) + { + /* If a user ID is given we return the validity for that + user ID ONLY. If the namehash is not found, then there + is no validity at all (i.e. the user ID wasn't + signed). */ + if(memcmp(vrec.r.valid.namehash,uid->namehash,20)==0) + { + validity=(vrec.r.valid.validity & TRUST_MASK); + break; + } + } + else + { + /* If no namehash is given, we take the maximum validity + over all user IDs */ + if ( validity < (vrec.r.valid.validity & TRUST_MASK) ) + validity = (vrec.r.valid.validity & TRUST_MASK); + } + + recno = vrec.r.valid.next; + } + + if ( (trec.r.trust.ownertrust & TRUST_FLAG_DISABLED) ) + { + validity |= TRUST_FLAG_DISABLED; + pk->is_disabled=2; + } + else + pk->is_disabled=1; + + leave: + /* set some flags direct from the key */ + if (main_pk->is_revoked) + validity |= TRUST_FLAG_REVOKED; + if (main_pk != pk && pk->is_revoked) + validity |= TRUST_FLAG_SUB_REVOKED; + /* Note: expiration is a trust value and not a flag - don't know why + * I initially designed it that way */ + if (main_pk->has_expired || pk->has_expired) + validity = (validity & ~TRUST_MASK) | TRUST_EXPIRED; + + if (pending_check_trustdb) + validity |= TRUST_FLAG_PENDING_CHECK; + + if (main_pk != pk) + free_public_key (main_pk); + return validity; +} + +int +get_validity_info (PKT_public_key *pk, PKT_user_id *uid) +{ + int trustlevel; + + trustlevel = get_validity (pk, uid); + if( trustlevel & TRUST_FLAG_REVOKED ) + return 'r'; + return trust_letter ( trustlevel ); +} + +const char * +get_validity_string (PKT_public_key *pk, PKT_user_id *uid) +{ + int trustlevel; + + trustlevel = get_validity (pk, uid); + if( trustlevel & TRUST_FLAG_REVOKED ) + return _("revoked"); + return trust_value_to_string(trustlevel); +} + +static void +get_validity_counts (PKT_public_key *pk, PKT_user_id *uid) +{ + TRUSTREC trec, vrec; + ulong recno; + + if(pk==NULL || uid==NULL) + BUG(); + + namehash_from_uid(uid); + + uid->help_marginal_count=uid->help_full_count=0; + + init_trustdb (); + + if(read_trust_record (pk, &trec)!=0) + return; + + /* loop over all user IDs */ + recno = trec.r.trust.validlist; + while (recno) + { + read_record (recno, &vrec, RECTYPE_VALID); + + if(memcmp(vrec.r.valid.namehash,uid->namehash,20)==0) + { + uid->help_marginal_count=vrec.r.valid.marginal_count; + uid->help_full_count=vrec.r.valid.full_count; + /* printf("Fetched marginal %d, full %d\n",uid->help_marginal_count,uid->help_full_count); */ + break; + } + + recno = vrec.r.valid.next; + } +} + +void +list_trust_path( const char *username ) +{ +} + +/**************** + * Enumerate all keys, which are needed to build all trust paths for + * the given key. This function does not return the key itself or + * the ultimate key (the last point in cerificate chain). Only + * certificate chains which ends up at an ultimately trusted key + * are listed. If ownertrust or validity is not NULL, the corresponding + * value for the returned LID is also returned in these variable(s). + * + * 1) create a void pointer and initialize it to NULL + * 2) pass this void pointer by reference to this function. + * Set lid to the key you want to enumerate and pass it by reference. + * 3) call this function as long as it does not return -1 + * to indicate EOF. LID does contain the next key used to build the web + * 4) Always call this function a last time with LID set to NULL, + * so that it can free its context. + * + * Returns: -1 on EOF or the level of the returned LID + */ +int +enum_cert_paths( void **context, ulong *lid, + unsigned *ownertrust, unsigned *validity ) +{ + return -1; +} + + +/**************** + * Print the current path + */ +void +enum_cert_paths_print( void **context, FILE *fp, + int refresh, ulong selected_lid ) +{ + return; +} + + + +/**************************************** + *********** NEW NEW NEW **************** + ****************************************/ + +static int +ask_ownertrust (u32 *kid,int minimum) +{ + PKT_public_key *pk; + int rc; + int ot; + + pk = xmalloc_clear (sizeof *pk); + rc = get_pubkey (pk, kid); + if (rc) + { + log_error (_("public key %s not found: %s\n"), + keystr(kid), g10_errstr(rc) ); + return TRUST_UNKNOWN; + } + + if(opt.force_ownertrust) + { + log_info("force trust for key %s to %s\n", + keystr(kid),trust_value_to_string(opt.force_ownertrust)); + update_ownertrust(pk,opt.force_ownertrust); + ot=opt.force_ownertrust; + } + else + { + ot=edit_ownertrust(pk,0); + if(ot>0) + ot = get_ownertrust (pk); + else if(ot==0) + ot = minimum?minimum:TRUST_UNDEFINED; + else + ot = -1; /* quit */ + } + + free_public_key( pk ); + + return ot; +} + + +static void +mark_keyblock_seen (KeyHashTable tbl, KBNODE node) +{ + for ( ;node; node = node->next ) + if (node->pkt->pkttype == PKT_PUBLIC_KEY + || node->pkt->pkttype == PKT_PUBLIC_SUBKEY) + { + u32 aki[2]; + + keyid_from_pk (node->pkt->pkt.public_key, aki); + add_key_hash_table (tbl, aki); + } +} + + +static void +dump_key_array (int depth, struct key_array *keys) +{ + struct key_array *kar; + + for (kar=keys; kar->keyblock; kar++) + { + KBNODE node = kar->keyblock; + u32 kid[2]; + + keyid_from_pk(node->pkt->pkt.public_key, kid); + printf ("%d:%08lX%08lX:K::%c::::\n", + depth, (ulong)kid[0], (ulong)kid[1], '?'); + + for (; node; node = node->next) + { + if (node->pkt->pkttype == PKT_USER_ID) + { + int len = node->pkt->pkt.user_id->len; + + if (len > 30) + len = 30; + printf ("%d:%08lX%08lX:U:::%c:::", + depth, (ulong)kid[0], (ulong)kid[1], + (node->flag & 4)? 'f': + (node->flag & 2)? 'm': + (node->flag & 1)? 'q':'-'); + print_string (stdout, node->pkt->pkt.user_id->name, len, ':'); + putchar (':'); + putchar ('\n'); + } + } + } +} + + +static void +store_validation_status (int depth, KBNODE keyblock, KeyHashTable stored) +{ + KBNODE node; + int status; + int any = 0; + + for (node=keyblock; node; node = node->next) + { + if (node->pkt->pkttype == PKT_USER_ID) + { + PKT_user_id *uid = node->pkt->pkt.user_id; + if (node->flag & 4) + status = TRUST_FULLY; + else if (node->flag & 2) + status = TRUST_MARGINAL; + else if (node->flag & 1) + status = TRUST_UNDEFINED; + else + status = 0; + + if (status) + { + update_validity (keyblock->pkt->pkt.public_key, + uid, depth, status); + + mark_keyblock_seen(stored,keyblock); + + any = 1; + } + } + } + + if (any) + do_sync (); +} + +/* + * check whether the signature sig is in the klist k + */ +static struct key_item * +is_in_klist (struct key_item *k, PKT_signature *sig) +{ + for (; k; k = k->next) + { + if (k->kid[0] == sig->keyid[0] && k->kid[1] == sig->keyid[1]) + return k; + } + return NULL; +} + +/* + * Mark the signature of the given UID which are used to certify it. + * To do this, we first revmove all signatures which are not valid and + * from the remain ones we look for the latest one. If this is not a + * certification revocation signature we mark the signature by setting + * node flag bit 8. Revocations are marked with flag 11, and sigs + * from unavailable keys are marked with flag 12. Note that flag bits + * 9 and 10 are used for internal purposes. + */ +static void +mark_usable_uid_certs (KBNODE keyblock, KBNODE uidnode, + u32 *main_kid, struct key_item *klist, + u32 curtime, u32 *next_expire) +{ + KBNODE node; + PKT_signature *sig; + + /* first check all signatures */ + for (node=uidnode->next; node; node = node->next) + { + int rc; + + node->flag &= ~(1<<8 | 1<<9 | 1<<10 | 1<<11 | 1<<12); + if (node->pkt->pkttype == PKT_USER_ID + || node->pkt->pkttype == PKT_PUBLIC_SUBKEY) + break; /* ready */ + if (node->pkt->pkttype != PKT_SIGNATURE) + continue; + sig = node->pkt->pkt.signature; + if (main_kid + && sig->keyid[0] == main_kid[0] && sig->keyid[1] == main_kid[1]) + continue; /* ignore self-signatures if we pass in a main_kid */ + if (!IS_UID_SIG(sig) && !IS_UID_REV(sig)) + continue; /* we only look at these signature classes */ + if(sig->sig_class>=0x11 && sig->sig_class<=0x13 && + sig->sig_class-0x10<opt.min_cert_level) + continue; /* treat anything under our min_cert_level as an + invalid signature */ + if (klist && !is_in_klist (klist, sig)) + continue; /* no need to check it then */ + if ((rc=check_key_signature (keyblock, node, NULL))) + { + /* we ignore anything that won't verify, but tag the + no_pubkey case */ + if(rc==G10ERR_NO_PUBKEY) + node->flag |= 1<<12; + continue; + } + node->flag |= 1<<9; + } + /* reset the remaining flags */ + for (; node; node = node->next) + node->flag &= ~(1<<8 | 1<<9 | 1<<10 | 1<<11 | 1<<12); + + /* kbnode flag usage: bit 9 is here set for signatures to consider, + * bit 10 will be set by the loop to keep track of keyIDs already + * processed, bit 8 will be set for the usable signatures, and bit + * 11 will be set for usable revocations. */ + + /* for each cert figure out the latest valid one */ + for (node=uidnode->next; node; node = node->next) + { + KBNODE n, signode; + u32 kid[2]; + u32 sigdate; + + if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY) + break; + if ( !(node->flag & (1<<9)) ) + continue; /* not a node to look at */ + if ( (node->flag & (1<<10)) ) + continue; /* signature with a keyID already processed */ + node->flag |= (1<<10); /* mark this node as processed */ + sig = node->pkt->pkt.signature; + signode = node; + sigdate = sig->timestamp; + kid[0] = sig->keyid[0]; kid[1] = sig->keyid[1]; + + /* Now find the latest and greatest signature */ + for (n=uidnode->next; n; n = n->next) + { + if (n->pkt->pkttype == PKT_PUBLIC_SUBKEY) + break; + if ( !(n->flag & (1<<9)) ) + continue; + if ( (n->flag & (1<<10)) ) + continue; /* shortcut already processed signatures */ + sig = n->pkt->pkt.signature; + if (kid[0] != sig->keyid[0] || kid[1] != sig->keyid[1]) + continue; + n->flag |= (1<<10); /* mark this node as processed */ + + /* If signode is nonrevocable and unexpired and n isn't, + then take signode (skip). It doesn't matter which is + older: if signode was older then we don't want to take n + as signode is nonrevocable. If n was older then we're + automatically fine. */ + + if(((IS_UID_SIG(signode->pkt->pkt.signature) && + !signode->pkt->pkt.signature->flags.revocable && + (signode->pkt->pkt.signature->expiredate==0 || + signode->pkt->pkt.signature->expiredate>curtime))) && + (!(IS_UID_SIG(n->pkt->pkt.signature) && + !n->pkt->pkt.signature->flags.revocable && + (n->pkt->pkt.signature->expiredate==0 || + n->pkt->pkt.signature->expiredate>curtime)))) + continue; + + /* If n is nonrevocable and unexpired and signode isn't, + then take n. Again, it doesn't matter which is older: if + n was older then we don't want to take signode as n is + nonrevocable. If signode was older then we're + automatically fine. */ + + if((!(IS_UID_SIG(signode->pkt->pkt.signature) && + !signode->pkt->pkt.signature->flags.revocable && + (signode->pkt->pkt.signature->expiredate==0 || + signode->pkt->pkt.signature->expiredate>curtime))) && + ((IS_UID_SIG(n->pkt->pkt.signature) && + !n->pkt->pkt.signature->flags.revocable && + (n->pkt->pkt.signature->expiredate==0 || + n->pkt->pkt.signature->expiredate>curtime)))) + { + signode = n; + sigdate = sig->timestamp; + continue; + } + + /* At this point, if it's newer, it goes in as the only + remaining possibilities are signode and n are both either + revocable or expired or both nonrevocable and unexpired. + If the timestamps are equal take the later ordered + packet, presuming that the key packets are hopefully in + their original order. */ + + if (sig->timestamp >= sigdate) + { + signode = n; + sigdate = sig->timestamp; + } + } + + sig = signode->pkt->pkt.signature; + if (IS_UID_SIG (sig)) + { /* this seems to be a usable one which is not revoked. + * Just need to check whether there is an expiration time, + * We do the expired certification after finding a suitable + * certification, the assumption is that a signator does not + * want that after the expiration of his certificate the + * system falls back to an older certification which has a + * different expiration time */ + const byte *p; + u32 expire; + + p = parse_sig_subpkt (sig->hashed, SIGSUBPKT_SIG_EXPIRE, NULL ); + expire = p? sig->timestamp + buffer_to_u32(p) : 0; + + if (expire==0 || expire > curtime ) + { + signode->flag |= (1<<8); /* yeah, found a good cert */ + if (next_expire && expire && expire < *next_expire) + *next_expire = expire; + } + } + else + signode->flag |= (1<<11); + } +} + +static int +clean_sigs_from_uid(KBNODE keyblock,KBNODE uidnode,int noisy,int self_only) +{ + int deleted=0; + KBNODE node; + u32 keyid[2]; + + assert(keyblock->pkt->pkttype==PKT_PUBLIC_KEY); + + keyid_from_pk(keyblock->pkt->pkt.public_key,keyid); + + /* Passing in a 0 for current time here means that we'll never weed + out an expired sig. This is correct behavior since we want to + keep the most recent expired sig in a series. */ + mark_usable_uid_certs(keyblock,uidnode,NULL,NULL,0,NULL); + + /* What we want to do here is remove signatures that are not + considered as part of the trust calculations. Thus, all invalid + signatures are out, as are any signatures that aren't the last of + a series of uid sigs or revocations It breaks down like this: + coming out of mark_usable_uid_certs, if a sig is unflagged, it is + not even a candidate. If a sig has flag 9 or 10, that means it + was selected as a candidate and vetted. If a sig has flag 8 it + is a usable signature. If a sig has flag 11 it is a usable + revocation. If a sig has flag 12 it was issued by an unavailable + key. "Usable" here means the most recent valid + signature/revocation in a series from a particular signer. + + Delete everything that isn't a usable uid sig (which might be + expired), a usable revocation, or a sig from an unavailable + key. */ + + for(node=uidnode->next; + node && node->pkt->pkttype==PKT_SIGNATURE; + node=node->next) + { + int keep=self_only?(node->pkt->pkt.signature->keyid[0]==keyid[0] + && node->pkt->pkt.signature->keyid[1]==keyid[1]):1; + + /* Keep usable uid sigs ... */ + if((node->flag & (1<<8)) && keep) + continue; + + /* ... and usable revocations... */ + if((node->flag & (1<<11)) && keep) + continue; + + /* ... and sigs from unavailable keys. */ + /* disabled for now since more people seem to want sigs from + unavailable keys removed altogether. */ + /* + if(node->flag & (1<<12)) + continue; + */ + + /* Everything else we delete */ + + /* At this point, if 12 is set, the signing key was unavailable. + If 9 or 10 is set, it's superceded. Otherwise, it's + invalid. */ + + if(noisy) + log_info("removing signature from key %s on user ID \"%s\": %s\n", + keystr(node->pkt->pkt.signature->keyid), + uidnode->pkt->pkt.user_id->name, + node->flag&(1<<12)?"key unavailable": + node->flag&(1<<9)?"signature superceded":"invalid signature"); + + delete_kbnode(node); + deleted++; + } + + return deleted; +} + +/* This is substantially easier than clean_sigs_from_uid since we just + have to establish if the uid has a valid self-sig, is not revoked, + and is not expired. Note that this does not take into account + whether the uid has a trust path to it - just whether the keyholder + themselves has certified the uid. Returns true if the uid was + compacted. To "compact" a user ID, we simply remove ALL signatures + except the self-sig that caused the user ID to be remove-worthy. + We don't actually remove the user ID packet itself since it might + be ressurected in a later merge. Note that this function requires + that the caller has already done a merge_keys_and_selfsig(). + + TODO: change the import code to allow importing a uid with only a + revocation if the uid already exists on the keyring. */ + +static int +clean_uid_from_key(KBNODE keyblock,KBNODE uidnode,int noisy) +{ + KBNODE node; + PKT_user_id *uid=uidnode->pkt->pkt.user_id; + int deleted=0; + + assert(keyblock->pkt->pkttype==PKT_PUBLIC_KEY); + assert(uidnode->pkt->pkttype==PKT_USER_ID); + + /* Skip valid user IDs, compacted user IDs, and non-self-signed user + IDs if --allow-non-selfsigned-uid is set. */ + if(uid->created || uid->flags.compacted + || (!uid->is_expired && !uid->is_revoked + && opt.allow_non_selfsigned_uid)) + return 0; + + for(node=uidnode->next; + node && node->pkt->pkttype==PKT_SIGNATURE; + node=node->next) + if(!node->pkt->pkt.signature->flags.chosen_selfsig) + { + delete_kbnode(node); + deleted=1; + uidnode->pkt->pkt.user_id->flags.compacted=1; + } + + if(noisy) + { + const char *reason; + char *user=utf8_to_native(uid->name,uid->len,0); + + if(uid->is_revoked) + reason=_("revoked"); + else if(uid->is_expired) + reason=_("expired"); + else + reason=_("invalid"); + + log_info("compacting user ID \"%s\" on key %s: %s\n", + user,keystr_from_pk(keyblock->pkt->pkt.public_key), + reason); + + xfree(user); + } + + return deleted; +} + +/* Needs to be called after a merge_keys_and_selfsig() */ +void +clean_one_uid(KBNODE keyblock,KBNODE uidnode,int noisy,int self_only, + int *uids_cleaned,int *sigs_cleaned) +{ + int dummy; + + assert(keyblock->pkt->pkttype==PKT_PUBLIC_KEY); + assert(uidnode->pkt->pkttype==PKT_USER_ID); + + if(!uids_cleaned) + uids_cleaned=&dummy; + + if(!sigs_cleaned) + sigs_cleaned=&dummy; + + /* Do clean_uid_from_key first since if it fires off, we don't + have to bother with the other */ + *uids_cleaned+=clean_uid_from_key(keyblock,uidnode,noisy); + if(!uidnode->pkt->pkt.user_id->flags.compacted) + *sigs_cleaned+=clean_sigs_from_uid(keyblock,uidnode,noisy,self_only); +} + +void +clean_key(KBNODE keyblock,int noisy,int self_only, + int *uids_cleaned,int *sigs_cleaned) +{ + KBNODE uidnode; + + merge_keys_and_selfsig(keyblock); + + for(uidnode=keyblock->next; + uidnode && uidnode->pkt->pkttype!=PKT_PUBLIC_SUBKEY; + uidnode=uidnode->next) + if(uidnode->pkt->pkttype==PKT_USER_ID) + clean_one_uid(keyblock,uidnode,noisy,self_only, + uids_cleaned,sigs_cleaned); +} + +/* Used by validate_one_keyblock to confirm a regexp within a trust + signature. Returns 1 for match, and 0 for no match or regex + error. */ +static int +check_regexp(const char *expr,const char *string) +{ +#ifdef DISABLE_REGEX + /* When DISABLE_REGEX is defined, assume all regexps do not + match. */ + return 0; +#elif defined(__riscos__) + return riscos_check_regexp(expr, string, DBG_TRUST); +#else + int ret; + regex_t pat; + + if(regcomp(&pat,expr,REG_ICASE|REG_NOSUB|REG_EXTENDED)!=0) + return 0; + + ret=regexec(&pat,string,0,NULL,0); + + regfree(&pat); + + if(DBG_TRUST) + log_debug("regexp `%s' on `%s': %s\n",expr,string,ret==0?"YES":"NO"); + + return (ret==0); +#endif +} + +/* + * Return true if the key is signed by one of the keys in the given + * key ID list. User IDs with a valid signature are marked by node + * flags as follows: + * flag bit 0: There is at least one signature + * 1: There is marginal confidence that this is a legitimate uid + * 2: There is full confidence that this is a legitimate uid. + * 8: Used for internal purposes. + * 9: Ditto (in mark_usable_uid_certs()) + * 10: Ditto (ditto) + * This function assumes that all kbnode flags are cleared on entry. + */ +static int +validate_one_keyblock (KBNODE kb, struct key_item *klist, + u32 curtime, u32 *next_expire) +{ + struct key_item *kr; + KBNODE node, uidnode=NULL; + PKT_user_id *uid=NULL; + PKT_public_key *pk = kb->pkt->pkt.public_key; + u32 main_kid[2]; + int issigned=0, any_signed = 0; + + keyid_from_pk(pk, main_kid); + for (node=kb; node; node = node->next) + { + /* A bit of discussion here: is it better for the web of trust + to be built among only self-signed uids? On the one hand, a + self-signed uid is a statement that the key owner definitely + intended that uid to be there, but on the other hand, a + signed (but not self-signed) uid does carry trust, of a sort, + even if it is a statement being made by people other than the + key owner "through" the uids on the key owner's key. I'm + going with the latter. However, if the user ID was + explicitly revoked, or passively allowed to expire, that + should stop validity through the user ID until it is + resigned. -dshaw */ + + if (node->pkt->pkttype == PKT_USER_ID + && !node->pkt->pkt.user_id->is_revoked + && !node->pkt->pkt.user_id->is_expired) + { + if (uidnode && issigned) + { + if (uid->help_full_count >= opt.completes_needed + || uid->help_marginal_count >= opt.marginals_needed ) + uidnode->flag |= 4; + else if (uid->help_full_count || uid->help_marginal_count) + uidnode->flag |= 2; + uidnode->flag |= 1; + any_signed = 1; + } + uidnode = node; + uid=uidnode->pkt->pkt.user_id; + + /* If the selfsig is going to expire... */ + if(uid->expiredate && uid->expiredate<*next_expire) + *next_expire = uid->expiredate; + + issigned = 0; + get_validity_counts(pk,uid); + mark_usable_uid_certs (kb, uidnode, main_kid, klist, + curtime, next_expire); + } + else if (node->pkt->pkttype == PKT_SIGNATURE + && (node->flag & (1<<8)) && uid) + { + /* Note that we are only seeing unrevoked sigs here */ + PKT_signature *sig = node->pkt->pkt.signature; + + kr = is_in_klist (klist, sig); + /* If the trust_regexp does not match, it's as if the sig + did not exist. This is safe for non-trust sigs as well + since we don't accept a regexp on the sig unless it's a + trust sig. */ + if (kr && (kr->trust_regexp==NULL || opt.trust_model!=TM_PGP || + (uidnode && check_regexp(kr->trust_regexp, + uidnode->pkt->pkt.user_id->name)))) + { + if(DBG_TRUST && opt.trust_model==TM_PGP && sig->trust_depth) + log_debug("trust sig on %s, sig depth is %d, kr depth is %d\n", + uidnode->pkt->pkt.user_id->name,sig->trust_depth, + kr->trust_depth); + + /* Are we part of a trust sig chain? We always favor + the latest trust sig, rather than the greater or + lesser trust sig or value. I could make a decent + argument for any of these cases, but this seems to be + what PGP does, and I'd like to be compatible. -dms */ + if(opt.trust_model==TM_PGP && sig->trust_depth + && pk->trust_timestamp<=sig->timestamp + && (sig->trust_depth<=kr->trust_depth + || kr->ownertrust==TRUST_ULTIMATE)) + { + /* If we got here, we know that: + + this is a trust sig. + + it's a newer trust sig than any previous trust + sig on this key (not uid). + + it is legal in that it was either generated by an + ultimate key, or a key that was part of a trust + chain, and the depth does not violate the + original trust sig. + + if there is a regexp attached, it matched + successfully. + */ + + if(DBG_TRUST) + log_debug("replacing trust value %d with %d and " + "depth %d with %d\n", + pk->trust_value,sig->trust_value, + pk->trust_depth,sig->trust_depth); + + pk->trust_value=sig->trust_value; + pk->trust_depth=sig->trust_depth-1; + + /* If the trust sig contains a regexp, record it + on the pk for the next round. */ + if(sig->trust_regexp) + pk->trust_regexp=sig->trust_regexp; + } + + if (kr->ownertrust == TRUST_ULTIMATE) + uid->help_full_count = opt.completes_needed; + else if (kr->ownertrust == TRUST_FULLY) + uid->help_full_count++; + else if (kr->ownertrust == TRUST_MARGINAL) + uid->help_marginal_count++; + issigned = 1; + } + } + } + + if (uidnode && issigned) + { + if (uid->help_full_count >= opt.completes_needed + || uid->help_marginal_count >= opt.marginals_needed ) + uidnode->flag |= 4; + else if (uid->help_full_count || uid->help_marginal_count) + uidnode->flag |= 2; + uidnode->flag |= 1; + any_signed = 1; + } + + return any_signed; +} + + +static int +search_skipfnc (void *opaque, u32 *kid, PKT_user_id *dummy) +{ + return test_key_hash_table ((KeyHashTable)opaque, kid); +} + + +/* + * Scan all keys and return a key_array of all suitable keys from + * kllist. The caller has to pass keydb handle so that we don't use + * to create our own. Returns either a key_array or NULL in case of + * an error. No results found are indicated by an empty array. + * Caller hast to release the returned array. + */ +static struct key_array * +validate_key_list (KEYDB_HANDLE hd, KeyHashTable full_trust, + struct key_item *klist, u32 curtime, u32 *next_expire) +{ + KBNODE keyblock = NULL; + struct key_array *keys = NULL; + size_t nkeys, maxkeys; + int rc; + KEYDB_SEARCH_DESC desc; + + maxkeys = 1000; + keys = xmalloc ((maxkeys+1) * sizeof *keys); + nkeys = 0; + + rc = keydb_search_reset (hd); + if (rc) + { + log_error ("keydb_search_reset failed: %s\n", g10_errstr(rc)); + xfree (keys); + return NULL; + } + + memset (&desc, 0, sizeof desc); + desc.mode = KEYDB_SEARCH_MODE_FIRST; + desc.skipfnc = search_skipfnc; + desc.skipfncvalue = full_trust; + rc = keydb_search (hd, &desc, 1); + if (rc == -1) + { + keys[nkeys].keyblock = NULL; + return keys; + } + if (rc) + { + log_error ("keydb_search_first failed: %s\n", g10_errstr(rc)); + xfree (keys); + return NULL; + } + + desc.mode = KEYDB_SEARCH_MODE_NEXT; /* change mode */ + do + { + PKT_public_key *pk; + + rc = keydb_get_keyblock (hd, &keyblock); + if (rc) + { + log_error ("keydb_get_keyblock failed: %s\n", g10_errstr(rc)); + xfree (keys); + return NULL; + } + + if ( keyblock->pkt->pkttype != PKT_PUBLIC_KEY) + { + log_debug ("ooops: invalid pkttype %d encountered\n", + keyblock->pkt->pkttype); + dump_kbnode (keyblock); + release_kbnode(keyblock); + continue; + } + + /* prepare the keyblock for further processing */ + merge_keys_and_selfsig (keyblock); + clear_kbnode_flags (keyblock); + pk = keyblock->pkt->pkt.public_key; + if (pk->has_expired || pk->is_revoked) + { + /* it does not make sense to look further at those keys */ + mark_keyblock_seen (full_trust, keyblock); + } + else if (validate_one_keyblock (keyblock, klist, curtime, next_expire)) + { + KBNODE node; + + if (pk->expiredate && pk->expiredate >= curtime + && pk->expiredate < *next_expire) + *next_expire = pk->expiredate; + + if (nkeys == maxkeys) { + maxkeys += 1000; + keys = xrealloc (keys, (maxkeys+1) * sizeof *keys); + } + keys[nkeys++].keyblock = keyblock; + + /* Optimization - if all uids are fully trusted, then we + never need to consider this key as a candidate again. */ + + for (node=keyblock; node; node = node->next) + if (node->pkt->pkttype == PKT_USER_ID && !(node->flag & 4)) + break; + + if(node==NULL) + mark_keyblock_seen (full_trust, keyblock); + + keyblock = NULL; + } + + release_kbnode (keyblock); + keyblock = NULL; + } + while ( !(rc = keydb_search (hd, &desc, 1)) ); + if (rc && rc != -1) + { + log_error ("keydb_search_next failed: %s\n", g10_errstr(rc)); + xfree (keys); + return NULL; + } + + keys[nkeys].keyblock = NULL; + return keys; +} + +/* Caller must sync */ +static void +reset_trust_records(void) +{ + TRUSTREC rec; + ulong recnum; + int count = 0, nreset = 0; + + for (recnum=1; !tdbio_read_record (recnum, &rec, 0); recnum++ ) + { + if(rec.rectype==RECTYPE_TRUST) + { + count++; + if(rec.r.trust.min_ownertrust) + { + rec.r.trust.min_ownertrust=0; + write_record(&rec); + } + + } + else if(rec.rectype==RECTYPE_VALID + && ((rec.r.valid.validity&TRUST_MASK) + || rec.r.valid.marginal_count + || rec.r.valid.full_count)) + { + rec.r.valid.validity &= ~TRUST_MASK; + rec.r.valid.marginal_count=rec.r.valid.full_count=0; + nreset++; + write_record(&rec); + } + + } + + if (opt.verbose) + log_info (_("%d keys processed (%d validity counts cleared)\n"), + count, nreset); +} + +/* + * Run the key validation procedure. + * + * This works this way: + * Step 1: Find all ultimately trusted keys (UTK). + * mark them all as seen and put them into klist. + * Step 2: loop max_cert_times + * Step 3: if OWNERTRUST of any key in klist is undefined + * ask user to assign ownertrust + * Step 4: Loop over all keys in the keyDB which are not marked seen + * Step 5: if key is revoked or expired + * mark key as seen + * continue loop at Step 4 + * Step 6: For each user ID of that key signed by a key in klist + * Calculate validity by counting trusted signatures. + * Set validity of user ID + * Step 7: If any signed user ID was found + * mark key as seen + * End Loop + * Step 8: Build a new klist from all fully trusted keys from step 6 + * End Loop + * Ready + * + */ +static int +validate_keys (int interactive) +{ + int rc = 0; + int quit=0; + struct key_item *klist = NULL; + struct key_item *k; + struct key_array *keys = NULL; + struct key_array *kar; + KEYDB_HANDLE kdb = NULL; + KBNODE node; + int depth; + int ot_unknown, ot_undefined, ot_never, ot_marginal, ot_full, ot_ultimate; + KeyHashTable stored,used,full_trust; + u32 start_time, next_expire; + + /* Make sure we have all sigs cached. TODO: This is going to + require some architectual re-thinking, as it is agonizingly slow. + Perhaps combine this with reset_trust_records(), or only check + the caches on keys that are actually involved in the web of + trust. */ + keydb_rebuild_caches(0); + + start_time = make_timestamp (); + next_expire = 0xffffffff; /* set next expire to the year 2106 */ + stored = new_key_hash_table (); + used = new_key_hash_table (); + full_trust = new_key_hash_table (); + + kdb = keydb_new (0); + reset_trust_records(); + + /* Fixme: Instead of always building a UTK list, we could just build it + * here when needed */ + if (!utk_list) + { + if (!opt.quiet) + log_info (_("no ultimately trusted keys found\n")); + goto leave; + } + + /* mark all UTKs as used and fully_trusted and set validity to + ultimate */ + for (k=utk_list; k; k = k->next) + { + KBNODE keyblock; + PKT_public_key *pk; + + keyblock = get_pubkeyblock (k->kid); + if (!keyblock) + { + log_error (_("public key of ultimately" + " trusted key %s not found\n"), keystr(k->kid)); + continue; + } + mark_keyblock_seen (used, keyblock); + mark_keyblock_seen (stored, keyblock); + mark_keyblock_seen (full_trust, keyblock); + pk = keyblock->pkt->pkt.public_key; + for (node=keyblock; node; node = node->next) + { + if (node->pkt->pkttype == PKT_USER_ID) + update_validity (pk, node->pkt->pkt.user_id, 0, TRUST_ULTIMATE); + } + if ( pk->expiredate && pk->expiredate >= start_time + && pk->expiredate < next_expire) + next_expire = pk->expiredate; + + release_kbnode (keyblock); + do_sync (); + } + + klist = utk_list; + + log_info(_("%d marginal(s) needed, %d complete(s) needed, %s trust model\n"), + opt.marginals_needed,opt.completes_needed,trust_model_string()); + + for (depth=0; depth < opt.max_cert_depth; depth++) + { + int valids=0,key_count; + /* See whether we should assign ownertrust values to the keys in + klist. */ + ot_unknown = ot_undefined = ot_never = 0; + ot_marginal = ot_full = ot_ultimate = 0; + for (k=klist; k; k = k->next) + { + int min=0; + + /* 120 and 60 are as per RFC2440 */ + if(k->trust_value>=120) + min=TRUST_FULLY; + else if(k->trust_value>=60) + min=TRUST_MARGINAL; + + if(min!=k->min_ownertrust) + update_min_ownertrust(k->kid,min); + + if (interactive && k->ownertrust == TRUST_UNKNOWN) + { + k->ownertrust = ask_ownertrust (k->kid,min); + + if (k->ownertrust == -1) + { + quit=1; + goto leave; + } + } + + /* This can happen during transition from an old trustdb + before trust sigs. It can also happen if a user uses two + different versions of GnuPG or changes the --trust-model + setting. */ + if(k->ownertrust<min) + { + if(DBG_TRUST) + log_debug("key %08lX%08lX:" + " overriding ownertrust `%s' with `%s'\n", + (ulong)k->kid[0],(ulong)k->kid[1], + trust_value_to_string(k->ownertrust), + trust_value_to_string(min)); + + k->ownertrust=min; + } + + if (k->ownertrust == TRUST_UNKNOWN) + ot_unknown++; + else if (k->ownertrust == TRUST_UNDEFINED) + ot_undefined++; + else if (k->ownertrust == TRUST_NEVER) + ot_never++; + else if (k->ownertrust == TRUST_MARGINAL) + ot_marginal++; + else if (k->ownertrust == TRUST_FULLY) + ot_full++; + else if (k->ownertrust == TRUST_ULTIMATE) + ot_ultimate++; + + valids++; + } + + /* Find all keys which are signed by a key in kdlist */ + keys = validate_key_list (kdb, full_trust, klist, + start_time, &next_expire); + if (!keys) + { + log_error ("validate_key_list failed\n"); + rc = G10ERR_GENERAL; + goto leave; + } + + for (key_count=0, kar=keys; kar->keyblock; kar++, key_count++) + ; + + /* Store the calculated valididation status somewhere */ + if (opt.verbose > 1) + dump_key_array (depth, keys); + + for (kar=keys; kar->keyblock; kar++) + store_validation_status (depth, kar->keyblock, stored); + + log_info (_("depth: %d valid: %3d signed: %3d" + " trust: %d-, %dq, %dn, %dm, %df, %du\n"), + depth, valids, key_count, ot_unknown, ot_undefined, + ot_never, ot_marginal, ot_full, ot_ultimate ); + + /* Build a new kdlist from all fully valid keys in KEYS */ + if (klist != utk_list) + release_key_items (klist); + klist = NULL; + for (kar=keys; kar->keyblock; kar++) + { + for (node=kar->keyblock; node; node = node->next) + { + if (node->pkt->pkttype == PKT_USER_ID && (node->flag & 4)) + { + u32 kid[2]; + + /* have we used this key already? */ + keyid_from_pk (kar->keyblock->pkt->pkt.public_key, kid); + if(test_key_hash_table(used,kid)==0) + { + /* Normally we add both the primary and subkey + ids to the hash via mark_keyblock_seen, but + since we aren't using this hash as a skipfnc, + that doesn't matter here. */ + add_key_hash_table (used,kid); + k = new_key_item (); + k->kid[0]=kid[0]; + k->kid[1]=kid[1]; + k->ownertrust = + (get_ownertrust (kar->keyblock->pkt->pkt.public_key) + & TRUST_MASK); + k->min_ownertrust = + get_min_ownertrust(kar->keyblock->pkt->pkt.public_key); + k->trust_depth= + kar->keyblock->pkt->pkt.public_key->trust_depth; + k->trust_value= + kar->keyblock->pkt->pkt.public_key->trust_value; + if(kar->keyblock->pkt->pkt.public_key->trust_regexp) + k->trust_regexp= + xstrdup(kar->keyblock->pkt-> + pkt.public_key->trust_regexp); + k->next = klist; + klist = k; + break; + } + } + } + } + release_key_array (keys); + keys = NULL; + if (!klist) + break; /* no need to dive in deeper */ + } + + leave: + keydb_release (kdb); + release_key_array (keys); + release_key_items (klist); + release_key_hash_table (full_trust); + release_key_hash_table (used); + release_key_hash_table (stored); + if (!rc && !quit) /* mark trustDB as checked */ + { + if (next_expire == 0xffffffff || next_expire < start_time ) + tdbio_write_nextcheck (0); + else + { + tdbio_write_nextcheck (next_expire); + log_info (_("next trustdb check due at %s\n"), + strtimestamp (next_expire)); + } + + if(tdbio_update_version_record()!=0) + { + log_error(_("unable to update trustdb version record: " + "write failed: %s\n"), g10_errstr(rc)); + tdbio_invalid(); + } + + do_sync (); + pending_check_trustdb = 0; + } + + return rc; +} diff --git a/g10/trustdb.h b/g10/trustdb.h new file mode 100644 index 0000000..2d0581f --- /dev/null +++ b/g10/trustdb.h @@ -0,0 +1,98 @@ +/* trustdb.h - Trust database + * Copyright (C) 1998, 1999, 2000, 2001, 2002, 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 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#ifndef G10_TRUSTDB_H +#define G10_TRUSTDB_H + +/* Trust values must be sorted in ascending order */ +#define TRUST_MASK 15 +#define TRUST_UNKNOWN 0 /* o: not yet calculated/assigned */ +#define TRUST_EXPIRED 1 /* e: calculation may be invalid */ +#define TRUST_UNDEFINED 2 /* q: not enough information for calculation */ +#define TRUST_NEVER 3 /* n: never trust this pubkey */ +#define TRUST_MARGINAL 4 /* m: marginally trusted */ +#define TRUST_FULLY 5 /* f: fully trusted */ +#define TRUST_ULTIMATE 6 /* u: ultimately trusted */ +/* trust values not covered by the mask */ +#define TRUST_FLAG_REVOKED 32 /* r: revoked */ +#define TRUST_FLAG_SUB_REVOKED 64 /* r: revoked but for subkeys */ +#define TRUST_FLAG_DISABLED 128 /* d: key/uid disabled */ +#define TRUST_FLAG_PENDING_CHECK 256 /* a check-trustdb is pending */ + +#define NAMEHASH_HASH DIGEST_ALGO_RMD160 +#define NAMEHASH_LEN 20 + +/*-- trustdb.c --*/ +void register_trusted_keyid(u32 *keyid); +void register_trusted_key( const char *string ); +void check_trustdb (void); +void update_trustdb (void); +int setup_trustdb( int level, const char *dbname ); +void init_trustdb( void ); +void check_trustdb_stale(void); +void sync_trustdb( void ); + +const char *uid_trust_string_fixed(PKT_public_key *key,PKT_user_id *uid); +const char *trust_value_to_string (unsigned int value); +int string_to_trust_value (const char *str); + +void revalidation_mark (void); +int trustdb_pending_check(void); +void trustdb_check_or_update(void); + +int cache_disabled_value(PKT_public_key *pk); + +unsigned int get_validity (PKT_public_key *pk, PKT_user_id *uid); +int get_validity_info (PKT_public_key *pk, PKT_user_id *uid); +const char *get_validity_string (PKT_public_key *pk, PKT_user_id *uid); + +void list_trust_path( const char *username ); +int enum_cert_paths( void **context, ulong *lid, + unsigned *ownertrust, unsigned *validity ); +void enum_cert_paths_print( void **context, FILE *fp, + int refresh, ulong selected_lid ); + +void read_trust_options(byte *trust_model,ulong *created,ulong *nextcheck, + byte *marginals,byte *completes,byte *cert_depth); + +unsigned int get_ownertrust (PKT_public_key *pk); +unsigned int get_min_ownertrust (PKT_public_key *pk); +int get_ownertrust_info (PKT_public_key *pk); +const char *get_ownertrust_string (PKT_public_key *pk); + +void update_ownertrust (PKT_public_key *pk, unsigned int new_trust ); +int clear_ownertrusts (PKT_public_key *pk); + +void clean_one_uid(KBNODE keyblock,KBNODE uidnode,int noisy,int self_only, + int *uids_cleaned,int *sigs_cleaned); +void clean_key(KBNODE keyblock,int noisy,int self_only, + int *uids_cleaned,int *sigs_cleaned); + +/*-- tdbdump.c --*/ +void list_trustdb(const char *username); +void export_ownertrust(void); +void import_ownertrust(const char *fname); + +/*-- pkclist.c --*/ +int edit_ownertrust (PKT_public_key *pk, int mode ); + +#endif /*G10_TRUSTDB_H*/ diff --git a/g10/verify.c b/g10/verify.c new file mode 100644 index 0000000..082db89 --- /dev/null +++ b/g10/verify.c @@ -0,0 +1,211 @@ +/* verify.c - verify signed data + * Copyright (C) 1998, 1999, 2000, 2001, 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 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <assert.h> +#include <unistd.h> /* for isatty() */ + +#include "options.h" +#include "packet.h" +#include "errors.h" +#include "iobuf.h" +#include "keydb.h" +#include "memory.h" +#include "util.h" +#include "main.h" +#include "status.h" +#include "filter.h" +#include "ttyio.h" +#include "i18n.h" + + + +/**************** + * Assume that the input is a signature and verify it without + * generating any output. With no arguments, the signature packet + * is read from stdin (it may be a detached signature when not + * used in batch mode). If only a sigfile is given, it may be a complete + * signature or a detached signature in which case the signed stuff + * is expected from stdin. With more than 1 argument, the first should + * be a detached signature and the remaining files are the signed stuff. + */ + +int +verify_signatures( int nfiles, char **files ) +{ + IOBUF fp; + armor_filter_context_t afx; + progress_filter_context_t pfx; + const char *sigfile; + int i, rc; + STRLIST sl; + + memset( &afx, 0, sizeof afx); + /* decide whether we should handle a detached or a normal signature, + * which is needed so that the code later can hash the correct data and + * not have a normal signature act as detached signature and ignoring the + * indended signed material from the 2nd file or stdin. + * 1. gpg <file - normal + * 2. gpg file - normal (or detached) + * 3. gpg file <file2 - detached + * 4. gpg file file2 - detached + * The question is how decide between case 2 and 3? The only way + * we can do it is by reading one byte from stdin and the unget + * it; the problem here is that we may be reading from the + * terminal (which could be detected using isatty() but won't work + * when under contol of a pty using program (e.g. expect)) and + * might get us in trouble when stdin is used for another purpose + * (--passphrase-fd 0). So we have to break with the behaviour + * prior to gpg 1.0.4 by assuming that case 3 is a normal + * signature (where file2 is ignored and require for a detached + * signature to indicate signed material comes from stdin by using + * case 4 with a file2 of "-". + * + * Actually we don't have to change anything here but can handle + * that all quite easily in mainproc.c + */ + + + sigfile = nfiles? *files : NULL; + + /* open the signature file */ + fp = iobuf_open(sigfile); + if (fp && is_secured_file (iobuf_get_fd (fp))) + { + iobuf_close (fp); + fp = NULL; + errno = EPERM; + } + if( !fp ) { + log_error(_("can't open `%s'\n"), print_fname_stdin(sigfile)); + return G10ERR_OPEN_FILE; + } + handle_progress (&pfx, fp, sigfile); + + if( !opt.no_armor && use_armor_filter( fp ) ) + iobuf_push_filter( fp, armor_filter, &afx ); + + sl = NULL; + for(i=nfiles-1 ; i > 0 ; i-- ) + add_to_strlist( &sl, files[i] ); + rc = proc_signature_packets( NULL, fp, sl, sigfile ); + free_strlist(sl); + iobuf_close(fp); + if( (afx.no_openpgp_data && rc == -1) || rc == G10ERR_NO_DATA ) { + log_error(_("the signature could not be verified.\n" + "Please remember that the signature file (.sig or .asc)\n" + "should be the first file given on the command line.\n") ); + rc = 0; + } + + return rc; +} + + +void +print_file_status( int status, const char *name, int what ) +{ + char *p = xmalloc(strlen(name)+10); + sprintf(p, "%d %s", what, name ); + write_status_text( status, p ); + xfree(p); +} + + +static int +verify_one_file( const char *name ) +{ + IOBUF fp; + armor_filter_context_t afx; + progress_filter_context_t pfx; + int rc; + + print_file_status( STATUS_FILE_START, name, 1 ); + fp = iobuf_open(name); + if (fp) + iobuf_ioctl (fp,3,1,NULL); /* disable fd caching */ + if (fp && is_secured_file (iobuf_get_fd (fp))) + { + iobuf_close (fp); + fp = NULL; + errno = EPERM; + } + if( !fp ) { + print_file_status( STATUS_FILE_ERROR, name, 1 ); + log_error(_("can't open `%s'\n"), print_fname_stdin(name)); + return G10ERR_OPEN_FILE; + } + handle_progress (&pfx, fp, name); + + if( !opt.no_armor ) { + if( use_armor_filter( fp ) ) { + memset( &afx, 0, sizeof afx); + iobuf_push_filter( fp, armor_filter, &afx ); + } + } + + rc = proc_signature_packets( NULL, fp, NULL, name ); + iobuf_close(fp); + write_status( STATUS_FILE_DONE ); + return rc; +} + +/**************** + * Verify each file given in the files array or read the names of the + * files from stdin. + * Note: This function can not handle detached signatures. + */ +int +verify_files( int nfiles, char **files ) +{ + int i; + + if( !nfiles ) { /* read the filenames from stdin */ + char line[2048]; + unsigned int lno = 0; + + while( fgets(line, DIM(line), stdin) ) { + lno++; + if( !*line || line[strlen(line)-1] != '\n' ) { + log_error(_("input line %u too long or missing LF\n"), lno ); + return G10ERR_GENERAL; + } + /* This code does not work on MSDOS but how cares there are + * also no script languages available. We don't strip any + * spaces, so that we can process nearly all filenames */ + line[strlen(line)-1] = 0; + verify_one_file( line ); + iobuf_ioctl( NULL, 2, 0, NULL); /* Invalidate entire cache. */ + } + + } + else { /* take filenames from the array */ + for(i=0; i < nfiles; i++ ) { + verify_one_file( files[i] ); + iobuf_ioctl( NULL, 2, 0, NULL); /* Invalidate entire cache. */ + } + } + return 0; +} |