From eee068778cb28ecf3c14e1bf843a95547d72c42d Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 18:14:06 +0200 Subject: Adding upstream version 2.2.40. Signed-off-by: Daniel Baumann --- g10/ChangeLog-2011 | 12066 +++++++++++++++++++++++++++++++++++++++++ g10/Makefile.am | 267 + g10/Makefile.in | 1453 +++++ g10/all-tests.scm | 35 + g10/armor.c | 1570 ++++++ g10/build-packet.c | 1808 ++++++ g10/call-agent.c | 2702 +++++++++ g10/call-agent.h | 224 + g10/call-dirmngr.c | 1401 +++++ g10/call-dirmngr.h | 48 + g10/card-util.c | 2489 +++++++++ g10/cipher.c | 187 + g10/compress-bz2.c | 254 + g10/compress.c | 377 ++ g10/cpr.c | 659 +++ g10/dearmor.c | 131 + g10/decrypt-data.c | 1030 ++++ g10/decrypt.c | 282 + g10/dek.h | 51 + g10/delkey.c | 382 ++ g10/distsigkey.gpg | Bin 0 -> 3902 bytes g10/ecdh.c | 516 ++ g10/encrypt.c | 1060 ++++ g10/exec.c | 705 +++ g10/exec.h | 51 + g10/export.c | 2475 +++++++++ g10/filter.h | 164 + g10/free-packet.c | 569 ++ g10/getkey.c | 4544 ++++++++++++++++ g10/gpg-w32info.rc | 52 + g10/gpg.c | 5569 +++++++++++++++++++ g10/gpg.h | 110 + g10/gpg.w32-manifest.in | 18 + g10/gpgcompose.c | 3089 +++++++++++ g10/gpgsql.c | 251 + g10/gpgsql.h | 61 + g10/gpgv-w32info.rc | 52 + g10/gpgv.c | 835 +++ g10/gpgv.w32-manifest.in | 18 + g10/helptext.c | 86 + g10/import.c | 4528 ++++++++++++++++ g10/kbnode.c | 431 ++ g10/key-check.c | 750 +++ g10/key-check.h | 28 + g10/key-clean.c | 614 +++ g10/key-clean.h | 52 + g10/keydb.c | 2116 ++++++++ g10/keydb.h | 570 ++ g10/keyedit.c | 6577 ++++++++++++++++++++++ g10/keyedit.h | 64 + g10/keygen.c | 5766 ++++++++++++++++++++ g10/keyid.c | 987 ++++ g10/keylist.c | 2226 ++++++++ g10/keyring.c | 1747 ++++++ g10/keyring.h | 45 + g10/keyserver-internal.h | 66 + g10/keyserver.c | 2001 +++++++ g10/main.h | 518 ++ g10/mainproc.c | 2914 ++++++++++ g10/mdfilter.c | 73 + g10/migrate.c | 118 + g10/misc.c | 1905 +++++++ g10/openfile.c | 403 ++ g10/options.h | 423 ++ g10/packet.h | 943 ++++ g10/parse-packet.c | 3663 +++++++++++++ g10/passphrase.c | 581 ++ g10/photoid.c | 401 ++ g10/photoid.h | 34 + g10/pkclist.c | 1721 ++++++ g10/pkglue.c | 453 ++ g10/pkglue.h | 51 + g10/plaintext.c | 814 +++ g10/progress.c | 202 + g10/pubkey-enc.c | 487 ++ g10/revoke.c | 911 ++++ g10/rmd160.c | 425 ++ g10/rmd160.h | 24 + g10/server.c | 799 +++ g10/seskey.c | 359 ++ g10/sig-check.c | 1229 +++++ g10/sign.c | 1819 +++++++ g10/skclist.c | 637 +++ g10/t-keydb-get-keyblock.c | 65 + g10/t-keydb-get-keyblock.gpg | Bin 0 -> 138824 bytes g10/t-keydb-keyring.kbx | Bin 0 -> 5104 bytes g10/t-keydb.c | 104 + g10/t-rmd160.c | 91 + g10/t-stutter-data.asc | 1 + g10/t-stutter.c | 613 +++ g10/tdbdump.c | 240 + g10/tdbio.c | 1936 +++++++ g10/tdbio.h | 122 + g10/test-stubs.c | 591 ++ g10/test.c | 192 + g10/textfilter.c | 245 + g10/tofu.c | 4033 ++++++++++++++ g10/tofu.h | 142 + g10/trust.c | 432 ++ g10/trustdb.c | 2343 ++++++++ g10/trustdb.h | 135 + g10/verify.c | 283 + 102 files changed, 109684 insertions(+) create mode 100644 g10/ChangeLog-2011 create mode 100644 g10/Makefile.am create mode 100644 g10/Makefile.in create mode 100644 g10/all-tests.scm create mode 100644 g10/armor.c create mode 100644 g10/build-packet.c create mode 100644 g10/call-agent.c create mode 100644 g10/call-agent.h create mode 100644 g10/call-dirmngr.c create mode 100644 g10/call-dirmngr.h create mode 100644 g10/card-util.c create mode 100644 g10/cipher.c create mode 100644 g10/compress-bz2.c create mode 100644 g10/compress.c create mode 100644 g10/cpr.c create mode 100644 g10/dearmor.c create mode 100644 g10/decrypt-data.c create mode 100644 g10/decrypt.c create mode 100644 g10/dek.h create mode 100644 g10/delkey.c create mode 100644 g10/distsigkey.gpg create mode 100644 g10/ecdh.c create mode 100644 g10/encrypt.c create mode 100644 g10/exec.c create mode 100644 g10/exec.h create mode 100644 g10/export.c create mode 100644 g10/filter.h create mode 100644 g10/free-packet.c create mode 100644 g10/getkey.c create mode 100644 g10/gpg-w32info.rc create mode 100644 g10/gpg.c create mode 100644 g10/gpg.h create mode 100644 g10/gpg.w32-manifest.in create mode 100644 g10/gpgcompose.c create mode 100644 g10/gpgsql.c create mode 100644 g10/gpgsql.h create mode 100644 g10/gpgv-w32info.rc create mode 100644 g10/gpgv.c create mode 100644 g10/gpgv.w32-manifest.in create mode 100644 g10/helptext.c create mode 100644 g10/import.c create mode 100644 g10/kbnode.c create mode 100644 g10/key-check.c create mode 100644 g10/key-check.h create mode 100644 g10/key-clean.c create mode 100644 g10/key-clean.h create mode 100644 g10/keydb.c create mode 100644 g10/keydb.h create mode 100644 g10/keyedit.c create mode 100644 g10/keyedit.h create mode 100644 g10/keygen.c create mode 100644 g10/keyid.c create mode 100644 g10/keylist.c create mode 100644 g10/keyring.c create mode 100644 g10/keyring.h create mode 100644 g10/keyserver-internal.h create mode 100644 g10/keyserver.c create mode 100644 g10/main.h create mode 100644 g10/mainproc.c create mode 100644 g10/mdfilter.c create mode 100644 g10/migrate.c create mode 100644 g10/misc.c create mode 100644 g10/openfile.c create mode 100644 g10/options.h create mode 100644 g10/packet.h create mode 100644 g10/parse-packet.c create mode 100644 g10/passphrase.c create mode 100644 g10/photoid.c create mode 100644 g10/photoid.h create mode 100644 g10/pkclist.c create mode 100644 g10/pkglue.c create mode 100644 g10/pkglue.h create mode 100644 g10/plaintext.c create mode 100644 g10/progress.c create mode 100644 g10/pubkey-enc.c create mode 100644 g10/revoke.c create mode 100644 g10/rmd160.c create mode 100644 g10/rmd160.h create mode 100644 g10/server.c create mode 100644 g10/seskey.c create mode 100644 g10/sig-check.c create mode 100644 g10/sign.c create mode 100644 g10/skclist.c create mode 100644 g10/t-keydb-get-keyblock.c create mode 100644 g10/t-keydb-get-keyblock.gpg create mode 100644 g10/t-keydb-keyring.kbx create mode 100644 g10/t-keydb.c create mode 100644 g10/t-rmd160.c create mode 100644 g10/t-stutter-data.asc create mode 100644 g10/t-stutter.c create mode 100644 g10/tdbdump.c create mode 100644 g10/tdbio.c create mode 100644 g10/tdbio.h create mode 100644 g10/test-stubs.c create mode 100644 g10/test.c create mode 100644 g10/textfilter.c create mode 100644 g10/tofu.c create mode 100644 g10/tofu.h create mode 100644 g10/trust.c create mode 100644 g10/trustdb.c create mode 100644 g10/trustdb.h create mode 100644 g10/verify.c (limited to 'g10') diff --git a/g10/ChangeLog-2011 b/g10/ChangeLog-2011 new file mode 100644 index 0000000..37da37b --- /dev/null +++ b/g10/ChangeLog-2011 @@ -0,0 +1,12066 @@ +2011-12-01 Werner Koch + + NB: ChangeLog files are no longer manually maintained. Starting + on December 1st, 2011 we put change information only in the GIT + commit log, and generate a top-level ChangeLog file from logs at + "make dist". See doc/HACKING for details. + +2011-11-30 Werner Koch + + * keyserver.c (keyserver_import_cert): Adjust for changed + get_dns_cert. + +2011-11-28 Werner Koch + + * keyserver.c (DEFAULT_MAX_CERT_SIZE): Increase from 16k to 64k. + +2011-11-22 Werner Koch + + * pubkey-enc.c (get_session_key): Don't print anonymous recipient + messages in quiet mode. This is bug#1378. + +2011-11-06 Werner Koch + + * card-util.c (generate_card_keys): Add arg CTRL. + + * call-agent.c (agent_readkey): New. + * keygen.c (do_create_from_keygrip): New. + (ask_algo): Add arg R_KEYGRIP and a prompt to enter it. + (generate_subkeypair): Call do_create_from_keygrip if required. + (generate_subkeypair): Add arg CTRL. Change caller. + (ask_algo): Add arg CTRL. + (generate_keypair): Ditto. + +2011-09-23 Werner Koch + + * gpgv.c (disable_dotlock): Rename to dotlock_disable. + (create_dotlock): Rename to dotlock_create. + (destroy_dotlock): Rename to dotlock_destroy. + (make_dotlock): Rename to dotlock_take. + (release_dotlock): Rename to dotlock_release. + (lockfiles_remove): Rename to dotlock_remove_lockfiles. + +2011-09-20 Werner Koch + + * free-packet.c (free_public_key): Allow a NULL argument. + * keyedit.c (keyedit_passwd): No more need to check that PK is NULL. + (menu_addrevoker): Ditto. + * passphrase.c (passphrase_get, passphrase_to_dek_ext): Ditto. + * skclist.c (release_sk_list): Ditto. + * revoke.c (gen_desig_revoke): Ditto. + * pubkey-enc.c (get_session_key): Ditto. + * pkclist.c (build_pk_list): Ditto. + +2011-09-20 Jim Meyering + + avoid use of freed pointer + If we free pk2 at the top of the for-loop, set it to NULL + so that we don't free it again just before returning. + * revoke.c (gen_desig_revoke): Don't use pk2 after freeing it. + +2011-09-20 Werner Koch + + * sign.c (sign_file, clearsign_file, sign_symencrypt_file): + s/gcry_md_start_debug/gcry_md_debug/ in preparation for Libgcrypt + 1.6. + * mainproc.c (proc_plaintext, proc_tree): Ditto. + * decrypt-data.c (decrypt_data): Ditto. + * cipher.c (write_header): Ditto. + +2011-08-10 Werner Koch + + * export.c (transfer_format_to_openpgp): Don't parse unneeded CSUM. + + * import.c (import_secret_one): Use arg OPTIONS instead of global + import options var. + + * sig-check.c (do_check): Remove unused var CTX. + + * build-packet.c (do_user_id): Return value. + +2011-07-29 Werner Koch + + * tdbio.c (open_db): Do not print read-only warning in quiet mode. + +2011-07-18 Werner Koch + + * parse-packet.c (parse_key): Print the decoded iteration count. + Fixes bug#1355. + +2011-07-07 Werner Koch + + * card-util.c (ask_card_keysize): Bump key size limit to 4096. + * call-agent.c (scd_genkey_parm_s): New. + (agent_scd_genkey): Use new struct. + (scd_genkey_cb): Implement chunked mode for KEY-DATA. + (scd_genkey_cb_append_savedbytes): New. + +2011-06-16 Werner Koch + + * card-util.c (ask_card_keysize): Bump key size limit to 4096. + * call-agent.c (scd_genkey_parm_s): New. + (agent_scd_genkey): Use new struct. + (scd_genkey_cb): Implement chunked mode for KEY-DATA. + (scd_genkey_cb_append_savedbytes): New. + +2011-06-13 Werner Koch + + * pkglue.c (mpi_from_sexp): Use GCRYMPI_FMT_USG to avoid problems + with leading zeroed. The latest Libgcrypt does this now + correctly. Given that the default of gcry_sexp_nth_mpi would use + a signed MPI, which is not implemented, the assertion would fail. + +2011-06-01 Marcus Brinkmann + + * parse-packet.c (parse_pubkeyenc): Change type of N to size_t. + (parse_key): Likewise. + * seskey.c (encode_session_key): Convert nframe to int for + debugging. + * build-packet.c (gpg_mpi_write): Change type of N to unsigned int. + * import.c (transfer_secret_keys): Likewise. + +2011-04-29 Werner Koch + + * keydb.c (keydb_get_keyblock, keydb_add_resource): Use gpg_error. + (keydb_get_keyblock): Return VALUE_NOT_FOUND instead of -1. + (keydb_update_keyblock, keydb_insert_keyblock) + (keydb_delete_keyblock): Ditto. + (keydb_locate_writable): Ditto. + (keydb_search_reset): Ditto. + (keydb_search2): Return GPG_ERR_NOT_FOUND instead of -1. Change + all callers. + (keydb_search_first, keydb_search_next, keydb_search_kid) + (keydb_search_fpr): Ditto. + +2011-04-29 Marcus Brinkmann + + * import.c (import_secret_one): Leave all checks to import_one. + Cancel secret key import if public key was skipped due to + merge-only request. Fix import status for non-new secret key + import by checking stat counter. + +2011-04-29 Marcus Brinkmann + + * delkey.c (do_delete_key): Access public keyblock even for secret + key operations. But deleting secret key is not supported yet, so + give an error. Limit secret-key-exists error case to public keys. + +2011-04-28 Werner Koch + + * ecdh.c (pk_ecdh_encrypt_with_shared_point): Remove memory leak + of SECRET_X in the error case. Replace an assert by an error + return. + +2011-04-26 Werner Koch + + * export.c (transfer_format_to_openpgp): Do not apply + encode_s2k_iterations to S2K_COUNT. + +2011-04-25 Werner Koch + + * delkey.c (do_delete_key): Mark classify_user_id for use with + OpenPGP. + * trustdb.c (register_trusted_key): Ditto. + * revoke.c (gen_revoke): Ditto. + * keyserver.c (keyserver_export, keyidlist, keyserver_export): Ditto. + * getkey.c (key_byname): Ditto. + * export.c (do_export_stream): Ditto. + +2011-04-20 Marcus Brinkmann + + * keylist.c (list_keyblock_colon): Use get_ownertrust_info, not + get_ownertrust (which lead to binary zeroes in the output!). + +2011-03-23 Werner Koch + + * parse-packet.c (read_rest): Drop unsed PARTIAL arg. Rewrite to + detect premature EOF. Suggested by Timo Schulz. + +2011-03-10 Werner Koch + + * passphrase.c (hash_passphrase): Remove. + (passphrase_to_dek_ext): Use gcry_kdf_derive. + +2011-03-03 Werner Koch + + * keylist.c (print_card_key_info): Re-implement using the agent. + * card-util.c (card_status) [GNUPG_MAJOR_VERSION!=1]: Call + print_card_key_info. + + * keyid.c (hash_public_key): Remove shadowing NBITS. + + * misc.c (pubkey_nbits): Replace GCRY_PK_ by PUBKEY_ALGO_. + (get_signature_count): Remove warning. + + * armor.c (armor_filter): Don't take a copy of radbuf while + writing the checksum. This works around a faulty gcc 4.4 warning. + +2011-03-02 Werner Koch + + * call-agent.c (agent_scd_pksign, agent_scd_pkdecrypt) + (hash_algo_option): Remove these unused functions. + +2011-02-10 Werner Koch + + * seskey.c (encode_md_value): Change last fix to avoid a + regression for DSA with SHA-2 hashes. + +2011-02-09 Werner Koch + + * keyserver.c: Replace all printf by es_printf. + +2011-02-08 Werner Koch + + * call-dirmngr.c (gpg_dirmngr_ks_fetch): New. + * keyserver.c (keyserver_fetch): Rewrite to use dirmngr. + +2011-02-07 Werner Koch + + * seskey.c (encode_md_value): Truncate to MDLEN and not to QBYTES + which makes a difference with 521 bit ECC keys. For clarity + rename QBYTES to QBITS and adjust accordingly. + +2011-02-04 Werner Koch + + * sig-check.c (do_check_messages): Remove the long deprecated + SIGEXPIRED status line. + +2011-02-03 Werner Koch + + * export.c (transfer_format_to_openpgp) [!HAVE_GCRY_PK_GET_CURVE]: + Fix syntax error. + + * decrypt-data.c: Include status.h. + (decrypt_data): Emit a DECRYPTION_INFO status line. + + * misc.c (has_invalid_email_chars): Relax mailbox name checking. + Fixes bug#1315. + + * sign.c (do_sign): Use openpgp_pk_algo_name. + + * keygen.c (ask_algo): Show ECC algos only in expert mode. Add + non-combined menu entries for ECDSA and ECDH. + (ask_key_flags): Use openpgp_pk_algo_name. + +2011-02-03 Werner Koch + + Finished ECC integration. + Wrote change description for 2011-01-13. + +2011-02-02 Werner Koch + + * encrypt.c (write_pubkey_enc_from_list): Don't compute the + fingerprint. + * pkglue.c (pk_encrypt): Replace PK_FP by PK and compute the + fingerprint only when needed. + * pkglue.h: Include packet.h. + + * import.c (transfer_secret_keys): Make sure keyids are available. + + * keyid.c (hash_public_key): Adjust for the ECC case. + +2011-02-01 Werner Koch + + * gpg.c (main): Call setup_libgcrypt_logging. + + * import.c (transfer_secret_keys): Implement ECC case. + (one_mpi_from_pkey): New. + * export.c (transfer_format_to_openpgp): Ditto. + * keygen.c (gpg_curve_to_oid): New. + (ecckey_from_sexp): Factor curve name mapping out to new function. + +2011-01-31 Werner Koch + + * ecdh.c (pk_ecdh_encrypt_with_shared_point): Return an opaque MPI. + + * build-packet.c (mpi_write): Rename to gpg_mpi_write and make global. + +2011-01-30 Werner Koch + + * keyid.c (keygrip_from_pk): Adjust ECC cases. + * pkglue.c (pk_verify): Ditto. + + * parse-packet.c (parse_key): Simply ECC case. + (parse_pubkeyenc): Ditto. + + * misc.c (pubkey_get_npkey): Special case ECC. + (pubkey_get_nskey): Ditto. + (mpi_print): Support printing of opaque values. + (openpgp_oid_to_str): New. + (pubkey_nbits): For ECC pass curve parameter. + + * ecdh.c (pk_ecdh_default_params): Change to return an opaque MPI. + + * build-packet.c (do_key): Automatically handle real and opaque + key parameters. + (write_fake_data): Return an error code. + (mpi_write): Support writing opaque MPIs. + (do_pubkey_enc): Simplify ECC handling. + +2011-01-28 Werner Koch + + * keygen.c (gen_ecc): Rewrite. Select a named curve and create a + keyspec based on that. + (pk_ecc_build_key_params): Remove. + (get_parameter_algo): Map algo number. + (ecckey_from_sexp): New. + * misc.c (map_pk_gcry_to_openpgp): New. + +2011-01-25 Werner Koch + + * ecdh.c (pk_ecdh_default_params_to_mpi): Remove. + (pk_ecdh_default_params): Rewrite. Factor KEK table out to .. + (kek_params_table): .. here. + (pk_ecdh_generate_ephemeral_key): New. + (pk_ecdh_encrypt): Remove. + (pk_ecdh_encrypt_with_shared_point): Make public. + + * pubkey-enc.c (get_it): Fix assertion. Use GPG_ERR_WRONG_SECKEY + instead of log_fatal. Add safety checks for NFRAME. + + * keygen.c (pk_ecc_keypair_gen): Make static. + (ask_keysize): Use proper rounding for ECC. + (pk_ecc_build_key_params): Remove NBITSSTR. + +2011-01-20 Werner Koch + + * keyserver.c: Rewrite most stuff for use with dirmngr. Get rid + of all spawn code. More work pending. + + * export.c (export_pubkeys_buffer): New. + + * import.c (import_keys_es_stream): New. + + * call-dirmngr.c, call-dirmngr.h: New. + * gpg.h (server_control_s): Add DIRMNGR_LOCAL. + * gpg.c: Include call-dirmngr.h. + (gpg_deinit_default_ctrl): Call gpg_dirmngr_deinit_session_data. + +2011-01-13 Andrey Jivsov (wk) + + Integrated ECC support. Below are the changes finally merged into + the git master after some cleanup by wk until 2011-02-03. + + * ecdh.c: New. + + * sign.c (mpi_from_sexp): Remove. + (match_dsa_hash): Uses SHA-512 for ECDSA with 521 bits. + (hash_for): Support ECDSA. + (make_keysig_packet): Ditto. + + * seskey.c (encode_session_key): Add arg OPENPGP_PK_ALGO. Support + ECDH. + (encode_md_value): Map pkalgo. Extend size checks to ECDSA. + + * pubkey-enc.c (get_it): Support ECDH. + + * pkglue.c (mpi_from_sexp): Make global. + (pk_verify, pk_encrypt, pk_check_secret_key): Support ECC. + + * parse-packet.c (read_size_body): New. + (parse_pubkeyenc): Support ECC. + (parse_key): Ditto. + + * misc.c (map_pk_openpgp_to_gcry, map_pk_gcry_to_openpgp): New. + (openpgp_pk_test_algo, openpgp_pk_test_algo2): Map algo numbers. + (openpgp_pk_algo_usage): Support ECDH and ECDSA. + (openpgp_pk_algo_name): Simplify. + (ecdsa_qbits_from_Q): New. + + * mainproc.c (proc_pubkey_enc): Support ECC. + + * keyid.c (pubkey_letter): Add 'E' and 'e'. + (keygrip_from_pk): Supporf ECC. + + * keygen.c: Include pkglue.h. + (ask_algo): Add option 9 for ECDSA and ECDH. + (ask_keysize): Support ECDSA and ECDH. + (do_create): Ditto. + (gen_ecc): New. + (pk_ecc_build_key_params): New. + + * getkey.c (cache_public_key): Support ECC. + + * encrypt.c (write_pubkey_enc_from_list): Pass PK to PK_ENCRYPT + and the pkalgo to encode_session_key. + + * build-packet.c (do_key, do_pubkey_enc): Support ECC. + (write_size_body_mpi): New. + +2011-01-06 Werner Koch + + * gpg.c (main): Use keyserver_spec_t. + + * options.h (struct opt): Factor definition of struct keyserver + out to ../common/keyserver.h. + (keyserver_spec_t): New. + +2011-01-21 Werner Koch + + * seskey.c (encode_md_value): Truncate the DSA hash again. + + * misc.c (openpgp_pk_algo_name): Always use the gcrypt function. + +2010-12-09 Werner Koch + + * tdbio.c (tdbio_set_dbname) [W32CE]: Take care of missing errno. + (strerror) [W32CE]: Dummy replacement. + (open_db) [W32CE]: Use CreateFile. + +2010-12-02 Werner Koch + + * misc.c (openpgp_cipher_algo_name): Use gnupg_cipher_algo_name. + +2010-11-23 Werner Koch + + * Makefile.am (gpg2_LDFLAGS, gpgv2_LDFLAGS): Add extra_bin_ldflags. + + * plaintext.c (handle_plaintext): Change to use estream. + s/rc/err/. Replace some xmalloc by xtrymalloc. Use more + gpg_strerror. + * options.h (struct): Change type of OUTFP to estream_t. + * decrypt.c (decrypt_message_fd): Adjust accordingly. + +2010-11-17 Werner Koch + + * keyedit.c (find_pk_from_sknode): Remove. + * misc.c (get_signature_count): Call agent. + * keygen.c (gen_card_key): Rework. Remove arg PARA. + (generate_keypair): Change arg BACKUP_ENCRYPTION_DIR to the flag + CARD_BACKUP_KEY. + (pBACKUPENCDIR): Change to pCARDBACKUPKEY. + (struct output_control_s): Remove struct SEC. Remove all usages + of it. + (gen_card_key_with_backup): Remove arg BACKUP_DIR. + + * call-agent.c (agent_scd_genkey): Remove extra memset. + +2010-11-16 Werner Koch + + * keygen.c (generate_card_subkeypair): Remove arg SEC_KEYBLOCK and + change to return an error code. Rework for removed secring code. + * card-util.c (card_generate_subkey): Remove arg SEC_KEYBLOCK. + Return an error code instead of a success flag. Change caller. + +2010-10-29 David Shaw + + * pkclist.c (select_algo_from_prefs): Make sure the scores can't + overflow when picking an algorithm (not a security issue since we + can't pick something not present in all preference lists, but we + might pick something that isn't scored first choice). + + * pkclist.c (select_algo_from_prefs): Slightly improve the + handling of MD5 in preference lists. Instead of replacing MD5 + with SHA-1, just remove MD5 from the list altogether, and let the + next-highest ranked algorithm be chosen. + +2010-10-27 Werner Koch + + * keygen.c (ask_expire_interval): Do not print the y2038 if we + have an unsigned time_t. + * keyid.c (IS_INVALID_TIME_T): New. + (mk_datestr): Use it to detect the y2038 problem. + +2010-10-26 Werner Koch + + * keyedit.c (change_passphrase): Handle the passwd_nonce. + * call-agent.c (cache_nonce_parm_s): New. + (cache_nonce_status_cb): Use that new struct. + (agent_genkey, agent_import_key, agent_export_key, agent_passwd): + Adjust for that change. + +2010-10-25 Werner Koch + + * passphrase.c (gpg_format_keydesc): Fix printing of main keyid. + + * keyedit.c (JNLIB_NEED_LOG_LOGV): Define. + * call-agent.c (agent_passwd): New. + +2010-10-21 Werner Koch + + * keyedit.c (keyedit_passwd): Simplify. + (change_passphrase): Return an error code and not the change + flag. Remove editing of the keyring. + + * seckey-cert.c: Remove. + * Makefile.am (gpg2_SOURCES): Remove seckey-cert.c + + * revoke.c (gen_revoke): Check that the secret key is available. + +2010-10-20 Werner Koch + + * verify.c (verify_signatures): Use gpg_strerror on open failure + for consistency of error messages. + + * packet.h (PKT_public_key): s/mdc_feature/flags.mdc/. Change all + users. + (PKT_public_key): Split is_disabled into flags.disabled_valid and + flags.disabled. Change all users. + (pk_is_disabled): Adjust for change. + (PKT_public_key): s/is_primary/flags.primary/. Change all users. + (PKT_public_key): s/is_revoked/flags.revoked/. Change all users. + (PKT_public_key): s/maybe_revoked/flags.maybe_revoked/. Change all + users. + (PKT_public_key): s/is_valid/flags.valid/. Change all users. + (PKT_public_key): s/dont_cache/flags.dont_cache/. Change all users. + (PKT_public_key): s/backsig/flags.backsig/. Change all users. + + * sign.c (openpgp_card_v1_p): New. + (hash_for): Re-implement test for v1 cards. + * packet.h (PKT_public_key): Add field serialno and + flags.serialno_valid. + * free-packet.c (release_public_key_parts): Free serialno. + + * parse-packet.c (parse_key): Cast -1 to size_t. + * trustdb.c (validate_keys): Cast -1 to size_t. Suggested by + Steven M. Schweda. + +2010-10-18 Werner Koch + + * call-agent.c (agent_scd_pksign): Replace sprintf by bin2hex. + (agent_scd_pkdecrypt, agent_pksign): Ditto. + + * sign.c (do_sign): Remove warning and commented old code. + +2010-10-14 Werner Koch + + * call-agent.c (agent_genkey): Add arg NO_PROTECTION. + * keygen.c (do_create, gen_elg, gen_dsa, gen_rsa, common_gen): Add + arg KEYGEN_FLAGS. + (read_parameter_file): Add options no-protection and transient-key. + (KEYGEN_FLAG_NO_PROTECTION, KEYGEN_FLAG_TRANSIENT_KEY): New. + (gen_rsa, gen_dsa, gen_elg): Use transient-key. + +2010-10-13 Werner Koch + + * call-agent.c (start_agent): Send option agent-awareness. + (status_sc_op_failure): Take care of GPG_ERR_FULLY_CANCELED. + * passphrase.c (passphrase_get): Ditto. + * import.c (transfer_secret_keys): Ditto. + * card-util.c (write_sc_op_status): Ditto. + + * getkey.c (enum_secret_keys): Rewrite. + + * pubkey-enc.c (get_session_key): Skip keys without an encryption + capability. Handle GPG_ERR_FULLY_CANCELED. + * gpg.c: Add option --try-secret-key. + * options.h (struct opt): Add field secret_keys_to_try. + + * passphrase.c (next_to_last_passphrase): Remove. + +2010-10-12 Werner Koch + + * keygen.c (generate_subkeypair): Check availibility of secret parts. + + * keylist.c (print_card_serialno): Change to take a hexified serialno. + (list_keyblock_print): Print serialno and stub key indicators. + (list_keyblock_colon): Ditto. + + * getkey.c (have_any_secret_key): Remove. Replace all calls by + agent_probe_any_secret_key. + * gpgv.c (agent_probe_any_secret_key): New. + (agent_get_keyinfo): New. + +2010-10-08 Werner Koch + + * gpg.c: Add option --with-keygrip. + * options.h (struct opt): Add WITH_KEYGRIP. + * keylist.c (list_keyblock_print, list_keyblock_colon): Implement + new option. + +2010-10-06 Werner Koch + + * import.c (transfer_secret_keys): Ignore missing key parameters. + Provide dummy IV. Ignore stub keys. + +2010-10-01 Werner Koch + + * export.c (do_export_stream): Rewrite to take the secret keys + from the agent. + (canon_pubkey_algo, transfer_format_to_openpgp): New. + +2010-09-29 Werner Koch + + * keygen.c (key_from_sexp): Fix memory leak in the error case. + + * call-agent.c (agent_export_key): New. + +2010-09-29 Werner Koch + + * build-packet.c (build_packet): Fix up the pkttype. + + * keyid.c (keystr_with_sub): Make SUB_KID optional. + (keystr_from_pk_with_sub): Ditto. + + * call-agent.c (agent_scd_pksign): Add missing space. + + * mainproc.c (struct mainproc_context): Add field CTRL. + (proc_packets): Add arg CTRL. Change all callers. + (proc_signature_packets, proc_signature_packets_by_fd) + (proc_encryption_packets): Add arg CTRL. Change all callers. + * compress.c (handle_compressed): Ditto. + * getkey.c (get_pubkey_byname): Ditto. + * keyserver.c (keyserver_spawn, keyserver_work): Ditto. + (show_prompt, keyserver_export, keyserver_import) + (keyserver_import_fprint, keyserver_import_keyid) + (keyserver_refresh, keyserver_search, keyserver_fetch) + (keyserver_import_name, keyserver_search_prompt) + (keyserver_import_pka, keyserver_import_cert): Ditto. + callers. + * verify.c (verify_signatures, verify_files): Ditto. + * sign.c (sign_file): Ditto. + * encrypt.c (encrypt_crypt, encrypt_crypt_files): Ditto. + * pkclist.c (find_and_check_key, build_pk_list): Ditto. + * keylist.c (locate_one, public_key_list, secret_key_list): Ditto. + * card-util.c (fetch_url, card_edit): Ditto. + * import.c (check_prefs, import_one, revocation_present): Ditto. + * keyedit.c (menu_addrevoker, keyedit_menu): Ditto. + * decrypt-data.c (decrypt_data): Ditto. + * decrypt.c (decrypt_message, decrypt_messages) + (decrypt_message_fd): Ditto. + * gpgv.c (main): Add CTRL structure. + +2010-09-28 Werner Koch + + * options.h (struct opt): Remove SIMPLE_SK_CHECKSUM. + + * export.c (parse_export_options): Remove option + export-resert-subkey-passwd. + (do_export_stream, do_export, export_pubkeys) + (export_pubkeys_stream, export_seckeys, export_secsubkeys): Add + arg CTRL. Change all callers. + + * call-agent.c (hash_algo_option): New. + (agent_scd_pksign): Use it. + +2010-09-17 Werner Koch + + * call-agent.c (agent_probe_any_secret_key): New. + +2010-09-28 David Shaw + + * options.skel: Make the example for force-v3-sigs match + reality (it has defaulted to off since 2007-10-25). + +2010-09-06 Werner Koch + + * card-util.c (card_status): Remove stub creation for GnuPG >= 2. + (card_store_subkey): Temporary disable this code. + + * keyedit.c (show_key_with_all_names): Merge secret and public key + parts. + (show_basic_key_info): Ditto. + * delkey.c (do_delete_key): Ditto. + * export.c (subkey_in_list_p, exact_subkey_match_p): Ditto. + (new_subkey_list_item): Ditto. + * keyid.c (keystr_from_sk, keystr_from_sk_with_sub) + (keyid_from_sk, nbits_from_sk, datestr_from_sk) + (expirestr_from_sk, colon_datestr_from_sk, fingerprint_from_sk) + (serialno_and_fpr_from_sk, do_fingerprint_md_sk): Remove. + * import.c (print_import_ok): Remove arg SK. + (import_secret_one): Adjust for seckey_info format. + (transfer_secret_keys): Ditto. Use gpg_format_keydesc. + (sec_to_pub_keyblock): Simplify. + (pub_to_sec_keyblock): Remove. + (auto_create_card_key_stub): Remove - not anymore needed. + (update_sec_keyblock_with_cardinfo): Remove. + (import_secret_one): Use arg option instead of the global option. + * free-packet.c (copy_public_key): Adjust for seckey_info format. + (copy_public_parts_to_secret_key, copy_secret_key) + (cmp_secret_keys, cmp_public_secret_key): Remove. + * passphrase.c (gpg_format_keydesc): Add arg MODE and change all + callers. + * keyring.c (keyring_search): Remove special case for secret keys. + * mainproc.c (struct mainproc_context): Remove unused field + LAST_SECKEY. + * parse-packet.c (parse_key): Rewrite to cope with new seckey_info + format. + * build-packet.c (do_public_key, do_secret_key): Merge code into ... + (do_key): .. new. Cope with seckey_info format. + +2010-09-03 Werner Koch + + * packet.h (struct seckey_info): New. + (PKT_public_key): Increase size of PKEY to allow storing of secret + keys. Add field SECKEY_INFO. + (PKT_secret_key): Remove. + * free-packet.c (release_public_key_parts): Take care of change. + (release_secret_key_parts, free_secret_key): Remove. + +2010-09-02 Werner Koch + + * import.c (transfer_secret_keys, import_secret_one): Enable stats. + (import_secret_one): Enable secret key merging. + +2010-09-01 Werner Koch + + * sign.c (do_sign, write_signature_packets, complete_sig): Add arg + CACHE_NONCE. + (make_keysig_packet): Ditto. + * keygen.c (make_backsig, write_direct_sig, write_selfsigs) + (write_keybinding): Add arg CACHE_NONCE. + (do_generate_keypair): Use cache_nonce to avoid a pinentry for the + self-signatures. + + * passphrase.c (gpg_format_keydesc): Remove now superfluous + algo_name fallback. + + * keygen.c (gen_elg, gen_dsa, gen_rsa, do_create, common_gen): Add + arg CACHE_NONCE_ADDR. + (generate_subkeypair): Pass NULL for CACHE_NONCE_ADDR. + (do_generate_keypair): Add cache nonce handling. + + * import.c (transfer_secret_keys): Support a cache nonce. + * call-agent.c (cache_nonce_status_cb): New. + (agent_genkey, agent_import_key): Add arg CACHE_NONCE_ADDR. + (agent_pksign): Ditto. + +2010-08-30 Werner Koch + + * keyid.c (KEYID_STR_SIZE): New + (keystr): Use snprintf and new macro. + (keystr_with_sub): New. + (keystr_from_sk_with_sub): New. + (keystr_from_pk_with_sub): New. + +2010-08-27 Werner Koch + + * gpg.c (main): Change scope of CTRL to the entire function. + + * import.c (import_secret_one, import, import_keys_internal) + (import_keys, import_keys_stream): Add arg CTRL. + * call-agent.c (agent_keywrap_key): New. + (agent_import_key, inq_import_key_parms): New. + +2010-08-26 Werner Koch + + * misc.c (openpgp_pk_algo_name): New. + (openpgp_md_algo_name): New. + +2010-08-24 Werner Koch + + * options.h (IMPORT_SK2PK): Remove. + * import.c (parse_import_options): Turn convert-sk-to-pk into a + dummy option. + (sec_to_pub_keyblock): Use modern functions. + +2010-08-16 Werner Koch + + * gpg.c (list_config, gpgconf_list): Use es_printf. + (print_hex, print_hashline, print_algo_numbers) + (print_algo_names): Use es_printf. + +2010-07-20 Werner Koch + + * mainproc.c (print_pkenc_list): Write a STATUS_ERROR. Fixes + bug#1255. + +2010-06-17 Werner Koch + + * gpg.c (main): Use CAST5 as default s2k algo. The macro + USE_CAST5 was only used with GnuPG 1.x. + +2010-06-07 Werner Koch + + * cpr.c: Use estream for status output. + +2010-05-12 Werner Koch + + * armor.c (radix64_read): Extended 2006-04-28 fix to fix bug#1179. + + * plaintext.c (handle_plaintext): Check return code of fflush. + Fixes bug#1207. + +2010-05-07 Werner Koch + + * import.c (fix_bad_direct_key_sigs): New. + (import_one): Call it. + (chk_self_sigs): Re-indent, slighly re-arrange code, use test + macros for the sig class. Check direct key signatures. Fixes + bug#1223. + +2010-04-27 Werner Koch + + * passphrase.c (gpg_format_keydesc): New. + * pubkey-enc.c (get_it): Use it. + * sign.c (do_sign): Use it. + +2010-04-26 Werner Koch + + * keygen.c (keygen_set_std_prefs): Explicitly include Z0 in the + default preferences if no compression algorithms are available. + Remove a possible trailing space in the dummy_string. + +2010-04-23 Werner Koch + + * pubkey-enc.c (get_it): Use the agent for decryption. + * call-agent.c (agent_pkdecrypt, inq_ciphertext_cb): New. + +2010-04-22 Werner Koch + + * photoid.c (show_photos): Remove arg SK. + + * pubkey-enc.c (get_session_key, get_it): Change to use the public + key object. + (get_it): Remove card related stuff. Now automagically handled + by the agent. + + * skclist.c (build_sk_list): Remove UNLOCK arg. + + * keylist.c (print_fingerprint): Remove arg SK. + * mainproc.c (list_node): Disable listing of secret key packets. + + * keyring.c (struct keyring_name, struct keyring_handle): Remove + field SECRET. + (keyring_register_filename, keyring_new, orename_tmp_file) + (do_copy): Remove arg SECRET. + * keydb.c (struct resource_item): Remove field SECRET. + (keydb_add_resource): Remove arg SECRET. + (keydb_new): Remove code fro secret keyrings. + + * gpg.c (main): Ignore --secret-keyring. Remove all secret + keyring related code. + +2010-04-21 Werner Koch + + * pkclist.c (default_recipient): Change to use public keys. + + * keydb.c (keydb_new): Remove arg SECRET. Change all callers. + + * getkey.c (get_seckey): Change to take a public key. + (have_secret_key): Rename to have_any_secret_key and make use of + the agent. + (key_byname): Rmemove unused arg SK. + (get_seckey_byname2): Remove and move code to + (get_seckey_byname): .. here. Remove INLOCK arg. + (get_seckey_bynames): Remove. + (get_seckey_next): Remove. + (get_seckey_end): Remove. Use get_pubkey_end instead. + (get_seckey_byfprint, get_seckeyblock_byfprint): Change to use + public keys. + (seckey_available): Rename to .. + (have_secret_key_with_kid): .. this and change to employ the + agent. Change all callers. + (sk_from_block): Remove. + + * call-agent.c (agent_probe_secret_key): New. + (agent_havekey): Remove. + * gpgv.c (agent_probe_secret_key): New. + + * keyedit.c (keyedit_menu) + (sign_uids, menu_adduid, menu_deluid, menu_delkey) + (menu_addrevoker, menu_expire, menu_backsign) + (menu_set_primary_uid, menu_set_preferences) + (menu_set_keyserver_url, menu_set_notation, menu_revsig) + (menu_revuid, menu_revkey, menu_revsubkey): Remove all code to + manage the secret keyring. + +2010-04-20 Werner Koch + + * keylist.c (list_keyblock_colon): Print the keygrip. + + * sign.c (do_sign): Call the agent to create the signature. + (mpi_from_sexp): New. + * keyid.c (keygrip_from_pk, hexkeygrip_from_pk): New. + * call-agent.c (agent_pksign): New. + + * pkglue.c (pk_sign): Remove. + + * keygen.c (generate_keypair): Do not ask for a passphrase. + +2010-04-15 Werner Koch + + * keygen.c (gen_dsa, gen_elg, gen_rsa): Remove args SEC_ROOT, DEK, + S2K and RET_SK. Change to use the gpg-agent based key generation. + Factor common code out to ... + (common_gen): New. + (do_create): Remove args SEC_ROOT, DEK, S2K and RET_SK. + (do_generate_keypair, write_selfsigs, write_direct_sig) + (write_keybinding, make_backsig): Adjust for above changes. + (generate_subkeypair): Remove arg SEC_KEYBLOCK. + (genhelp_protect, genhelp_factors): Remove. + (get_parameter_dek, get_parameter_s2k): Remove. + + * call-agent.c (start_agent): Add dummy arg CTRL. + (agent_havekey, keyinfo_status_cb, agent_get_keyinfo) + (agent_genkey): New. + + * seckey-cert.c (check_secret_key): Remove + (is_secret_key_protected): Take a public key as arg. + (protect_secret_key): Remove. + + * seskey.c (encode_md_value): Remove SK arg. + +2010-04-14 Werner Koch + + * cpr.c (myread) [W32CE]: Do not use raise. + + * misc.c (check_compress_algo): Rewrite to handle the new HAVE_ZIP. + * compress.c (push_compress_filter2): Ditto. + (init_compress, do_compress, init_uncompress, do_uncompress) + (compress_filter) [!HAVE_ZIP]: Do not build. + * main.h (DEFAULT_COMPRESS_ALGO): Depend on HAVE_ZIP. + * keygen.c (keygen_set_std_prefs): Use check_compress_algo also + for ZIP and ZLIB. + + * Makefile.am (install-exec-hook) [W32CE]: New. + (bin_PROGRAMS) [W32CE]: Do not build gpgv2. + (gpg2_LDADD): Add extra_syslibs. + +2010-04-06 Werner Koch + + * openfile.c (mkdir): Remove. + (try_make_homedir): Use gnupg_mkdir. + +2010-04-01 Werner Koch + + Use gpg_err_set_errno to set ERRNO. + +2010-03-26 Werner Koch + + * signal.c (pause_on_sigusr): Remove. It was used in ancient gpg + version with shared memory IPC. Last caller removed on 2006-04-18. + +2010-03-24 Werner Koch + + * openfile.c (CMP_FILENAME): Depend on HAVE_DOSISH_SYSTEM instead + of HAVE_DRIVE_LETTERS. + +2010-03-15 Werner Koch + + * card-util.c: Replace stdio by estream. + * keylist.c: Ditto. + +2010-03-12 Werner Koch + + * plaintext.c (setup_plaintext_name): Do not encode pipe like + filenames. This helps with bug#1201. + + * seckey-cert.c (do_check): Return GPG_ERR_CANCELED. + * keyedit.c (change_passphrase): Add arg R_ERR. + (keyedit_passwd): Return the correct error or emit a success + status message. + +2010-03-11 Werner Koch + + * misc.c (mpi_print): Change to take a estream_t arg. + + * parse-packet.c (listfp): Change to an estream_t. Change all + users to use estream functions. + + * kbnode.c (dump_kbnode): Change to use log functions. + * pkclist.c (do_show_revocation_reason): Ditto + + * armor.c (parse_header_line): Replace print_string by + es_print_sanitized. + (fake_packet): Ditto. + * keyedit.c (print_and_check_one_sig_colon): Ditto. + (show_key_with_all_names_colon): Ditto. + (ask_revoke_sig): Ditto. + * keylist.c (list_keyblock_colon): Ditto. + * mainproc.c (print_userid, list_node): Ditto. + * trustdb.c (dump_key_array): Ditto. + * gpg.c (list_config): ditto. + + * gpg.c: Include "asshelp.h". + (main): Remove assuan_set_assuan_log_prefix. Add + assuan_set_log_cb. + * server.c (gpg_server): Remove assuan_set_log_stream. + +2010-03-10 Werner Koch + + * Makefile.am (needed_libs): Remove libjnlib.a. + + * main.h: Remove "estream.h". + +2010-03-08 Werner Koch + + * main.h: Include "estream.h" + * openfile.c (open_outfile): Replace dup/iobuf_fdopen by + iobuf_fdopen_nc. + * mainproc.c (proc_signature_packets_by_fd): Return error on + memory failure. + * plaintext.c (hash_datafile_by_fd): Ditto. + * verify.c (gpg_verify): Use iobuf_fdopen_nc. Change OUT_FP to an + estream_t. + * server.c (cmd_verify): Do not dup the fds. + + Use macros for iobuf_ioctl commands. + +2010-02-17 Werner Koch + + * keygen.c (ask_user_id): Avoid infinite loop in case of invalid + data. Fixes bug#1186. + +2010-02-02 Werner Koch + + * keyedit.c (keyedit_menu): Change prompt to "gpg". + * card-util.c (card_edit): Change prompt to "gpg/card". + +2010-01-11 Werner Koch + + * sign.c (only_old_style, write_onepass_sig_packets, hash_for) + (write_signature_packets, print_status_sig_created) + (clearsign_file, make_keysig_packet, mk_notation_policy_etc) + (complete_sig, do_sign, update_keysig_packet): Replace all + secret key access by the matching public key. + * keylist.c (print_seckey_info): Ditto. + * revoke.c (gen_desig_revoke): Ditto. + * skclist.c (release_sk_list): Ditto. + * keyedit.c (sign_uids): Ditto. + * misc.c (get_signature_count): Ditto. + * main.h (struct expand_args): s/sk/pksk/. Change all users. + + * keyedit.c (keyedit_passwd): Finish implementation. + +2010-01-10 Werner Koch + + * skclist.c (GCRYCTL_FAKED_RANDOM_P): Remove because we require + libgcrypt 1.4. + (is_insecure, key_present_in_sk_list): Work with public keys. + (build_sk_list): Change to work on public keys. + * keydb.h (struct sk_list): Replace field SK by a PK field. + + * keylist.c (list_keyblock_print): Always look for the public key + and ignore all secret key packets. + (list_keyblock_colon): Ditto. + (print_capabilities): Remove arg SK and all secret key stuff. + Adjust all callers. + (dump_attribs): Ditto. + + * getkey.c (getkey_bynames, getkey_next, get_pubkey_end): New. + (getkey_byname): New. + (getkey_ctx_s): Add WANT_SECRET. + (key_byname): Set it. + (merge_keys_and_selfsig): Remove all the secret key merging. + (lookup): Simplify by removing secret key code. + + * keylist.c (list_all): Scan public keys and use have_secret_key + to filter secret keys. + (list_one): Use the new get_key functions. + + * gpg.h (kbnode_t): Add as alias for KBNODE. + * keydb.h (getkey_ctx_t): Add as alias for GETKEY_CTX. + +2010-01-09 Werner Koch + + * getkey.c, keylist.c: Re-indent. + +2010-01-08 Werner Koch + + * cpr.c (write_status_error): Rename to write_status_errcode. + Change all callers. + (write_status_error): New. + + * gpg.c: Add option --passwd. + (aPasswd): New. + (main): Implement. + * keyedit.c (keyedit_passwd): New. + + * gpg.c (oPasswd, oPasswdFD, oPasswdFile, oPasswdRepeat): Change + to oPassphrase, oPassphraseFD, oPassphraseFile, oPassphraseRepeat. + * options.h (struct): s/passwd_repeat/passphrase_repeat/. + * gpg.c (main): Ditto. + * passphrase.c (passphrase_to_dek_ext): Ditto. + +2009-12-21 Werner Koch + + * call-agent.c (agent_get_s2k_count): New. + * gpg.c (main): Set s2k_count to 0. + * (encode_s2k_iterations): Move ... + * passphrase.c (encode_s2k_iterations): ... here. Call + agent_get_s2k_count if called with a 0 arg. + (passphrase_to_dek_ext): Set S2K_COUNT via encode_s2k_iterations. + +2009-12-17 Werner Koch + + * sig-check.c (do_check_messages): Evaluate the HAS_EXPIRED flag. + Fixes bug#1059. + + * gpg.c: Add new option --faked-system-time. + +2009-12-15 Werner Koch + + * keydb.c (keydb_add_resource): s/readonly/read_only/g. + * keyring.c (keyring_register_filename): Ditto. + + * tdbio.c (tdbio_set_dbname): Do not call log_fatal after creating + the directory. Fixes bug#1169. Reported by Daniel Leidert. + +2009-12-08 Werner Koch + + * keyring.h: Include userids.h. + * gpg.h (KEYDB_SEARCH_DESC): Remove. + * packet.h: Include userids.h. + (PKT_user_id): Declare using gpg_pkt_user_id_s. + * keydb.h (KeydbSearchMode, struct keydb_search_desc): Remove. We + now use those in ../kbx. + * getkey.c (classify_user_id): Remove. It is now in common/. + (key_byname): Adjust for changed classify_user_id. + * delkey.c (do_delete_key): Ditto. + * trustdb.c (register_trusted_key): Ditto. + * revoke.c (gen_desig_revoke, gen_revoke): Ditto. + * keyserver.c (parse_keyrec, keyserver_export, keyserver_import) + (keyidlist): Ditto. + * export.c (do_export_stream): Ditto. + + * pkclist.c (find_and_check_key): Replace GPG_ERR_INV_NAME by + GPG_ERR_INV_USER_ID. + +2009-12-04 Werner Koch + + * keygen.c (DEFAULT_STD_ALGO, DEFAULT_STD_KEYSIZE): New. + (ask_keysize): Use new macro. + (gen_rsa): Set default size if NBITS is 0. + (get_parameter_algo): Add algo name "default". Add arg R_DEFAULT. + (proc_parameter_file): Process default flag. + +2009-12-03 Werner Koch + + * gpg.c (set_debug): Allow for numerical debug leveles. Print + active debug flags. + +2009-11-27 Werner Koch + + * keyedit.c (cmds, keyedit_menu): New command "checkbkupkey". + +2009-11-25 Marcus Brinkmann + + * server.c (gpg_server): Use assuan_fd_t and assuan_fdopen on fds. + +2009-11-23 Werner Koch + + * gpg.c (gpgconf_list): Add key "default_pubkey_algo". + +2009-11-18 Werner Koch + + * gpg.c: Add option --skip-hidden-recipients and no- variant. + * options.h (struct opt): Add field SKIP_HIDDEN_RECIPIENTS. + * pubkey-enc.c (get_session_key): Implement that option. + +2009-11-04 Werner Koch + + * server.c (register_commands): Add NULL arg to + assuan_register_command. + +2009-11-02 Marcus Brinkmann + + * server.c (reset_notify, input_notify, output_notify): Update to + new assuan interface. + (register_commands): Use assuan_handler_t. + +2009-10-19 Werner Koch + + * options.h (glo_ctrl): Add field LASTERR. + * mainproc.c (proc_encrypted): Set LASTERR. + * server.c (cmd_decrypt): Check LASTERR. + +2009-10-02 Werner Koch + + * server.c (cmd_encrypt, cmd_decrypt): Implement. + * decrypt.c (decrypt_message_fd): New. + * options.h (struct opt): Add field OUTFP. + * plaintext.c (handle_plaintext): Support opt.outfp. + + * encr-data.c: Rename to decrypt-data.c to reflect the action and + not the processed packet type. + +2009-10-02 Werner Koch + + * encr-data.c (decode_filter_context_s): Add fields PARTIAL and + LENGTH. + (decrypt_data): Set them. Simplify premature EOF detection. + (mdc_decode_filter): Take fixed length packets in account. + (decode_filter): Ditto. Better EOF detection. + * parse-packet.c (parse_encrypted): Store ed->LEN without the MDC + version byte. + +2009-09-30 Werner Koch + + * parse-packet.c (skip_packet, parse_gpg_control) : Take + care of premature EOFs. + + * gpg.c (main): Remove obsolete GCRYCTL_DISABLE_INTERNAL_LOCKING. + +2009-09-29 Werner Koch + + * openfile.c (open_outfile): Re-indent. Use xstrconcat. + (NAME_OF_DEV_NULL): New. + (open_outfile): Use it. + (overwrite_filep): Use it. Also use case insensitive compare + when needed. Re-indent. + (open_outfile): Add arg INP_FD. Change all callers. + + * encrypt.c (encrypt_crypt): Add new args FILEFD, OUTPUTFD and + PROVIDED_KEYS. Change all callers. + +2009-09-28 Werner Koch + + * server.c (skip_options, has_option): New. + (cmd_recipient): Implement. + + * keydb.h (pk_list_t): New. + + * pkclist.c (send_status_inv_recp): New. Replace direct calls. + (build_pk_list): Factor some code out to ... + (find_and_check_key): ... new. + + * encode.c: Rename to encrypt.c. Re-indent all. + * encrypt.c (encode_symmetric, encode_store, encode_seskey) + (encode_simple, encode_crypt, encode_filter) + (encode_crypt_files): Rename all to encrypt_*. Change all callers. + + * trustdb.c (get_validity_info): Take care of a NULL PK. Fixes + bug#1138. + (get_validity_string): Ditto. + +2009-09-25 Werner Koch + + * pkglue.c (pk_sign, pk_verify, pk_encrypt, pk_decrypt) + (pk_check_secret_key): Allow deprecated RSA identifiers 2 and 3. + Fixes bug#1139. + +2009-09-23 Marcus Brinkmann + + * call-agent.c: Include "scdaemon.h" before because of + GPG_ERR_SOURCE_DEFAULT check. + (learn_status_cb, dummy_data_cb, get_serialno_cb, default_inq_cb) + (learn_status_cb, inq_writecert_parms, inq_writekey_parms) + (scd_genkey_cb, membuf_data_cb): Return gpg_error_t instead of + int. + * gpg.c: Include "scdaemon.h" before because of + GPG_ERR_SOURCE_DEFAULT check. + (main): Update to new Assuan API. + * server.c: Include "scdaemon.h" before because of + GPG_ERR_SOURCE_DEFAULT check. + (option_handler, cmd_recipient, cmd_signer, cmd_encrypt) + (cmd_decrypt, cmd_verify, cmd_sign, cmd_import, cmd_export) + (cmd_delkeys, cmd_message, do_listkeys, cmd_listkeys) + (cmd_listsecretkeys, cmd_genkey, cmd_getinfo): Return gpg_error_t + instead of int. + (register_commands): Allocate assuan context before starting + server. + (gpg_server): Allocate assuan_context before starting server. + +2009-09-04 Werner Koch + + * keyedit.c (menu_select_uid): Use IDX ==-1 t select all. + (menu_select_key): Ditto. + (keyedit_menu) : Allow '*' to select all. + +2009-09-03 Werner Koch + + * keyedit.c (menu_adduid): Pass keyblock to generate_user_id. + * keygen.c (generate_user_id): Add arg KEYBLOCK. Factor code out + to ... + (uid_from_string): ... new. + (ask_user_id): Add arg KEYBLOCK and check for duplicates. Fix + bug#1122. + + * Makefile.am (uninstall-local): New. + + * compress-bz2.c (do_uncompress): Detect unexpected EOF. Fix + bug#1011. + +2009-08-26 Werner Koch + + * keyedit.c (menu_revsig): Check for signature right away. Fix + Debian-bug#543530. + +2009-08-20 Daiki Ueno + + * mainproc.c (proc_encrypted): Clear passphrase cached with S2K + cache ID if decryption failed. + * passphrase.c (passphrase_to_dek_ext): Set dek->s2k_cacheid. + * gpgv.c (passphrase_clear_cache): New stub. + +2009-08-11 Werner Koch + + * call-agent.c (get_serialno_cb): New. From ../agent/call-scd.c. + (gpg_agent_get_confirmation): New. + (select_openpgp): New. + (agent_scd_pkdecrypt, agent_scd_pksign): Use it here. + +2009-08-06 Werner Koch + + * skclist.c (build_sk_list): Print INV_SGNR status line. + * seckey-cert.c (do_check): Return G10ERR_UNU_SECKEY instead of + general error. + +2009-08-05 Werner Koch + + * card-util.c: Enable readline support also in GnuPG-2. + + * call-agent.c (agent_learn): Always select the card first. + + * gpg.c: Add --key-edit alias. + + * call-agent.c (scd_genkey_cb): Forward progress status lines. + + * card-util.c (generate_card_keys): Remove special case for + GnuPG-2. Ask for the keysize and change it. + (card_generate_subkey): Ask for the keysize and change it. + (get_info_for_key_operation): Read KEY-ATTR. + (show_keysize_warning, ask_card_keysize): New. + (do_change_keysize): New. + +2009-07-31 David Shaw + + * gpg.c (main): --pgp6 includes --disable-mdc. + +2009-07-23 David Shaw + + * keyserver.c (keyserver_import_ldap): Try a DNS-SD lookup to find + a domain-specific LDAP server before resorting to keys.{domain}. + +2009-07-22 Werner Koch + + * card-util.c (generate_card_keys): Ask for off-card keys only if + the card supports it. + (get_info_for_key_operation): Read EXTCAP. + (card_store_subkey): Check for non matching sizes. + + * call-agent.h (struct agent_card_info_s): Add field EXTCAP. + * call-agent.c (agent_learn): Use a direct SCD command. + (did_early_card_test): New. + (start_agent): Perform an early test for the card. Add arg FOR_CARD. + (status_sc_op_failure): New. + (agent_scd_setattr, agent_scd_writekey, agent_scd_genkey) + (agent_scd_pksign, agent_scd_pkdecrypt, agent_scd_change_pin) + (agent_scd_checkpin): Call new function. + (learn_status_cb): Parse KEY_TIME and EXTCAP. + + * gpg.c (main) : Show commands to run. + * trustdb.c (how_to_fix_the_trustdb): New. + * tdbio.c (tdbio_invalid): Show commands to re-create the trustdb. + Fixes bug#929. + +2009-07-20 Werner Koch + + * keygen.c (generate_keypair): Allow Elgamal > 3072 in BOTH mode. + Reported by Jeroen Schot. Fixes bug#1091. + +2009-07-17 Werner Koch + + * keyring.c (keyring_rebuild_cache): Replace the assert by a + proper error message and allow to delete a bad keyblock. + +2009-07-13 Werner Koch + + * exec.c: Fix function name indentation. + (expand_args): Simplify by using membuf functions. + (exec_write): Fix memory leak on error. + (w32_system): Use DETACHED_PROCESS so that a new console is not + created. + +2009-07-09 Werner Koch + + * card-util.c (card_store_subkey): Do not restrict to 1024 bit keys. + Print an error message on write errors. + + * gpg.c (main): Remove the SHA-1 default from the personal digest + list. This was used in the past as a hack to avoid preferring + RMD-160. + + * keygen.c (keygen_set_std_prefs): Remove RMD-160 from the list. + Change order to SHA-256, SHA-1, SHA-384, SHA-512, SHA-224. + (gen_dsa): Use a 256 bit Q for 2048 bit P. Round to FIPS allowed + values in non-expert mode. + +2009-07-07 Werner Koch + + * gpg.c (set_opt_session_env): New. + (main): Allocate opt.session_env. Use it for oDisplay, oTTYname, + oTTYtype and oXauthority. + + * options.h: Include session_env.h. + (opt): Add field SESSION_ENV, remove obsolete fields. + + * call-agent.c (start_agent): Adjust start_new_gpg_agent for + changed args. + +2009-06-24 Werner Koch + + * keyedit.c (menu_select_key): Remove dead assign to I. + (menu_select_uid): Ditto. + * keyring.c (keyring_search): Remove dead assign to NAME. + * card-util.c (card_edit): Remove useless DID_CHECKPIN. + * call-agent.c (unhexify_fpr): Remove dead op on N. + * passphrase.c (passphrase_to_dek_ext): Do not deref a NULL PW. + * revoke.c (gen_revoke): Remove unused malloc of PK. + * parse-packet.c (mpi_read): Init NREAD. + Reported by Fabian Keil. + +2009-06-17 Werner Koch + + * parse-packet.c (parse): Use a casted -1 instead of a 32 bit + constant to check for a garbled package. Fixes bug#1040. + + * card-util.c (put_data_to_file, read_cert): New. + (card_edit): Add command "readcert". + (fetch_url): Allow code also for this gnupg major version 2. + * call-agent.c (agent_scd_readcert): New. + +2009-06-15 Werner Koch + + * keyserver.c (keyserver_search_prompt): No prompt in batch+colons + mode. + +2009-06-09 Werner Koch + + * card-util.c (write_sc_op_status): New. + (change_pin): Use it. + (change_url, change_login, change_private_do, change_cert) + (change_lang, change_sex, change_cafpr, toggle_forcesig) + (check_pin_for_key_operation): Ditto. + +2009-06-05 David Shaw + + * gpg.c (main), misc.c (openpgp_cipher_test_algo): Remove Camellia + restriction. + + * misc.c (map_cipher_openpgp_to_gcry), main.h: Add macros for + openpgp_cipher_open, openpgp_cipher_get_algo_keylen, and + openpgp_cipher_get_algo_blklen to wrap around the corresponding + gcry_* functions, but pass the algorithm number through + map_cipher_openpgp_to_gcry. This is needed in case the gcry + algorithm number doesn't match the OpenPGP number (c.f. Camellia). + + * encr-data.c, pubkey-enc.c, mainproc.c, cipher.c, encode.c, + seskey.c, passphrase.c, seckey-cert.c: Use new openpgp_cipher_* + macros here. + +2009-06-02 Werner Koch + + * card-util.c (get_manufacturer): Add new manufacturer. + +2009-05-26 Werner Koch + + * parse-packet.c (mpi_read): Workaround for zero-length MPI bug in + libgcrypt<1.5.0. + +2009-05-22 Werner Koch + + * signal.c (got_fatal_signal): Call new function + tty_cleanup_after_signal. + +2009-05-20 Werner Koch + + * gpg.c (main): Fix --fingerprint/--with-fingerprint command + detection. Fixes bug#1044. + + * keygen.c (ask_keysize): Allow selection of DSA key size even + without --enable-dsa2. + (gen_dsa): Remove size check. + + * keygen.c (ask_key_flags): Fix bug in the translation check. + Fixes bug#1056. + +2009-05-18 Daiki Ueno (wk) + + * encode.c (encode_simple): Tell passphrase_to_dek to cache + the passphrase. + (setup_symkey): Ditto. + * mainproc.c (proc_symkey_enc): Tell passphrase_to_dek to cache + the passphrase. + (proc_encrypted): Ditto. + * passphrase.c (hash_passphrase): Remove arg CREATE. + (passphrase_to_dek): New mode 3 and 4 for caching passphrase for + symmetric encryption. + +2009-05-17 Werner Koch + + * keygen.c (ask_algo): Add arg R_SUBKEY_ALGO. Change return value + semantics. Change presented order of algorithms. Make RSA+RSA + the default. + (generate_keypair): Adjust for change. + (ask_keysize): Add arg PRIMARY_KEYSIZE for subkey creation. + Change callers. + +2009-05-15 Werner Koch + + * keygen.c (gen_card_key_with_backup): Get the size of the key + from the card. + * call-agent.h (struct agent_card_info_s): Add field KEY_ATTR. + * call-agent.c (learn_status_cb): Support KEY-ATTR. + * card-util.c (card_status): Print key attributes. + +2009-05-15 Marcus Brinkmann + + * gpg.c (gpgconf_list): Remove dead entry "allow-pka-lookup" (a + verify option for a couple of years now). + +2009-05-14 Werner Koch + + * call-agent.c (agent_get_passphrase): Add arg CHECK. + * passphrase.c (passphrase_get): Pass new arg. + + * keygen.c (gen_card_key_with_backup): Print a status error. + (do_generate_keypair): Ditto. + (do_ask_passphrase): Add arg MODE. + (generate_raw_key): Call with mode 1. + * passphrase.c (ask_passphrase): Remove becuase it is not used. + (passphrase_to_dek): Factor code out to ... + (passphrase_to_dek_ext): .. New. Add args CUSTDESC and CUSTPROMPT. + +2009-05-13 Werner Koch + + * keygen.c (parse_expire_string): Base ISO date string at noon. + Also allow full ISO timestamp. + +2009-05-11 Werner Koch + + * parse-packet.c (parse_key): Print the key id in list mode. + + * skclist.c (build_sk_list): Use log_info for "duplicated entry". + Fixes bug#1045. + + * encode.c (encode_simple): Print empty file warning only in + verbose mode. Closes bug#1039. + (encode_crypt): Ditto. + * sign.c (write_plaintext_packet): Ditto. + +2009-05-10 David Shaw + + * keyserver.c (keyserver_typemap): gpgkeys_hkp handles hkps as + well. From 1.4. + +2009-05-06 Werner Koch + + * getkey.c (finish_lookup): Remove dead code. + + * keyring.c (keyring_get_keyblock): Fix memory leak due to ring + trust packets. Fixes bug#1034. + +2009-04-03 Werner Koch + + * gpgv.c (main): Open keyrings readonly. + * keydb.c (keydb_add_resource): Add readonly flag bit. + (keydb_rebuild_caches): Don't act on readonly resources. + + * keyring.c (keyring_register_filename): Add arg READONLY. + (struct keyring_name): Add field READONLY. + (keyring_is_writable): Implement readonly feature. + (keyring_update_keyblock): Return GPG_ERR_EACCES for readonly + keyrings. + (keyring_insert_keyblock, keyring_delete_keyblock): Ditto. + +2009-04-01 Werner Koch + + * gpg.c (main): Properly handle UTF8 usernames with --sign-key and + --lsign-key. From 1.4, David 2008-12-21. + +2009-03-20 David Shaw (wk) + + * keyring.c (rename_tmp_file): Force a fsync (via iobuf_ioctl) on + secret keyring files to be extra safe on filesystems that may not + sync data and metadata together (ext4). Also check return code + from the cache invalidation to make sure we're safe over NFS and + similar. + +2009-03-31 Werner Koch + + * passphrase.c (ask_passphrase): Use percent_plus_unescape. + * misc.c (unescape_percent_string): Remove. + + * call-agent.c (unescape_status_string): Chnage to use + percent_plus_unescape. + +2009-03-25 Werner Koch + + * mainproc.c (print_pkenc_list): Use snprintf. + +2009-03-17 Werner Koch + + * call-agent.c (my_percent_plus_escape): Remove. + (agent_get_passphrase): Rewrite using percent_plus_escape. + +2009-03-17 Daiki Ueno + + * passphrase.c (passphrase_get): Add extra arg REPEAT and adjust + callers; remove special treatment for MODE==2. + (passphrase_to_dek): Move --passphrase-repeat handling to + gpg-agent. + + * call-agent.c (agent_get_passphrase): Add extra arg REPEAT. + * call-agent.h: Ditto. + +2009-03-16 Werner Koch + + * gpg.c (my_strusage): Revert last change. Systems w/o a gpg1 may, + and actually do, install gpg2 as gpg. + * gpgv.c (my_strusage): Ditto. + +2009-03-14 David Shaw + + * gpg.c (my_strusage): gpg2 and gpgv2 (not gpg and gpgv). + * gpgv.c (my_strusage): Same. + + * gpgv.c (my_strusage): Fix name of program in "Syntax" line. + +2009-02-27 Werner Koch + + * call-agent.c (agent_scd_pksign, agent_scd_pkdecrypt): First send + the SERIALNO command. + +2009-02-24 Werner Koch + + * pkglue.c (pk_verify): Return an error for improper DATA instead + of calling BUG(). + +2009-02-09 Werner Koch + + * keylist.c (print_capabilities): Take care of cert-only keys. + Fixes bug#998. + * keyedit.c (show_key_with_all_names_colon): Print the capabilities. + +2009-01-26 Werner Koch + + * card-util.c (card_status): Detect a Geldkarte. + +2009-01-13 Werner Koch + + * call-agent.c (dummy_data_cb): New. + (agent_learn): Use it. + * card-util.c (card_status): Print type of non-OpenPGP card. + * call-agent.h (agent_card_info_s): Add field APPTYPE. + +2009-01-12 Werner Koch + + * getkey.c (finish_lookup): Take care of keys with a zero + timestamp. Reported by Peter Gutmann. + +2009-01-08 Werner Koch + + * misc.c (has_invalid_email_chars): Let non-ascii pass through. + + * cpr.c [USE_SHM_COPROCESSING]: Remove this code. + +2008-12-12 Werner Koch + + * passphrase.c (passphrase_get): Write a STATUS_ERROR. + * cpr.c (write_status_error): New. + + * Makefile.am (common_source): Add rmd160.h. + +2008-12-11 Werner Koch + + * sig-check.c (signature_check2): Change algorithm used to compute + the SIG_ID. + (check_revocation_keys): Close message digest. + + * rmd160.c, rmd160.h: New. Based on code from GnuPG-1.4. + * t-rmd160.c: New. + * Makefile.am: Add support to run tests. + * keyid.c (namehash_from_uid): Use rmd160_hash_buffer. + +2008-12-10 Werner Koch + + * trustdb.h (NAMEHASH_HASH): Remove unsued constant. + + * gpg.c (print_mds): Print RMD160 only is enabled. + + * keygen.c (keygen_set_std_prefs): Include RMD160 only if + available. + +2008-12-09 Werner Koch + + * gpg.c (main) [IS_DEVELOPMENT_VERSION]: Fix strusage use. + +2008-12-09 Werner Koch + + * keygen.c (proc_parameter_file): Check that key and subkey usages + are allowed. + +2008-12-09 David Shaw (wk) + + * trustdb.c (validate_one_keyblock): Fix the trust signature + calculations so that we lower the trust depth of signatures to fit + within the current chain, rather than discarding any signature + that does not fit within the trust depth. + +2008-12-09 Werner Koch + + * keyserver.c (show_prompt): Flush stdout. + + * gpg.c (open_info_file): Add arg BINARY and adjust callers. + + * gpg.c (main): Call i18n_init before init_common_subsystems. + * gpgv.c (main): Ditto. + + * keylist.c (set_attrib_fd): Do not close ATTRIB_FP if it is the + log stream. + (set_attrib_fd) [W32]: Set to binary mode. + (dump_attribs): Flush the stream after writing. + +2008-12-05 Werner Koch + + * call-agent.c (percent_plus_escape): Rename to + my_percent_plus_escape and also escape the percent character. + Change all callers. + +2008-11-18 Werner Koch + + * gpg.c (build_lib_list): Remove. + (make_libversion): New. + (my_strusage): Use it. + * gpgv.c (make_libversion): New. + (my_strusage): Print libgcrypt version. + +2008-11-13 Werner Koch + + * gpgv.c: Use new ARGPARSE macros and re-indent. + +2008-11-11 Werner Koch + + * gpg.c (opts): Use new ARGPARSE macros for clarity. + +2008-10-24 Werner Koch + + * keyedit.c (change_passphrase): Clear passphrase cache. + +2008-10-20 Werner Koch + + * gpgv.c: Mark all args of the stub fucntions as unused. + + * card-util.c (generate_card_keys): Remove unused arg SERIALNO and + adjust caller. + + * build-packet.c (write_sign_packet_header): Mark unused arg. + * gpg.c (gpg_init_default_ctrl, gpg_deinit_default_ctrl): Ditto. + * getkey.c (skip_unusable): Ditto. + (write_version): Ditto. + * keydb.c (keydb_locate_writable): Ditto. + * keyring.c (update_offset_hash_table): Ditto. + (keyring_lock): Ditto. + * misc.c (register_secured_file): Ditto. + (unregister_secured_file): Ditto. + (is_secured_file): Ditto. + (is_secured_filename): Ditto. + * parse-packet.c (parse_marker): Ditto. + (parse_key, parse_attribute): Ditto. + (parse_trust, parse_compressed, parse_mdc, parse_gpg_control): Ditto. + * cpr.c (progress_cb): Ditto. + * passphrase.c (passphrase_clear_cache): Ditto. + (ask_passphrase): Ditto. + * keyedit.c (keyedit_completion): Ditto. + * import.c (import_revoke_cert): Ditto. + (chk_self_sigs, delete_inv_parts, append_uid): Ditto. + (merge_sigs, merge_keysigs, append_key): Ditto. + * trustdb.c (list_trust_path): Ditto. + (enum_cert_paths, enum_cert_paths_print): Ditto. + * tdbdump.c (list_trustdb): Ditto. + * keygen.c (keygen_upd_std_prefs): Ditto. + (genhelp_factors): Ditto. + * call-agent.c (agent_scd_setattr): Ditto. + (agent_scd_writekey, agent_scd_change_pin, agent_scd_genkey): Ditto. + (agent_clear_pin_cache): Ditto. + + * server.c (option_handler): Mark non yet used arg. + (input_notify, output_notify): Ditto. + (cmd_recipient, cmd_signer, cmd_encrypt, cmd_decrypt, cmd_verify) + (cmd_sign, cmd_import, cmd_export, cmd_delkeys, do_listkeys) + (cmd_genkey): Ditto. + * verify.c (gpg_verify): Ditto. + +2008-10-17 Werner Koch + + * main.h (idea_cipher_warn): Use do while construct in place of an + empty definition. + +2008-10-03 David Shaw + + * main.h, mainproc.c (check_sig_and_print) + * keylist.c (list_keyblock_print) + * pkclist.c (do_edit_ownertrust) + * keyedit.c (menu_showphoto) + * photoid.c (generate_photo_id, show_photos) + * misc.c (pct_expando): Add %v and %V expandos so + that displaying photo IDs can show the attribute validity + tag (%v) and string (%V). Originally by Daniel Gillmor. + +2008-09-29 Werner Koch + + * gpg.c (main): Remove -sat kludge. Note that we printed a + warning for two years. + + * seskey.c (encode_md_value): Remove extra gcry_md_test_algo since + it is not needed with Libgcrypt 1.4. + * skclist.c (random_is_faked): Simplify. + * sign.c (match_dsa_hash): Remove runtime check for SHA224. + * gpg.c (print_mds): Use GCRY_MD_SHA224 constant. + +2008-09-25 David Shaw + + * keyedit.c (keyedit_menu): Fix bug where a modified keyring loses + its modified status after a "clean" or "minimize" that doesn't + need to do anything. + +2008-09-25 Werner Koch + + * parse-packet.c (parse): Remove special treatment for compressed + new style packets. Fixes bug#931. + + * card-util.c (change_pin): Support setting of the reset code. + +2008-09-24 Werner Koch + + * call-agent.h (struct agent_card_info_s): Add field IS_V2. + * call-agent.c (learn_status_cb): That that field. + + * card-util.c (change_pin): Rename first arg to UNBLOCK_v2 and use + it this way. + (card_edit): Add new command UNBLOCK. + +2008-09-23 David Shaw + + * pkclist.c (select_algo_from_prefs): Redo function to rank prefs + and pick a consensus winner across all keys. + +2008-09-16 Werner Koch + + * card-util.c (fpr_is_ff): New. + (card_status): Do not print general key info for an all-ff fpr. + (change_login, change_private_do): Factor common code out to ... + (get_data_from_file): .. new. + (change_cert): New. + (card_edit): Add command "writecert". + * call-agent.c (writecert_parm_s): New. + (inq_writecert_parms, agent_scd_writecert): New. + +2008-09-04 David Shaw + + * keyserver.c (keyserver_import_cert): Allow keyserver URLs in + addition to full URLs in CERT records. + +2008-08-11 Werner Koch + + * keygen.c (ask_expire_interval): Check for time overflow of an + u32. Fixes bug #947. + +2008-08-01 Werner Koch + + * tdbio.c (open_db) [!EROFS]: Move closing parens out of the + ifdef. Reported by Ken Takusagawa. + +2008-06-25 Marcus Brinkmann + + * gpg.c (enum cmd_and_opt_values): Remove option + oEnableW32HandleTranslation. + (opts): Remove option --enable-w32-handle-translation. + (main): Remove variable w32_handle_translation. + +2008-06-19 Werner Koch + + * gpg.c (gpgconf_list): Add "group". + +2008-06-18 Marcus Brinkmann + + * gpg.c (enum cmd_and_opt_values): New option + oEnableW32HandleTranslation. + (opts): New option --enable-w32-handle-translation. + (main): New variable w32_handle_translation to keep track of + option. + +2008-06-16 Werner Koch + + * keygen.c (output_control_s): Add ASK_PASSPHRASE. + (read_parameter_file): Add commands %ask-passphrase and + %no-ask-passphrase. + +2008-06-11 Werner Koch + + * gpg.c: Make --fixed-list-mode a dummy. + * options.h (struct): Removed FIXED_LIST_MODE. + * keyid.c (colon_strtime, colon_datestr_from_pk) + (colon_datestr_from_sk, colon_datestr_from_sig) + (colon_expirestr_from_sig): Remove fixed_list_mode case. + * keylist.c (list_keyblock_colon): Ditto. Remove all now unsed + code and reindent. + +2008-05-31 Werner Koch + + * keygen.c (ask_user_id): Change the string printed as header of + the user ID generation. Use code to not break existing + translations. Suggested by Eric Tetz. + +2008-05-08 Werner Koch + + * sig-check.c (do_check_messages): Print a revocation diagnostic + in verbose mode. + +2008-05-07 Werner Koch + + * gpg.c: New command --locate-keys. New options --with-sig-list + and --with-sig-check. + * keylist.c (locate_one): New. + (public_key_list): Add arg LOCATE_MODE and use locate_one. + * getkey.c (get_pubkey_byname): Fix nodefault case. Add option + RETCTX, change all callers. + (struct getkey_ctx_s): Add field extra_ptr; + (get_pubkey_end): Free it. + +2008-04-18 Werner Koch + + * misc.c (map_cipher_openpgp_to_gcry, map_cipher_gcry_to_openpgp) + (openpgp_cipher_test_algo): Add camellia-192. + (openpgp_cipher_blocklen): New. + * parse-packet.c (parse_key): Use new function here. + +2008-04-15 David Shaw + + * getkey.c (merge_selfsigs_subkey): If there are multiple 0x19 + backsigs, take the most recent one. + +2008-04-08 Werner Koch + + * options.h (opt): Add AKL_NODEFAULT and AKL_LOCAL. + * getkey.c (parse_auto_key_locate): Parse them. + (get_pubkey_byname): Implement them. Add arg NO_AKL and use that + in all cases where a local key is expected. + * import.c (import_one): Fill in the fingerprint in all cases. + Use log_get_stream. + * keyserver.c (keyserver_import_pka): Set FPR to NULL on error. + Return G10ERR_NO_PUBKEY if no PKA info is available or no key URI + is given in the PKA record.. + (keyserver_import_cert): Return G10ERR_NO_PUBKEY if a CERT record + was not found. + + * getkey.c (get_pubkey_byname): Release FPR in the error case. + Continue with next mechanism on error. Better diagnostics. + +2008-04-07 Werner Koch + + * keyserver.c (parse_keyserver_uri): Allow a default host name. + + * getkey.c (get_pubkey_byname): Replace sprintf by bin2hex. + +2008-04-02 Werner Koch + + * gpg.c (main): Do not allow DSA2 with a too old Libgcrypt. + +2008-03-26 Werner Koch + + * tdbio.c (lookup_hashtable): Make cmp args const. + (cmp_trec_fpr): Make FPR const. + (tdbio_search_trust_byfpr): Remove cast. + +2008-03-25 Werner Koch + + * keyserver.c (parse_keyrec): Take care of char defaulting to + unsigned when using hextobyte. + +2008-03-25 David Shaw (wk) + + * import.c (collapse_uids): Fix bug 894: possible memory + corruption around deduplication of user IDs. + +2008-03-25 Werner Koch + + * parse-packet.c (parse_key): Parse a secret key encrypted with + Camellia. + + * options.skel: Make the default keyserver keys.gnupg.net. + +2008-03-18 Werner Koch + + * seckey-cert.c (do_check): Use GCRYMPI_FMT_PGP for v3 keys. + Reported by Petr Cerny. + +2008-03-13 Werner Koch + + * passphrase.c (PROMPTSTRING): Change string to me more similar to + the X.509 prompt. + +2008-02-26 Werner Koch + + * getkey.c (get_pubkey_byname): Fix comment. + +2008-02-14 Werner Koch + + * call-agent.c (default_inq_cb): New. + (agent_learn, agent_scd_getattr, agent_scd_pksign) + (agent_scd_pkdecrypt, agent_scd_change_pin, agent_scd_checkpin) + (agent_get_passphrase, agent_clear_passphrase): Use new callback. + (inq_writekey_parms): Fall back to the new callback for other + inquiries. + (start_agent): Tell agent that we accept pinentry notifications. + +2008-02-11 Werner Koch + + * server.c (cmd_getinfo): New. + (register_commands): Register GETINFO. + +2008-02-09 Marcus Brinkmann + + * gpg.c (main): New variable default_configname. Use it if + save_configname is NULL (can happen if default configfile does + not exist). Move default configname determination to ... + (get_default_configname): ... this new function. + +2008-01-30 Werner Koch + + * keydb.c (maybe_create_keyring): Fixed last change. + * tdbio.c (tdbio_set_dbname): Also test for forward slash. + +2008-01-29 Werner Koch + + * keydb.c (maybe_create_keyring): Take care of a missing slash. + (maybe_create_keyring) [W32]: Also test for forward slash. + +2008-01-26 Werner Koch + + * card-util.c (get_manufacturer): Add vendor 0004. + +2008-01-02 Werner Koch + + * gpg.c: Add --logger-file as an alias for log-file. + +2007-12-14 Werner Koch + + * gpg.c (main): Set opt.no_homedir_creation during the first option + parsing pass. + +2007-12-12 Werner Koch + + * misc.c (print_pubkey_algo_note): Print a warning if a type 20 + key is used. + (openpgp_pk_test_algo, openpgp_pk_test_algo2) + (openpgp_pk_algo_usage): Allow type 20 keys only in rfc2440 mode. + +2007-12-12 David Shaw (wk) + + * trustdb.c (sanitize_regexp): New. Protect against dangerous + regexps (malloc bombs) by force-commenting any characters aside + from the ones we explicitly want. + (check_regexp): Use it here before passing the regexp to + regcomp(). + +2007-12-12 Werner Koch + + * misc.c (map_cipher_openpgp_to_gcry): New. Used to map Camellia + algorithms to Gcrypt. + (openpgp_cipher_test_algo): Call new map function. Replace + all remaining calls to gcry_cipher_test_algo by a call to this. + (openpgp_cipher_algo_name): New. Replace all remaining calls to + gcry_cipher_algo_name by a call to this. + (map_cipher_gcry_to_openpgp): New. + (string_to_cipher_algo): Use it. + * gpg.c (main): Print a warning if Camellia support is build in. + + * gpg.c (print_algo_names): New. From the 1.4 branch by David. + (list_config): Use it here for the "ciphername" and "digestname" + config items so we can get a script-parseable list of the names. + + * parse-packet.c (parse_onepass_sig): Sigclass is hex, so include + the 0x. + + * sign.c (match_dsa_hash): Remove conditional builds dending on + USE_SHAxxx. We don't need this becuase it can be expected that + libgcrypt provides it. However we need to runtime test for SHA244 + becuase that is only available with libgcrypt 2.4. + +2007-12-11 Werner Koch + + * mainproc.c (proc_pubkey_enc): Allow type 20 Elgamal key for + decryption. + +2007-12-10 Werner Koch + + * import.c (auto_create_card_key_stub): Do not clear the entire + fingerprint. This finally makes the stub creation work. My past + tests seemed to work because there was a key with a all zero + fingerprint available (Elgamal signing keys). + +2007-12-08 Werner Koch + + * misc.c (openpgp_pk_algo_usage): Allow Elgamal type 20 for + encryption. + +2007-12-04 Werner Koch + + * helptext.c (get_help_from_file): New. + (display_online_help): Use it to geting the help through a file. + (helptexts): Remove. + +2007-12-03 Werner Koch + + * keygen.c (ask_key_flags): Add a translation remark and implement + a workaround. + + * gpg.c (reopen_std): Moved to ../common and renamed to + gnupg_reopen_std. + + * gpg.c: Remove second inclusion of fcntl.h. + +2007-11-19 Werner Koch + + * keyedit.c (keyedit_menu): String grammar fix. + +2007-11-15 Werner Koch + + * gpg.c (main): New option --xauthority. + * call-agent.c (start_agent): Adjust changed start_new_gpg_agent. + +2007-11-12 Werner Koch + + * cpr.c (do_get_from_fd): s/bool/getbool/ to overcome problems + with Mac OS 10.5 which seems to include stdbool.h silently. + +2007-11-07 Werner Koch + + Replace all includes of errors.h by status.h (found in common/). + + * status.h: Remove. + * status.h: Move prototypes to main.h. + * status.c: Rename to .. + * cpr.c: .. this. + (get_status_string): Remove. We take this now from common/. + +2007-10-25 David Shaw (wk) + + From 1.4 (October): + + * gpg.c (main): Add --require-cross-certification to + --openpgp/--rfc4880 mode. + + * gpg.c (main): Disable --rfc2440-text and --force-v3-sigs by + default. Enable --require-cross-certification by default. + --openpgp (--rfc4880) is the same as --rfc2440 except with + "--enable-dsa2 --no-rfc2440-text --escape-from-lines". + + * misc.c (compliance_option_string, compliance_failure): Minor + cleanup. + + * armor.c (is_armor_header): Comment about 4880. + + * options.h, gpg.c (main): Add --rfc4880, and make --openpgp an + alias to it. --rfc2440 now stands alone. For now, use the old + 2440 defaults for 4880. + * misc.c (compliance_option_string): Ditto. + + * keyedit.c (keyedit_menu): Use compliance_option_string() instead + of printing the compliance modes here. + +2007-10-25 David Shaw (wk) + + From 1.4 (September): + + * import.c (collapse_uids): Significant speedup for de-duping user + IDs. + +2007-10-25 David Shaw (wk) + + From 1.4 (July): + + * armor.c (parse_header_line): Improve test so that the header + test only allows "Hash" in the signed data section. + + * armor.c (is_armor_tag): New. Detect if an armor header matches + 2440bis-21. + (parse_header_line): Call it here, as bis-21 requires warning the + user (but continuing to process the message) when seeing an + unknown header. + + * encode.c (encode_crypt): Missed one call to + setup_plaintext_name(). This is bug#809. + + * sign.c (mk_notation_policy_etc): Expect all sigs that this is + called for are >=v4. + (write_signature_packets, make_keysig_packet): Only call it for + >=v4 sigs. This allows --force-v3-sigs and --force-v4-certs to + enable or disable notations, policies, and keyserver URLs. This + is bug#800. + +2007-10-19 Werner Koch + + * passphrase.c (passphrase_get): Use new utf8 switching fucntions. + +2007-09-14 Werner Koch + + * gpg.c (build_lib_list): New. + (my_strusage): Print lib info. + +2007-08-27 Werner Koch + + * trustdb.c (USE_INTERNAL_REGEX): Remove support. + +2007-08-24 Werner Koch + + * keyring.c (keyring_register_filename): Use same_file_p(). + +2007-08-21 Werner Koch + + * misc.c (openpgp_md_test_algo): Remove rfc2440bis hash algorithms. + (openpgp_cipher_test_algo): Likewise for algos 5 and 6. + +2007-08-02 Werner Koch + + * gpg.c: Include gc-opt-flags.h and remove their definition here. + +2007-07-17 Werner Koch + + * gpg.c (gpgconf_list): Declare --encrypt-to and --default-key. + + * card-util.c (get_manufacturer): Add the unmanaged S/N range. + +2007-07-12 Werner Koch + + * gpg.c (main): Use translate_sys2libc_fd_int when passing an int + value. + * gpgv.c (main): Ditto. + +2007-07-05 Werner Koch + + * card-util.c (card_generate_subkey, card_store_subkey): Enable + the code also for GnuPG-2. + + * keygen.c (make_backsig): Add arg TIMESTAMP. + (write_keybinding): Add arg TIMESTAMP, pass it to make_backsig. + (write_direct_sig, write_selfsigs): Add arg TIMESTAMP. + (gen_elg, gen_dsa, gen_rsa): Add arg TIMESTAMP. + (do_create): Ditto. + (do_generate_keypair): Use the same timestamp for key creation + time and all key signatures. Return an error if write_direct_sig + for the secret key fails. + (generate_subkeypair): Ditto. + (gen_card_key): New arg TIMESTAMP. + (generate_card_subkeypair): Pass current time to gen_card_key. + (gen_card_key_with_backup): New arg TIMESTAMP. + (read_parameter_file): Add option Creation-Date. + (parse_creation_string): New. + (do_generate_keypair): Use the Creation-Date if available. + (save_unprotected_key_to_card): Use P for P and not D. + * call-agent.c (agent_scd_genkey): Add arg CREATETIME. + * keyedit.c (menu_backsign): Use the same timestamp for all backsigs. + +2007-06-26 Werner Koch + + * openfile.c (try_make_homedir): Support W32; use standard_homedir. + +2007-06-25 Werner Koch + + * gpg.c, gpgv.c: Include sysutils.h. + (main): Replace iobuf_translate_file_handle by + translate_sys2libc_fd. + +2007-06-21 Werner Koch + + * main.h: Include util.h. + + * call-agent.c (start_agent): Factored almost all code out to + ../common/asshelp.c. + + * gpg.h (ctrl_t): Remove. It is now declared in ../common/util.h. + +2007-06-20 Werner Koch + + * misc.c (setsysinfo, trap_unaligned): Remove. It is also in + common/sysutils.c. + (disable_core_dumps, get_session_marker): + + * sign.c (sleep): Remove sleep wrapper. + +2007-06-18 Marcus Brinkmann + + * gpg.c (gpgconf_list): Percent escape output of --gpgconf-list. + +2007-06-14 Werner Koch + + * call-agent.c (start_agent): Use gnupg_module_name. + +2007-06-12 Werner Koch + + * openfile.c (copy_options_file): Use gnupg_datadir. + * misc.c (get_libexecdir): Remove. Changed all callers to use + gnupg_libexecdir. + * gpg.c (check_permissions): Use gnupg_libdir. + + * gpg.c (main): Replace some calls by init_common_subsystems. + * gpgv.c (main): Ditto. + +2007-06-11 Werner Koch + + * Makefile.am (needed_libs): Use libcommonstd macro. + + * gpgv.c (main) [W32]: Call pth_init. + * gpg.c (main) [W32]: Call pth_init. + +2007-06-08 Werner Koch + + * Makefile.am (gpg2_LDADD): Syntax fix. + +2007-06-06 Werner Koch + + * passphrase.c (passphrase_get) [!ENABLE_NLS]: Do not define + orig_codeset. + + * Makefile.am (gpgv2_LDADD, gpg2_LDADD): Include LDADD before + libgcrypt. + + * plaintext.c (handle_plaintext): Replace eof by eof_seen as W32's + io.h has a symbol with that name. + + * misc.c: Do not include dynload.h. + (w32_shgetfolderpath): Remove. It is now in common/homedir.c. + + * gpgv.c (i18n_init): Remove. + * gpg.c (i18n_init): Remove. + (main): Make --load-extension a dummy + +2007-05-19 Marcus Brinkmann + + * passphrase.c (passphrase_get): Use PACKAGE_GT, not PACKAGE. + + * passphrase.c (passphrase_get): Free ORIG_CODESET on error. + +2007-05-16 Werner Koch + + * sig-check.c (check_backsig): Check the digest algorithm before + using it. Fixed bug 797. + +2007-05-09 Werner Koch + + * openfile.c (overwrite_filep, open_outfile) [W32]: Need to use + just "nul". Though, I am pretty sure that some MSDOS versions + grok the extra /dev/. + +2007-05-07 Werner Koch + + * openfile.c (open_outfile, overwrite_filep) [W32]: Use "/dev/nul". + +2007-05-02 David Shaw + + * packet.h, mainproc.c (reset_literals_seen): New function to + reset the literals count. + + * verify.c (verify_one_file), decrypt.c (decrypt_messages): Call + it here so we allow multiple literals in --multifile mode (in + different files - not concatenated together). + +2007-04-26 Marcus Brinkmann + + * passphrase.c (passphrase_to_dek): Write missing passphrase + status message in case of cancellation. + +2007-04-16 Werner Koch + + * build-packet.c (mpi_write): Made buffer a bit larger. Reported + by Alexander Feigl. + +2007-04-13 Werner Koch + + * call-agent.c (start_agent): Don't use log_error when using the + fallback hack to start the agent. This is bug 782. + +2007-04-05 David Shaw + + From STABLE-BRANCH-1-4 + + * parse-packet.c (parse_marker): New. Enforce that the marker + contains 'P', 'G', 'P', and nothing but. + (parse): Call it here. + (skip_packet): No longer need to handle marker packets here. + +2007-03-14 David Shaw + + From STABLE-BRANCH-1-4 + + * keyserver.c: Windows Vista doesn't grok X_OK and so fails + access() tests. Previous versions interpreted X_OK as F_OK + anyway, so we'll just use F_OK directly. + +2007-03-09 David Shaw + + From STABLE-BRANCH-1-4 + + * parse-packet.c (parse_signature): It's hex. + + * getkey.c (merge_selfsigs_subkey): Avoid listing the contents of + a backsig when list mode is on. Noted by Timo Schulz. + +2007-03-08 Werner Koch + + * plaintext.c (handle_plaintext): Add two extra fflush for stdout. + +2007-03-08 David Shaw (wk) + + * keyedit.c (keyedit_menu): If we modify the keyblock (via + fix_keyblock() or collapse_uids()) make sure we reprocess the + keyblock so the flags are correct. Noted by Robin H. Johnson. + + * getkey.c (fixup_uidnode): Properly clear flags that don't apply + to us (revoked, expired) so that we can reprocess a uid. + +2007-03-05 Werner Koch + + Converted this file to UTF-8. + + Ported David and my multiple messages changes from 1.4.7. + + * options.h, gpg.c (main), mainproc.c (check_sig_and_print): Allow + multiple sig verification again as this is protected via the + multiple-messages code. New option --allow-multiple-messages and + --no variant. + * status.h (STATUS_ERROR): New status code. + * status.c (get_status_string): Ditto. + * mainproc.c (proc_plaintext): Emit it if multiple messages are + detected. Error out if more than one plaintext packet is + encountered. + * mainproc.c (literals_seen): New. + +2007-02-26 Werner Koch + + * gpg.c (main): Add verify option show-primary-uid-only. + * options.h (VERIFY_SHOW_PRIMARY_UID_ONLY): New. + * mainproc.c (check_sig_and_print): Implement it. + +2007-02-22 Werner Koch + + * encr-data.c (decrypt_data): Correctly test for unknown algorithm. + * import.c (check_prefs): Ditto. + * keyedit.c (show_prefs): Ditto. + * mainproc.c (proc_symkey_enc): Ditto. + +2007-02-06 Werner Koch + + * export.c (do_export_stream): Allow reset-subkey-passwd along + with sexp-format. + +2007-02-04 Werner Koch + + * parse-packet.c (parse_signature): Limit bytes read for an + unknown alogorithm. Fixes Debian bug#402592. + +2007-01-31 Werner Koch + + * verify.c (verify_signatures): Do no dereference a NULL afx. + + * passphrase.c (passphrase_get): Set the cancel flag on all error + from the agent. Fixes a bug reported by Tom Duerbusch. + +2007-01-30 Werner Koch + + * status.c (write_status_begin_signing): New. + * sign.c (sign_file, sign_symencrypt_file): Call it. + * textfilter.c (copy_clearsig_text): Call it. + + * call-agent.c (agent_scd_pksign): Pass --hash-rmd160 to SCD if + required. + + * gpg.c (main): Let --no-use-agent and --gpg-agent-info print a + warning. + * misc.c (obsolete_option): New. + +2007-01-29 Werner Koch + + * pkclist.c (do_we_trust_pre): Issue a user-id-hint status code. + +2007-01-15 Werner Koch + + * parse-packet.c (read_protected_v3_mpi): Make sure to stop + reading even for corrupted packets. + * keygen.c (generate_user_id): Need to allocate one byte more. + Reported by Felix von Leitner. + +2006-12-21 Werner Koch + + * gpg.c (main): New command --server. + * gpg.h (struct server_control_s, ctrl_t): New. + * server.c: New. + * verify.c (gpg_verify): New. + * mainproc.c (mainproc_context): Made SIGNED_DATA a structure. + (proc_signature_packets_by_fd): New. + (proc_compressed_cb): Divert depending on SIGNED_DATA. + * plaintext.c (hash_datafile_by_fd): New. + * mainproc.c (proc_tree): Use it here. + + * verify.c (verify_signatures): Init AFX only when needed. + Don't leak a context on error. + (verify_one_file): Don't leak a context on error. + +2006-12-07 Werner Koch + + * openfile.c (copy_options_file): Use log_info instead of + log_error to avoid an error return of gpg due to a missing + skeleton file. + +2006-12-07 David Shaw + + * Makefile.am: Link to iconv for jnlib dependency. + +2006-12-05 Werner Koch + + * passphrase.c (passphrase_to_dek): Handle a Cancel request + correctly. [Bug#737] + * mainproc.c (proc_symkey_enc): Removed workaround for bogus cancel + processing. + * encode.c (encode_simple): Distinguish error message between + cancel and invalid passphrase. + (setup_symkey): Ditto. + * sign.c (sign_symencrypt_file): Ditto + * keyedit.c (change_passphrase): Allow cancellation. + * keygen.c (do_ask_passphrase): New arg R_CANCELED. + (generate_keypair): Handle a passphrase cancellation. + (generate_raw_key): Ditto. + (generate_subkeypair): Ditto. + +2006-12-04 Werner Koch + + * filter.h (armor_filter_context_t): New element REFCOUNT. + * armor.c (armor_filter): Made static. + (push_armor_filter, release_armor_context, new_armor_context): New. + (armor_filter): Release the context. + * gpg.c (main): Use new armor context functions and + push_armor_filter. + * export.c (do_export): Ditto. + * encode.c (encode_simple, encode_crypt): Ditto. + * decrypt.c (decrypt_message, decrypt_messages): Ditto. + * dearmor.c (dearmor_file, enarmor_file): Ditto. + * verify.c (verify_signatures, verify_one_file): Ditto. + * sign.c (sign_file, clearsign_file, sign_symencrypt_file): Ditto. + * revoke.c (gen_desig_revoke, gen_revoke): Ditto. + * keyserver.c (keyserver_spawn): Ditto. + * keygen.c (output_control_s): Turn AFX fields into pointers. + (read_parameter_file): Allocate and release AFX fields. + (do_generate_keypair): Use push_armor_filter. + * import.c (import): Replace iobuf_push_filter2 hack by the new + armor context stuff. + +2006-12-03 Werner Koch + + * filter.h: New element REFCOUNT. + (handle_progress): Remove prototype. + * progress.c (new_progress_context, release_progress_context): New. + (progress_filter): Use new function to release context. Made static. + (handle_progress): Bumb reference counter. No more check for + enabled progress as this is handled by new_progress_context. + * verify.c (verify_signatures, verify_one_file): Replace stack + based progress context by a heap based one. + * sign.c (sign_file, clearsign_file, sign_symencrypt_file): Ditto. + * plaintext.c (ask_for_detached_datafile, hash_datafiles): Ditto. + * encode.c (encode_simple, encode_crypt): Ditto. + * decrypt.c (decrypt_message, decrypt_messages): Ditto. + + * keyedit.c (menu_clean): Made strings translatable. + +2006-12-03 David Shaw + + * 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 + + * 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-12-02 Werner Koch + + * encr-data.c: Allocate DFX context on the heap and not on the + stack. Changes at several places. Fixes CVE-2006-6235. + +2006-11-27 Werner Koch + + * openfile.c (ask_outfile_name): Fixed buffer overflow occurring + if make_printable_string returns a longer string. Fixes bug 728. + +2006-11-21 Werner Koch + + * Makefile.am (needed_libs): libgnu needs to come after libcommon. + + * keygen.c (ask_expire_interval): Print y2038 warning only for 32 + bit time_t. + (save_unprotected_key_to_card): Made RSA_N_LEN et al a size_t. + Cast printf args. + (get_parameter_algo): Allow "ELG" as alias for "ELG-E". + + * seckey-cert.c (do_check): Made NBYTES a size_t. + (do_check): Made NDATA a size_t. + (protect_secret_key): Made NARR a size_t. + (protect_secret_key): Made NVYES a size_t. + * pubkey-enc.c (get_it): Made INDATALEN a size_t. + (get_it): Made NFRAME a size_t. + * keyid.c (hash_public_key): Made NBITS an unsigned int. + * misc.c (checksum_mpi): Made NBYTES a size_t. + (openpgp_pk_test_algo2): Made USE_BUF a size_t. + * seskey.c (encode_session_key): Made NFRAME a size_t. + (do_encode_md): Ditto. + (encode_md_value): Cast size_t argument of printf. + (encode_md_value): Ditto. + +2006-11-10 Werner Koch + + * parse-packet.c (mpi_read): Changed NREAD to size_t to match the + gcry_mpi-scan prototype. + (mpi_read): Fixed double increment of bytes read to correctly + detect overlong MPIs. + +2006-11-05 Werner Koch + + * gpg.c (main): Remove the default --require-cross-certification. + * options.skel: Enable require-cross-certification. + +2006-10-31 Werner Koch + + * pkclist.c (warn_missing_aes_from_pklist): New. + * encode.c (encrypt_filter, encode_crypt): Use it here. + +2006-10-27 Werner Koch + + * pkclist.c (warn_missing_mdc_from_pklist): New. + * encode.c (use_mdc): Use it here. + +2006-10-24 Marcus Brinkmann + + * Makefile.am (AM_CFLAGS): Add $(LIBASSUAN_CFLAGS). + +2006-10-23 Werner Koch + + * gpg.c (main): New command --gpgconf-test. + + * Makefile.am (bzip2_source): New. + +2006-10-20 Werner Koch + + * getkey.c (classify_user_id): Reserve '&' for search by keygrip. + +2006-10-19 Werner Koch + + * keygen.c (get_parameter_algo): Add special case for ELG_E which + is not supported by libgcrypt's mapping function. + +2006-10-18 Werner Koch + + * keyid.c (v3_keyid): Don't use mempcy as we need to hold the + keyids in the native endian format. + + * import.c (import_print_stats): Use log_printf. + + * build-packet.c (do_public_key): Care about mpi_write errors. + (do_secret_key, do_pubkey_enc, do_signature): Ditto. + (mpi_write): Print an extra warning on error. + +2006-10-17 Werner Koch + + * Makefile.am (LDADD): Replaced W32LIBS by NETLIBS. + +2006-10-12 David Shaw + + * 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-08 Werner Koch + + * gpgv.c: Remove the tty stubs as we are now required to link to + tty anyway (it is included in libcommand and has dependencies to + other modules as well). + + * keyedit.c (keyedit_menu): Use keyedit_completion only if + readline is available. It would be better to move this code into + gpgrlhelp.c + +2006-10-06 Werner Koch + + * Makefile.am (AM_CFLAGS): Use PTH version of libassuan. + +2006-10-06 David Shaw + + * keyserver.c (keyserver_spawn): Write the 16-digit keyid rather + than whatever key selector the user used on the command line. + +2006-10-05 Werner Koch + + * status.c (progress_cb): Changed to libgcrypt API. + (set_status_fd): Register the progress cb. + + * seskey.c (encode_md_value): Check that the hash algo is valid + before getting the OID. + +2006-10-04 Werner Koch + + * passphrase.c: Allow for a static passphrase in batch mode. + + * call-agent.c (agent_havekey): Removed. + (percent_plus_escape): New. + (agent_get_passphrase): New. + (agent_clear_passphrase): New. + + * passphrase.c: Changed so that we always require the agent. + (agent_send_option, agent_send_all_options, agent_open): Removed. + (agent_get_passphrase): Cleaned up. Does now use the call-agent + functions. Renamed to + (passphrase_get): .. this. Changed all callers. + (passphrase_clear_cache): Rewritten. + (passphrase_to_dek, hash_passphrase): Re-indented. + + * gpg.c (main): Made --use-agent a dummy option. + * seckey-cert.c (check_secret_key): We require the agent, so always + allow for 3 tries. + + * gpg.c (main): Print a warning if -sat has been used. + (main): Removed the special treatment of the -k option. -k is now + an alias for --list-keys. + (main): Removed --list-ownertrust. + +2006-10-02 Werner Koch + + * encr-data.c (decrypt_data, mdc_decode_filter): Check the MDC + right here and don't let parse-packet handle the MDC. + +2006-09-29 Werner Koch + + * compress.c (do_uncompress): Removed use of Z_PARTIAL_FLUSH. + This is outdated and old zlib versions which still require it have + security problems. + +2006-09-27 Werner Koch + + Replaced all STRLIST by strlist_t. + +2006-09-21 Werner Koch + + * signal.c (got_fatal_signal): Replaced readline stuff by a tty + function. + + * Makefile.am (LDADD): Include libgpgrl.a. + + * gpg.c (main): Call gpg_rl_initialize. + + * keyedit.c: Removed double inclusion of stdio.h. + +2006-09-20 Werner Koch + + * call-agent.c: Include asshelp.h. + (start_agent): Use send_pinentry_environment. + +2006-09-14 Werner Koch + + Replaced all call gpg_error_from_errno(errno) by + gpg_error_from_syserror(). + +2006-09-13 Werner Koch + + * gpg.c (main): Made --require-cross-certification the default. + +2006-09-06 Marcus Brinkmann + + * Makefile.am (gpg2_LDADD, gpgv2_LDADD): Replace -lassuan and + -lgpg-error with $(LIBASSUAN_LIBS) and $(GPG_ERROR_LIBS). + (AM_CFLAGS): Add $(LIBASSUAN_CFLAGS) and $(GPG_ERROR_CFLAGS). + +2006-09-06 Werner Koch + + * gpg.c (main): Enable new assuan API. + * call-agent.c: Changed to new Assuan API. + +2006-09-01 Werner Koch + + * call-agent.c: Do not force using the pipe server. + + * gpg.c (main): Enable card related commands. + +2006-08-22 Werner Koch + + * mainproc.c (proc_plaintext): Fixed a #warning + +2006-08-21 Werner Koch + + * skclist.c (random_is_faked): Implemented. + (is_insecure): Also test for the old uppercase version of the + insecure string. + * gpg.c (main): Renamed --quick-random to debug-quick-quick-random. + + * gpg.c (print_mds): Do not use the USE_SHA macros. + + * mainproc.c (proc_encrypted): Remove assign inside condition for + better readibility. + + * packet.h: Moved consts to new header ../common/openpgpdefs.h. + +2006-08-16 Werner Koch + + * keyserver.c (GPGKEYS_PREFIX): Rename to gpg2keys_. This is so + that we can install helpers from 1.4 and 2 without conflicts and + first of all don't get lost with weird bug reports. + + * keyid.c (serialno_and_fpr_from_sk): New. Actually lost during + the last 1.4 to 1.9 merge. + + * gpg.c (list_config): Output ccid-reader-id only for gnupg 1. + + * call-agent.c (agent_scd_writekey): New. + (inq_writekey_parms): New. + + * gpgv.c: Include call-agent.h for use by stubs. + + * misc.c: Include call-agent.h for use by get_signature_count. + +2006-07-27 Werner Koch + + * parse-packet.c (parse_comment): Cap comments at 65k. + (parse_gpg_control): Skip too large control packets. + +2006-07-24 David Shaw (wk) + + * 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-30 Werner Koch + + * misc.c (checksum_mpi): No need for nbits as they are alredy + included in the buffer. + +2006-06-29 Werner Koch + + * parse-packet.c (parse_signature, parse_key): Need store the + length of opaque data as number of bits. + * card-util.c (card_store_subkey): Ditto. + + * mainproc.c (print_pkenc_list, check_sig_and_print): Replaced + log_get_stream by calls to log_printf. This avoids the extra LFs + inserted by the logging function. They are a bit too smart + sometimes. + * pkclist.c (do_show_revocation_reason): Print final LF through + log_printf to avoid extra LFs. + * pubkey-enc.c (get_it): Ditto. + + * seskey.c (encode_md_value): Fix call to gcry. + +2006-06-27 Werner Koch + + Applied patches from 1.4.x (2006-05-22 to 2006-06-23) from David: + + * 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. + * options.h, gpg.c (main): Ditto. + + * sign.c (do_sign): For now don't accept a truncated hash even + for DSA1 keys (be liberal in what you accept, etc). + + * 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. + + * 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. + + * 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. + + * exec.c (make_tempdir) [W32]: Fix bug with a temporary directory + on W32 that is over 256 bytes long. Noted by Israel G. Lugo. + + * 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. + + * parse-packet.c (dump_sig_subpkt, parse_signature): Fix meaning + of key expiration and sig expiration subpackets - zero means + "never expire" according to 2440, not "expire instantly". + * build-packet.c (build_sig_subpkt_from_sig): Ditto. + * getkey.c (fixup_uidnode, merge_selfsigs_main) + (merge_selfsigs_subkey): Ditto. + * keygen.c (keygen_add_key_expire): Ditto. + + * 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-05-24 Werner Koch + + * keyid.c (hash_public_key): Do not double hash the length bytes, + they are already included by mpi_print. + + * misc.c (openpgp_pk_test_algo2): Get test call right. + + * misc.c (string_to_cipher_algo, string_to_digest_algo): New. + * keygen.c (keygen_set_std_prefs): use them here. + * gpg.c (main): and here. + +2006-05-23 Werner Koch + + * card-util.c (generate_card_keys): Removed temporary kludge for + generate_keypair. + + * call-agent.c (agent_scd_setattr): Add arg SERIALNO. + (agent_scd_genkey): Ditto. + (agent_scd_change_pin): Ditto. + + * call-agent.h (struct agent_card_info_s): Updated to match the + one of 1.4.3. + + * Makefile.am (LDADD): Include ZLIBS. + + * gpgv.c: Removed stubs not anymore useful due to libgcrypt. + +2006-05-22 Werner Koch + + * keyserver.c (keyidlist): Replaced mpi_get_keyid by v3_keyid. + * keydb.h (v3_keyid): Added. + + * import.c (import): Better initialize KEYBLOCK as to quiet + compiler warning. + + * skclist.c (random_is_faked): New. + + * mainproc.c: Include pka.h. + +2006-05-19 Werner Koch + + * misc.c (openpgp_pk_test_algo2): Need to use gcry_pk_algo_info + directly. + (string_count_chr): New. + + * armor.c (parse_header_line): Use renamed function + length_sans_trailing_ws. + + * options.h, gpg.c: Option --strict is not used thus removed code + but kept option. + +2006-04-28 David Shaw (wk) + + * 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. + + * keyserver.c: Fix build problem with platforms that stick libcurl + in a place not in the regular include search path. + + * 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. + + * gpg.c (print_mds): Add SHA-224. + * armor.c (armor_filter, parse_hash_header): Add SHA-224. + + * sign.c (write_plaintext_packet): + Factor common literal packet setup code from here, to... + * encode.c (encode_simple): .. there. + + * 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. + + * keyedit.c (menu_backsign): Give some more verbose errors when we + have no need to backsign. + + * 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-18 Werner Koch + + * tdbio.c (open_db, migrate_from_v2): Removed feature to migration + from old trustdb version 2. + + * gpg.c, mainproc.c: Removed pipemode feature. + + * status.c: Removed shared memory coprocess stuff + + Merged with current gpg 1.4.3 code. + + * keygen.c, keyid.c, misc.c, openfile.c, verify.c, trustdb.c + * textfilter.c, tdbio.c, tdbdump.c, status.c, skclist.c, signal.c + * sign.c, sig-check.c, seskey.c, seckey-cert.c, revoke.c + * pubkey-enc.c, progress.c, plaintext.c, pkclist.c, photoid.c + * passphrase.c, parse-packet.c, mdfilter.c, mainproc.c + * keyserver.c, keyring.c, keylist.c, keyedit.c, keydb.c, kbnode.c + * import.c, getkey.c, gpgv.c, helptext.c, free-packet.c + * build-packet.c, cipher.c, compress.c, dearmor.c, decrypt.c + * delkey.c, encr-data.c, encode.c, exec.c, export.c + * gpg.c, armor.c: Updated from gnupg-1.4.3 and merged back gcry and + gnupg-1.9 related changes. + * trustdb.h, tdbio.h, status.h, photoid.h, packet.h, options.h + * main.h, keyserver-internal.h, keyring.h, keydb.h, filter.h + * exec.h: Ditto. + * global.h: Removed after merging constants with gpg.h. + * comment.c, pipemode.c: Removed. + * card-util.c: Updated from gnupg-1.4.3. + * compress-bz2.c: New. + +2005-06-15 Werner Koch + + * g10.c (print_hashline, add_group): Fixes for signed/unsigned + pointer mismatch warnings. + +2005-06-01 Werner Koch + + * mkdtemp.c: Removed. + * exec.c: Include mkdtemp.h + +2004-12-21 Werner Koch + + * gpgv.c, g10.c (main): Use default_hoemdir (). + +2004-12-18 Werner Koch + + * gpg.h (map_assuan_err): Define in terms of + map_assuan_err_with_source. + +2004-12-15 Werner Koch + + * Makefile.am (LDADD): Remove ZLIBS. + +2004-10-22 Werner Koch + + * g10.c (main): Display a bit fat warning that this gpg should not + be used. + + * card-util.c (fetch_url): Disable for gnupg 1.9 + (card_generate_subkey): Ditto. + (card_store_subkey): Ditto. + +2004-09-30 Werner Koch + + * gpgv.c (i18n_init): Always use LC_ALL. + + * Makefile.am (LDADD): Adjusted for gettext 0.14. + +2004-09-20 Werner Koch + + * keyedit.c (show_key_with_all_names): Print the card S/N. + +2004-09-11 Moritz Schulte + + * openfile.c (copy_options_file): Fixed last commit (added a `+'). + +2004-08-31 Werner Koch + + * openfile.c (copy_options_file): Use gpg-conf.skel. Better take + the length of SKELEXT into account, someone might make it larger. + * Makefile.am: Install options.skel as gpg-conf.skel. + +2004-08-18 Marcus Brinkmann + + * passphrase.c (agent_get_passphrase): Fix detection of gpg-agent + cancellation. + +2004-07-01 Werner Koch + + * 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. + + * call-agent.h: Add members for CA fingerprints. + * call-agent.c (agent_release_card_info): Invalid them. + (learn_status_cb): Store them. + +2004-04-30 Werner Koch + + * g10.c (main) : Use gpg.conf and not /dev/null as + default filename. + +2004-04-28 Werner Koch + + * card-util.c (card_edit): Remove PIN verification. + (generate_card_keys): New arg SERIALNO. Do PIN verification here + after resetting forced_chv1. + +2004-04-26 Werner Koch + + * card-util.c (change_name): Check that the NAME is not too long. + (change_url): Likewise. + (change_login): Likewise. + +2004-03-23 Werner Koch + + * g10.c: New options --gpgconf-list, --debug-level and --log-file + (set_debug): Add arg DEBUG_LEVEL. + (main): Look at less and less version specific config files. From + gnupg 1.3. + +2004-02-17 Werner Koch + + * call-agent.c (start_agent): Ignore an empty GPG_AGENT_INFO. + * passphrase.c (agent_open): Ditto. + +2004-02-12 Werner Koch + + * gpgv.c: Removed g10defs.h. + + * Makefile.am: Include cmacros.am for common flags. + +2004-02-11 Werner Koch + + * openfile.c (try_make_homedir): Use GNUPG_DEFAULT_HOMEDIR. + * gpgv.c (main): Ditto. + * g10.c (main): Ditto. + +2004-01-19 Moritz Schulte + + * keygen.c (do_generate_keypair): Print member fname, instead of + newfname, again. + (do_generate_keypair): Don't try to execute certain pieces of code + in case an error occured. + (gen_card_key): Don't print out a message, which is already + printed by do_generate_keypair(). + +2004-01-18 Moritz Schulte + + * keygen.c (do_generate_keypair): Print member fname, instead of + newfname. + +2003-12-17 Werner Koch + + * card-util.c (print_name): Fixed bad format string usage. + (print_isoname): Ditto. + + * trustdb.c (check_regexp): s/exp/expr/. + + * keyedit.c (trustsig_prompt): Removed a "> 255" term; it is + always false due to the data type. + + * passphrase.c (agent_get_passphrase): Use xasprintf and avoid + non-literal format strings. + + * tdbio.c (upd_hashtable, drop_from_hashtable, lookup_hashtable): + Fixed log_error format string bugs. Kudos to the now working + gcc-3.3 -Wformat-nonliteral and Florian Weimer's investigations in + gnupg 1.2.3. + +2003-12-15 Werner Koch + + * seckey-cert.c (protect_secret_key): Use gry_create_nonce for the + IV; there is not need for real strong random here and it even + better protect the random bits used for the key. + +2003-11-16 Moritz Schulte + + * signal.c: Removed unused file. + +2003-11-10 Moritz Schulte + + * Makefile.am (INCLUDES): Added: @LIBGCRYPT_CFLAGS@. + +2003-10-25 Werner Koch + + * call-agent.c (learn_status_cb, scd_genkey_cb): Fixed faulty use + of !spacep(). + +2003-10-20 Werner Koch + + * 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. + * call-agent.c (agent_scd_checkpin): New. + +2003-10-08 Werner Koch + + * call-agent.c (agent_scd_getattr): Don't clear the passed info + structure, so that it can indeed be updated. + + * card-util.c (fpr_is_zero): New. + (generate_card_keys): New. + (card_edit): New command "generate". + * keygen.c (generate_keypair): New arg CARD_SERIALNO, removed call + to check_smartcard. + (check_smartcard,show_smartcard): Removed. + (show_sha1_fpr,fpr_is_zero): Removed. + +2003-10-01 Werner Koch + + * card-util.c: Tweaked to use this source also under 1.3. + +2003-09-30 Werner Koch + + * keylist.c (print_card_serialno): New. + (list_keyblock_print): Use it here. + + * card-util.c (toggle_forcesig): New. + (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. + + * call-agent.c (agent_scd_getattr): New. + (learn_status_cb): Release values before assignment so that it can + be used by getattr to update the structure. + + * card-util.c (change_pin): Simplified. We now have only a PIN + and an Admin PIN. + +2003-09-27 Werner Koch + + * sign.c (do_sign): Removed disabled testcode. + +2003-09-26 Timo Schulz + + * card_status (card_status): Do not use fputs since the fp + parameter can be NULL. This fixes a segv. + +2003-09-24 Werner Koch + + * card-util.c (print_isoname,card_status): Handle opt.with_colons. + (print_sha1_fpr_colon): New. + +2003-09-23 Werner Koch + + Merged most of David Shaw's changes in 1.3 since 2003-06-03. + + * Makefile.am: Include W32LIBS where appropriate. + + * armor.c (parse_hash_header,armor_filter): Drop TIGER/192 support. + * g10.c (print_hex,print_mds): Ditto. + * pkclist.c (algo_available): Ditto. + + * armor.c (armor_filter): Allow using --comment multiple times to + get multiple Comment header lines. --no-comments resets list. + * options.h, g10.c (main): Ditto. Deprecate --default-comment in + favor of --no-comments. + + * g10.c (main): Trim --help to commonly used options. Remove -f. + + * g10.c (main): Add --multifile as an alias to turn --encrypt into + --encrypt-files (plus --verify-files, --decrypt-files). Error out + if --multifile is used with the commands that don't support it yet. + + * 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. + + * g10.c (main): Use "keyserver-url" instead of + "preferred-keyserver" for the sake of short and simple commands. + (add_keyserver_url): Clarify a few strings. It's a + "preferred keyserver URL". + * keyedit.c (keyedit_menu): Ditto. + * sign.c (mk_notation_policy_etc): Ditto. + + * 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. + + * 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. + + * 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. + + * 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. + + * 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. + + * 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. + + * 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). + + * 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). + + * 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. + + * 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. + + * keyedit.c (show_key_with_all_names): Use list-option + show-validity in --edit-key interface as well. + + * 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. + + * 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). + + * 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. + + * 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 + + * 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. + + * parse-packet.c (parse): Disallow old style partial length for + all key material packets to avoid possible corruption of keyrings. + + * import.c (import_keys_internal): Invalidate the cache so that + the file descriptor gets closed. Fixes bug reported by Juan + F. Codagnone. + + * 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. + + * 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. + + * keyedit.c (show_key_with_all_names): Fix assertion failure when + using toggle to see a secret key. Reported by Maxim Britov. + + +2003-09-22 Timo Schulz + + * card-util.c (card_status): Free pk in case of an error + and return if the card is no OpenPGP card. + +2003-09-18 Werner Koch + + * g10.c: New command --card-edit. + * card-util.c (card_status): Use tty_fprintf for all output. + (print_sha1_fpr, print_isoname): Ditto. + (get_one_name,change_name, change_url, change_login,change_lang) + (change_sex): New; taken from keygen.c. + * keygen.c (smartcard_get_one_name, smartcard_change_name) + (smartcard_change_url, smartcard_change_login_data) + (smartcard_change_lang, smartcard_change_sex): Removed. + (check_smartcard): Removed most menu items. + +2003-09-06 Werner Koch + + * misc.c (openpgp_pk_algo_usage): Allow AUTH where SIGN is allowed. + + * keygen.c (ask_passphrase): No need to allocated S2K in secure + memory. + +2003-09-04 Werner Koch + + * keygen.c (do_add_key_flags, parse_parameter_usage) + (do_generate_keypair): 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. + +2003-08-25 Timo Schulz + + * pkglue.c (mpi_from_sexp): New. Used to factor out + some common code. + +2003-08-24 Werner Koch + + * keygen.c (do_generate_keypair): Print a reminder to use --gen-revoke. + +2003-08-18 Timo Schulz + + * encode.c (encode_sesskey): Checked the code and removed + the warning since all compatibility checks with PGP succeeded. + * mainproc.c (symkey_decrypt_sesskey): Better check for the + algorithm and check the return values of some functions. + * mdc.c (use_mdc): Simplified. + +2003-08-07 Werner Koch + + * pkglue.c (pk_sign): Fix last change. + (pk_verify): Check for valid DATA array so that we don't segv in + Libgcrypt. + (pk_verify): Ditto. + +2003-08-06 Werner Koch + + * pkglue.c (pk_sign): Allow signing using RSA. + +2003-08-05 Werner Koch + + * Makefile.am (install-data-local): Dropped check for the ancient + gpgm tool. + (bin_PROGRAMS): Renamed gpg to gpg2 and gpgv to gpgv2. This is so + that it won't conflict with the current stable version of gpg. + + * pkglue.c (pk_check_secret_key): New. + * seckey-cert.c (do_check): Reenable this test here again. + + * g10.c (main): Add command -K as an alias for + --list-secret-keys. Command "-k" is now an alias to --list-keys. + Remove special treatment of -kv and -kvv. + (set_cmd): Ditto. + (main): Strip a "-cvs" suffix when testing for a version specific + config file. + + * status.h, status.c, g10.c [USE_SHM_COPROCESSING]: Removed. This + is not any longer available. + +2003-07-29 Werner Koch + + * g10.c (main): Add secmem features and set the random seed file. + (g10_exit): Update the random seed file. + + * parse-packet.c (parse_signature,read_protected_v3_mpi) + (parse_key): Fixed use of mpi_set_opaque. + * keygen.c (gen_card_key): Ditto. + +2003-07-28 Werner Koch + + * status.c (progress_cb): Adjusted for use with Libcgrypt. + (set_status_fd): Register that callback. + + * keygen.c (smartcard_change_login_data): New. + (smartcard_change_lang): New. + (smartcard_change_sex): New. + (check_smartcard): Add menu entries to edit the above. + (gen_elg,gen_dsa,gen_rsa): Reimplemented in terms of Libgcrypt. + (genhelp_protect, genhelp_factors, key_from_sexp): New. + * comment.c (make_comment_node_from_buffer): New. + (make_comment_node): Reimplemented in terms of above. + +2003-07-27 Werner Koch + + Adjusted for gcry_mpi_print and gcry_mpi_scan API change. + +2003-07-24 Werner Koch + + * g10.c: New command --card-status. + * card-util.c (card_status): New. + * call-agent.c (learn_status_cb): Parse more information. + + * keylist.c (print_pubkey_info): Add FP arg for optional printing + to a stream. Changed all callers. + +2003-07-23 Werner Koch + + * keygen.c (generate_keypair): Create an AUTHKEYTYPE entry for cards. + (do_generate_keypair): Abd generate the authkey. + (check_smartcard): Changed menu accordingly. + +2003-07-22 Werner Koch + + * g10.c: New command --change-pin. + * card-util.c: New. + * call-agent.c (agent_scd_change_pin): New. + (agent_release_card_info): New. + * keygen.c (check_smartcard): Use it here. + +2003-07-16 Werner Koch + + * export.c (parse_export_options): New option sexp-format. + (export_seckeys,export_secsubkeys): Check sexp-format option. + (do_export): Ignore armor for sexp format. + (do_export_stream): Handle sexp-format. + (write_sexp_line,write_sexp_keyparm, build_sexp_seckey): New. + (build_sexp): New. + +2003-07-03 Werner Koch + + * options.h (DBG_CIPHER): Reintroduced it. + * seskey.c (encode_session_key): Debug output of the session key. + + * pubkey-enc.c (get_it): Handle card case. + * call-agent.c (agent_scd_pkdecrypt): New. + * pkglue.c (pk_encrypt): Add RSA support. + + * g10.c (main): Default to --use-agent. + + * keygen.c (show_smartcard): Print info about the public key. + (check_smartcard): Check for existing key here. + (gen_card_key): And not anymore here. + (fpr_is_zero): New. + (generate_keypair): Generate both keys for a card. + (smartcard_change_url): Nw. + +2003-07-02 Werner Koch + + * seckey-cert.c (is_secret_key_protected): Let it handle mode 1002. + +2003-07-01 Werner Koch + + * keygen.c (gen_card_key): Obviously we should use the creation + date received from SCDAEMON, so that the fingerprints will match. + * sign.c (do_sign): Pass the serialno to the sign code. + * keyid.c (serialno_and_fpr_from_sk): New. + +2003-06-30 Werner Koch + + * call-agent.h (agent_card_info_s): Add field serialno. + * call-agent.c (store_serialno): New. + (learn_status_cb): Store the serial number. + * keygen.c (gen_card_key): Store the serial number + (check_smartcard): New argument to return the serial number. + (generate_keypair): Get the serial number from check_smartcard and + store it as a parameter. + * parse-packet.c (parse_key): Use the protect.iv field to store the + serial number. + * build-packet.c (do_secret_key): Write the serial number. + +2003-06-27 Werner Koch + + * seckey-cert.c (check_secret_key): Bypass the unprotection for + mode 1002. + * sign.c (do_sign): Handle card case (i.e. mode 1002). + +2003-06-26 Werner Koch + + * build-packet.c (do_secret_key): Implement special protection + mode 1002. + * parse-packet.c (parse_key): Likewise. + + * keygen.c (smartcard_gen_key): New. + * call-agent.c (agent_scd_setattr): New. + +2003-06-24 Werner Koch + + * Makefile.am: Removed signal.c + + * g10.c (emergency_cleanup): New. + (main): Use gnupg_init_signals and register malloc for assuan. + +2003-06-23 Werner Koch + + * keyid.c (do_fingerprint_md): Made it work again. + +2003-06-19 Werner Koch + + Fixed all "==" comparisons against error code constants to use + gpg_err_code(). + + * import.c (import_secret_one): + (import_revoke_cert): + (chk_self_sigs): + + * misc.c (openpgp_md_map_name): Check also for the Hx format. + (openpgp_cipher_map_name): Check also for the Sx format. + (pubkey_get_npkey): Adjusted for changed gcrypt API. + (pubkey_get_nskey): Ditto. + (pubkey_get_nsig): Ditto. + (pubkey_get_nenc): Ditto. + +2003-06-18 Werner Koch + + Finished the bulk of changes for gnupg 1.9. This included + switching to libgcrypt functions, using shared error codes from + libgpg-error, replacing the old functions we used to have in + ../util by those in ../jnlib and ../common, renaming the malloc + functions and a couple of types. Note, that not all changes are + listed below becuause they are too similar and done at far too + many places. As of today the code builds using the current + libgcrypt from CVS but it is very unlikely that it actually works. + + * sig-check.c (cmp_help): Removed. Was never used. + + * pkglue.c: New. Most stuff taken from gnupg 1.1.2. + * pkglue.h: New. + + * misc.c (pull_in_libs): Removed. + + * keygen.c (count_chr): New. + (ask_user_id): Removed faked RNG support. + + * misc.c (openpgp_md_map_name,openpgp_cipher_map_name) + (openpgp_pk_map_name): New. + + * skclist.c (build_sk_list): Removed faked RNG support. + (is_insecure): Removed. + + * comment.c (make_mpi_comment_node): Use gcry MPI print function. + + * keyid.c (v3_keyid): New. + + * misc.c (mpi_write,mpi_write_opaque,mpi_read,mpi_read_opaque) + (mpi_print): New. Taken from gnupg 1.1.2. + (checksum_mpi): Replaced by implementation from 1.1.2. + + * g10.c (my_strusage): Renamed from strusage and return NULL + instead calling a default function. + (add_to_strlist2): New. Taken from ../util/strgutil.c of gnupg 1.2. + + * plaintext.c (handle_plaintext): New arg CREATE_FILE to cope with + the fact that gpg-error does not have this error code anymore. + + * mainproc.c (symkey_decrypt_sesskey): Ditto. + + * seskey.c (make_session_key): Adjusted for use with libgcrypt. + (encode_session_key): Ditto. + (do_encode_md): Ditto. + (encode_md_value): Ditto. + + * keyring.c: Use libgpg-error instead of READ_ERROR etc. + + * g10.c: Adjusted all algorithm name/id mapping functions. + (set_debug): Pass MPI and CRYPTO debug values to libgcrypt. + + * Makefile.am (INCLUDES): Define LOCALEDIR and the default error + source. + + * g10.c (i18n_init): s/G10_LOCALEDIR/LOCALEDIR/. + + Renamed m_alloc et al to xmalloc et al. + s/g10_errstr/gpg_strerror/ + s/MPI/gcry_mpi_t/ + Adjusted all md_open calls to the libgcrypt API. + + * build-packet.c (do_comment): Return error code from iobuf write + function. + (do_user_id): Ditto. + (do_public_key): Ditto. + + * Makefile.am: Add new files, link gpg with libgpg-error. + * g10.c, options.h: New option --agent-program. + * call-agent.c: New. + * gpg.h, call-agent.h: New. + +2003-06-03 David Shaw + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * mainproc.c (check_sig_and_print): Add primary key fpr to VALIDSIG + status. + +2003-03-24 David Shaw + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * tdbio.c (tdbio_set_dbname): Fix assertion failure with + non-fully-qualified trustdb names. + +2003-01-11 David Shaw + + * 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 + + * 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 + + * keygen.c (keygen_set_std_prefs): Warn when setting an IDEA + preference when IDEA is not available. + +2003-01-06 David Shaw + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * import.c (print_import_check): Changed int to size_t. + +2002-12-27 David Shaw + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * options.h: Fixed typo (mangle_dos_names instead of + mangle_dos_filenames). + +2002-12-05 Werner Koch + + * 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 + + * 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 + + * 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 + + * 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 + + * plaintext.c (handle_plaintext) [__riscos__]: If nooutput is set, + no filetype is needed obviously. + +2002-11-24 David Shaw + + * 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 + + * 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 + + * 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 + + * 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 + + * getkey.c (get_pubkey_byfprint_fast): Fixed type incompatibility, + was unsigned char instead of byte. + +2002-11-13 David Shaw + + * 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 + + * passphrase.c (agent_send_all_options): Use tty_get_ttyname to + get the default ttyname. + +2002-11-07 David Shaw + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * trustdb.c (check_regexp): Modern regexps require REG_EXTENDED. + +2002-10-29 David Shaw + + * 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 + + * 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 + + * 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 + + * 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 + + * pubkey-enc.c (get_it): Fix segv, test for revoked only when PK + has been assigned. + +2002-10-18 Timo Schulz + + * 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 + + * 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 + + * g10.c (main): Handle --strict and --no-strict from the command + line before the options file is loaded. + +2002-10-15 David Shaw + + * 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 + + * 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 + + * keygen.c (print_status_key_created): New. + (do_generate_keypair): Use it to print the fingerprint. + (generate_subkeypair): Likewise. + +2002-10-11 David Shaw + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * import.c (import_one): Make sure that a newly imported key + starts with a clean ownertrust. + +2002-10-01 Werner Koch + + * 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 + + * keyserver.c (parse_keyserver_uri): Force the keyserver URI + scheme to lowercase to be case-insensitive. + +2002-09-28 David Shaw + + * export.c (do_export_stream): Comment. + + * sig-check.c (check_key_signature2): Properly handle a + non-designated revocation import. + +2002-09-26 Werner Koch + + * g10.c (set_homedir): New. Changed all direct assignments to use + this. + * gpgv.c (set_homedir): Ditto. + +2002-09-25 David Shaw + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * g10.c (main) [__MINGW32__]: Activate oLoadExtension. + +2002-09-12 David Shaw + + * 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 + + * exec.c (expand_args): Remove loop left over from earlier + implementation. + (exec_write): Missed one tick. + +2002-09-10 Werner Koch + + * 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) : 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * hkp.c (parse_hkp_index): Properly handle really large keys + (5 digit key length) in HKP searches. + +2002-08-13 David Shaw + + * 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 + + * 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 + + * 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 + + * g10.c (main): Use of EXTSEP_S in new gpg.conf string. + * openfile.c (copy_options_file): Ditto. + +2002-08-06 David Shaw + + * 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 + + * 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 + + * Makefile.am (LDFLAGS): Removed DYNLINK_LDFLAGS. + +2002-07-30 David Shaw + + * 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 + + * sig-check.c (do_check): Properly validate v4 sigs with no hashed + section at all. + +2002-07-25 Werner Koch + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 ' ' 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 + + * 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 + + * 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 + + * skclist.c (is_insecure): Implemented. + +2002-06-12 David Shaw + + * 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 + + * Makefile.am: link gpg with NETLIBS for the built-in HKP access. + +2002-06-10 David Shaw + + * 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 + + * 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 + + * encode.c (encode_symmetric): Disable the compat flag + when the expert mode is enabled. + +2002-06-07 David Shaw + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * mainproc.c (symkey_decrypt_sesskey): New. + (proc_symkey_enc): Support for encrypted session keys. + +2002-06-04 David Shaw + + * 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 + + * 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 + + * 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 + + * 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 + + * photoid.c (show_photos): Work properly when not called with a + public key. + +2002-05-31 David Shaw + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * g10.c (strusage, build_list): Add compress algorithms to + --version list. Show algorithm numbers when --verbose --version + is done. + +2002-05-22 David Shaw + + * 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 + + * g10.c (main): Add missing LF in a info printout and made it + translatable. Noted by Michael Tokarev. + +2002-05-21 Werner Koch + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * exec.c (make_tempdir) [MINGW32]: Added missing '\'. + +2002-05-14 Stefan Bellon + + * exec.c (make_tempdir): Make use of EXTSEP_S instead of hardcoded + dot as extension separator. + +2002-05-13 David Shaw + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * pkclist.c (select_algo_from_prefs): There is an assumed + compression preference for uncompressed data. + +2002-05-07 David Shaw + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * mainproc.c (proc_symkey_enc): Don't ask for a passphrase + in the list only mode. + +2002-05-05 David Shaw + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * options.skel: Removed the comment on trusted-keys because this + option is now deprecated. + +2002-05-01 David Shaw + + * 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 + + Added a copyright year for files changed this year. + +2002-04-25 Werner Koch + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + guarantee 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 + + * Makefile.am (EXTRA_DIST): Removed OPTIONS an pubring.asc - they + are no longer of any use. + +2002-04-03 David Shaw + + * 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 + + * 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 + + * keyserver.c (printunquoted): unquote backslashes from keyserver + searches + + * hkp.c (write_quoted): quote backslashes from keyserver searches + +2002-03-26 Werner Koch + + * keygen.c (ask_keysize): Removed the warning for key sizes > 1536. + +2002-03-25 Werner Koch + + * 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 + + * 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 + + * 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 + + * 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 + + * main.h: Changed the default algorithms to CAST5 and SHA1. + +2002-03-13 David Shaw + + * 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 + + * 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 + + * 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 + + * parse-packet.c (parse_signature): variable type tweak for RISC + OS (from Stefan) + +2002-02-28 David Shaw + + * 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 + + * 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 + + * 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 + + * 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 + + * parse-packet.c (parse_trust): Made parsing more robust. + +2002-02-19 David Shaw + + * 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 + + * 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 + + * hkp.c (parse_hkp_index): Bug fix - properly handle user IDs with + colons (":") in them while HKP searching. + +2002-02-09 David Shaw + + * 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 + + * 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 + + * 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 + + * keyserver.c (keyserver_spawn): Bug fix - do not append keys to + each other when --sending more than one. + +2002-02-02 David Shaw + + * 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 + + * g10.c (main): --gen-random --armor does now output a base64 + encoded string. + +2002-01-28 David Shaw + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * exec.c: Include windows.h for dosish systems. + +2002-01-08 Timo Schulz + + * g10.c (main): New description for --encrypt-files. + +2002-01-08 Werner Koch + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * encode.c (encode_simple, encode_crypt): i18n 2 strings. + +2001-12-22 Timo Schulz + + * encode.c (encode_simple, encode_crypt): Use is_file_compressed + to avoid to compress compressed files. + +2001-12-22 Werner Koch + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * g10.c, passphrase.c [CYGWIN32]: Allow this as an alias for MINGW32. + +2001-12-18 David Shaw + + * 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 + + * 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 + + * keyserver.c (keyserver_spawn): Assert that we have dropped privs. + +2001-12-13 Werner Koch + + * 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 + + * Fixed some types for portability. Noted by Stefan Bellon. + +2001-12-11 Werner Koch + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * g10.c: Add advisory --for-your-eyes-only option as per section + 5.9 of 2440. + +2001-12-05 David Shaw + + * Force a V4 sig if the user has a notation or policy URL set. + +2001-12-04 David Shaw + + * 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 + + * 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 + + * trustdb.c (mark_usable_uid_certs): Fix segfault from bad + initialization and fix reversed key signature expiration check. + +2001-11-09 Werner Koch + + * 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 + + * 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 + + * g10.c (main): Print a warning when -r is used w/o encryption. + Suggested by Pascal Scheffers. + +2001-10-23 Werner Koch + + * 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 + + * 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 + + * 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 + + * keyring.c (keyring_search): Enabled word search mode but print a + warning that it is buggy. + +2001-10-11 Werner Koch + + * 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 + + * passphrase.c [__riscos__]: Disabled agent specific stuff. + * g10.c: New option --no-force-v3-sigs. + +2001-10-04 Werner Koch + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 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. + * passphrase.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 + + * 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 + + 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * cipher.c (write_header): Don't use MDC with --rfc1991. Suggested + by disastry@saiknes.lv. + +2001-07-05 Werner Koch + + * 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 + + * ringedit.c (add_keyblock_resource): Restore filename in case of error. + +2001-06-25 Werner Koch + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * g10.c (opts): Typo fix by Robert C. Ames. + +2001-05-06 Werner Koch + + * revoke.c: Small typo fix + +2001-05-04 Werner Koch + + * passphrase.c (passphrase_clear_cache): Shortcut if agent usage + is not enabled. + +2001-05-01 Werner Koch + + * 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 + + * getkey.c (merge_public_with_secret): pkttype was not set to subkey. + +2001-04-27 Werner Koch + + * skclist.c (build_sk_list): Changed one log_debug to log_info. + +2001-04-25 Werner Koch + + * 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 + + * 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 + + * 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 + + * options.skel: Add some more comments. + +2001-04-19 Werner Koch + + * 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 + + * 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 + + * getkey.c (fixup_uidnode): Removed check for !sig which is + pointless here. Thanks to Jan Niehusmann. + +2001-04-10 Werner Koch + + * 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 + + * 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 because this does only check 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 + + 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 + + * getkey.c (premerge_public_with_secret): Changed wording an add + the keyID to the info message. + +2001-03-29 Werner Koch + + * 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 + + * 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 + + * 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 + + * 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 + + * sign.c (do_sign): Verify the signature right after creation. + +2001-03-23 Werner Koch + + * status.c, status.h (STATUS_UNEXPECTED): New. + * mainproc.c (do_proc_packets): And emit it here. + +2001-03-21 Werner Koch + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * getkey.c (finish_lookup): Print an info message only in verbose mode. + +2001-03-05 Werner Koch + + * 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 + + * 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 + + * keylist.c (list_one): Remove the merge key calls. + +2001-03-01 Werner Koch + + * 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 + + * delkey.c (delete_key): Removed that silly assert which rendered + the whole new stuff meaningless. + +2001-02-08 Werner Koch + + * 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 + + * encr-data.c (decrypt_data): Catch error when we had problems to + parse the encrypted packet. By Timo. + +2001-01-29 Werner Koch + + * g10.c (main): --batch does now set nogreeting. + + * delkey.c (do_delete_key): Fixed delete-both functionality. + +2001-01-22 Werner Koch + + * 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 + + * 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 + + * sig-check.c (do_check): Print the signature key expire message + only in verbose mode and added the keyID. + +2001-01-09 Werner Koch + + * 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 + + * g10.c, options.h: New option --no-expensive-trust-checks. + * keylist.c (list_keyblock): Act on this option. + +2001-01-04 Werner Koch + + * g10.c (main): Set homedir only in the pre-parsing phase and + replace backslashes in the W32 version. + +2001-01-03 Werner Koch + + * status.c, status.h : New status KEY_CREATED + * keygen.c (do_generate_keypair,generate_subkeypair): Emit it. + +2000-12-28 Werner Koch + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * import.c (merge_blocks): Increment n_sigs for revocations. + +2000-11-30 Werner Koch + + * 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 + + * passphrase.c [HAVE_DOSISH_SYSTEM]: Disabled all the agent stuff. + +2000-11-16 Werner Koch + + * 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 + + * 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 + + * sign.c (clearsign_file): Use LF macro to print linefeed. + +2000-11-11 Paul Eggert + + 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 + + * g10.c (main): New option --enable-special-filenames. + +2000-11-07 Werner Koch + + * g10.c (main): New command --pipemode. + * pipemode.c: New. + +2000-10-23 Werner Koch + + * 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 + + * 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 + + * misc.c (print_cipher_algo_note): Don't print the note for AES. + Changed wording. + +2000-10-16 Werner Koch + + * 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 + + * 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 + + * 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 + + * keygen.c (keygen_add_std_prefs): Add Rijndael to the prefs. + +2000-10-07 Werner Koch + + * gpgv.c: Add more stubs for ununsed code to make the binary smaller. + +Wed Oct 4 15:50:18 CEST 2000 Werner Koch + + * 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 + + * hkp.c (not_implemented): Print a notice for W32 + +Fri Sep 15 18:40:36 CEST 2000 Werner Koch + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * keyedit.c (menu_expire): expire date for primary key can be set again. + +Wed Jul 19 11:26:43 CEST 2000 Werner Koch + + * 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 + + * helptext.c, pkclist.c: s/superseeded/superseded/ + +Mon Jul 10 16:08:57 CEST 2000 Werner Koch + + * 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 + + * 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 + + * 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 + + * 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 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 + + * 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 + + * sig-check.c (do_check): Use EMULATE_MDENCODE also on v4 packets. + +Wed Jun 7 17:25:38 CEST 2000 Werner Koch + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * keyedit.c (show_key_with_all_names): s/sbb/ssb/ + +Tue Mar 28 14:26:58 CEST 2000 Werner Koch + + * 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 + + * 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 + + * 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 + + * 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 + + * helptext.c: typo fix. + +Thu Feb 17 13:39:32 CET 2000 Werner Koch + + * 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 + + * 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 + + * 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 + + * 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 + + * keyedit.c (menu_expire): Fixed segv due to unitialized sub_pk. + By Rémi. + +Thu Feb 10 11:39:41 CET 2000 Werner Koch + + * 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 + + * 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 + + * 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 + + * armor.c (is_armored): Check for 1-pass-sig packets. Reported by + David Hallinan . + (armor_filter): Replaced one LF by the LF macro. Reported by + Wolfgang Redtenbacher. + +Wed Jan 5 11:51:17 CET 2000 Werner Koch + + * 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 + + * 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 + + * 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 + + * mainproc.c (print_failed_pkenc): Fix for unknown algorithm. + Found by fygrave@epr0.org. + +Thu Dec 9 10:31:05 CET 1999 Werner Koch + + * hkp.c: i18n the strings. + +Sat Dec 4 15:32:20 CET 1999 Werner Koch + + * trustdb.c (verify_key): Shortcut for ultimately trusted keys. + +Sat Dec 4 12:30:28 CET 1999 Werner Koch + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + + * verify.c (verify_files, ferify_one_file): New. + * g10.c: New command --verify-files + +Fri Sep 17 12:56:42 CEST 1999 Werner Koch + + * 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 + + * g10.c: New option --entropy-dll-name + +Mon Sep 13 10:51:29 CEST 1999 Werner Koch + + * 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 + + * tdbio.c (tdbio_set_dbname): Use mkdir macro + * ringedit.c (add_keyblock_resource): Ditto. + +Fri Sep 3 10:04:45 CEST 1999 Werner Koch + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + + * 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 + + + * 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 + + * g10.c: New options --disable-{cipher,pubkey}-algo. + +Wed Jul 14 19:42:08 CEST 1999 Werner Koch + + * 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 + + * 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 + + * 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 + + * 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 + + + * 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 + + * 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 + + * 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 + + + * 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 + + + * 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 + + + * 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 + + * 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 + + * g10.c: New options --openpgp, --no-tty, --emit-version, + --default-comment and --lock-multiple + +Thu Jun 10 14:18:23 CEST 1999 Werner Koch + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * keyedit.c (keyedit_menu): Add new command revkey. + * (menu_revkey): New. + + +Mon Apr 26 17:48:15 CEST 1999 Werner Koch + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * armor.c (fake_packet): Fix for not not-dash-escaped + +Sat Mar 20 11:44:21 CET 1999 Werner Koch + + * g10.c (main): Added command --recv-keys + * hkp.c (hkp_import): New. + +Wed Mar 17 13:09:03 CET 1999 Werner Koch + + * 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 + + * keygen.c (do_create): Changed wording of the note (Hugh Daniel) + +Thu Mar 11 16:39:46 CET 1999 Werner Koch + + * 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 + + * keylist.c (list_keyblock): Fixed segv in uid. Print 'u' as + validity of sks. + +Mon Mar 8 20:47:17 CET 1999 Werner Koch + + * getkey.c (classify_user_id): Add new mode 12 (#). + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * getkey.c (lookup_sk): Return G10ERR_NO_SECKEY and not x_PUBKEY. + +Fri Feb 19 15:49:15 CET 1999 Werner Koch + + * 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 + + * 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 + + * tdbio.c (tdbio_set_dbname): Init lockhandle for a new trustdb + +Wed Feb 10 17:15:39 CET 1999 Werner Koch + + * 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 + + * 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 + + * textfilter.c: Mostly rewritten + * plaintext.c (handle_plaintext): Use now text_filter semantics. + +Tue Jan 19 19:34:58 CET 1999 Werner Koch + + * 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 + + * 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 + + * hkp.c (hkp_ask_import): Use only the short keyid + +Sat Jan 16 09:27:30 CET 1999 Werner Koch + + * 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 + + * armor.c (radix64_read): Print an error if a bad armor was detected. + +Wed Jan 13 12:49:36 CET 1999 Werner Koch + + * armor.c (radix64_read): Now handles malformed armors produced + by some buggy MUAs. + +Tue Jan 12 11:17:18 CET 1999 Werner Koch + + * 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 + + * armor.c (fake_packet): Fixed not-dash-escaped bug. + +Sat Jan 9 16:02:23 CET 1999 Werner Koch + + * 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 + + * 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 + + * 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 + + * 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 + + * armor.c (find_header): Reset not_dashed at every header + +Wed Dec 23 13:18:14 CET 1998 Werner Koch + + * 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 + + * 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 + + * 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 + + * pcklist.c (select_algo_from_prefs): Preferences should + now work (lost the != ? ) + +Thu Dec 10 20:15:36 CET 1998 Werner Koch + + * 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 + + * 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 + + * 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 + + * 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 + + * armor.c (find_header): Another fix for clearsigs. + +Fri Nov 27 12:39:29 CET 1998 Werner Koch + + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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_passphrase_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,2007,2008,2009,2010,2011 Free Software Foundation, Inc. + + This file is free software; as a special exception the author gives + unlimited permission to copy and/or distribute it, with or without + modifications, as long as this notice is preserved. + + This file is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY, to the extent permitted by law; without even the + implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +Local Variables: +buffer-read-only: t +End: diff --git a/g10/Makefile.am b/g10/Makefile.am new file mode 100644 index 0000000..f885673 --- /dev/null +++ b/g10/Makefile.am @@ -0,0 +1,267 @@ +# Copyright (C) 1998, 1999, 2000, 2001, 2002, +# 2003, 2006, 2010 Free Software Foundation, Inc. +# +# This file is part of GnuPG. +# +# GnuPG is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# GnuPG is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see . + +## Process this file with automake to produce Makefile.in + +EXTRA_DIST = distsigkey.gpg \ + ChangeLog-2011 \ + gpg-w32info.rc gpg.w32-manifest.in \ + gpgv-w32info.rc gpgv.w32-manifest.in \ + test.c t-keydb-keyring.kbx \ + t-keydb-get-keyblock.gpg t-stutter-data.asc \ + all-tests.scm + +AM_CPPFLAGS = + +include $(top_srcdir)/am/cmacros.am + +AM_CFLAGS = $(LIBGCRYPT_CFLAGS) $(SQLITE3_CFLAGS) \ + $(LIBASSUAN_CFLAGS) $(GPG_ERROR_CFLAGS) + +needed_libs = ../kbx/libkeybox.a $(libcommon) ../regexp/libregexp.a + +# Because there are no program specific transform macros we need to +# work around that to allow installing gpg as gpg2. +gpg2_hack_list = gpg gpgv +if USE_GPG2_HACK +gpg2_hack_uninst = gpg2 gpgv2 +use_gpg2_hack = yes +else +gpg2_hack_uninst = $(gpg2_hack_list) +use_gpg2_hack = no +endif + +# NB: We use noinst_ for gpg and gpgv so that we can install them with +# the install-hook target under the name gpg2/gpgv2. +noinst_PROGRAMS = gpg +if !HAVE_W32CE_SYSTEM +noinst_PROGRAMS += gpgv +endif +if MAINTAINER_MODE +noinst_PROGRAMS += gpgcompose +endif +noinst_PROGRAMS += $(module_tests) +if DISABLE_TESTS +TESTS = +else +TESTS = $(module_tests) +endif +TESTS_ENVIRONMENT = \ + abs_top_srcdir=$(abs_top_srcdir) + +if ENABLE_BZIP2_SUPPORT +bzip2_source = compress-bz2.c +else +bzip2_source = +endif + +if ENABLE_CARD_SUPPORT +card_source = card-util.c +else +card_source = +endif + +if NO_TRUST_MODELS +trust_source = +else +trust_source = trustdb.c trustdb.h tdbdump.c tdbio.c tdbio.h +endif + +if USE_TOFU +tofu_source = tofu.h tofu.c gpgsql.c gpgsql.h +else +tofu_source = +endif + + +if HAVE_W32_SYSTEM +gpg_robjs = $(resource_objs) gpg-w32info.o +gpgv_robjs = $(resource_objs) gpgv-w32info.o +gpg-w32info.o : gpg.w32-manifest +gpgv-w32info.o : gpgv.w32-manifest +else +gpg_robjs = +gpgv_robjs = +endif + +common_source = \ + gpg.h \ + dek.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 \ + rmd160.c rmd160.h \ + options.h \ + openfile.c \ + keyid.c \ + packet.h \ + parse-packet.c \ + cpr.c \ + plaintext.c \ + sig-check.c \ + keylist.c \ + pkglue.c pkglue.h \ + ecdh.c + +gpg_sources = server.c \ + $(common_source) \ + pkclist.c \ + skclist.c \ + pubkey-enc.c \ + passphrase.c \ + decrypt.c \ + decrypt-data.c \ + cipher.c \ + encrypt.c \ + sign.c \ + verify.c \ + revoke.c \ + dearmor.c \ + import.c \ + export.c \ + migrate.c \ + delkey.c \ + keygen.c \ + helptext.c \ + keyserver.c \ + keyserver-internal.h \ + call-dirmngr.c call-dirmngr.h \ + photoid.c photoid.h \ + call-agent.c call-agent.h \ + trust.c $(trust_source) $(tofu_source) \ + $(card_source) \ + exec.c exec.h \ + key-clean.c key-clean.h \ + key-check.c key-check.h + +gpg_SOURCES = gpg.c \ + keyedit.c keyedit.h \ + $(gpg_sources) + +gpgcompose_SOURCES = gpgcompose.c $(gpg_sources) +gpgv_SOURCES = gpgv.c \ + $(common_source) \ + verify.c + +#gpgd_SOURCES = gpgd.c \ +# ks-proto.h \ +# ks-proto.c \ +# ks-db.c \ +# ks-db.h \ +# $(common_source) + +LDADD = $(needed_libs) ../common/libgpgrl.a \ + $(ZLIBS) $(LIBINTL) $(CAPLIBS) $(NETLIBS) +gpg_LDADD = $(LDADD) $(LIBGCRYPT_LIBS) $(SQLITE3_LIBS) $(LIBREADLINE) \ + $(LIBASSUAN_LIBS) $(GPG_ERROR_LIBS) \ + $(LIBICONV) $(gpg_robjs) $(extra_sys_libs) +gpg_LDFLAGS = $(extra_bin_ldflags) +gpgv_LDADD = $(LDADD) $(LIBGCRYPT_LIBS) \ + $(GPG_ERROR_LIBS) \ + $(LIBICONV) $(gpgv_robjs) $(extra_sys_libs) +gpgv_LDFLAGS = $(extra_bin_ldflags) + +gpgcompose_LDADD = $(LDADD) $(SQLITE3_LIBS) $(LIBGCRYPT_LIBS) $(LIBREADLINE) \ + $(LIBASSUAN_LIBS) $(GPG_ERROR_LIBS) \ + $(LIBICONV) $(extra_sys_libs) +gpgcompose_LDFLAGS = $(extra_bin_ldflags) + +t_common_ldadd = +module_tests = t-rmd160 t-keydb t-keydb-get-keyblock t-stutter +t_rmd160_SOURCES = t-rmd160.c rmd160.c +t_rmd160_LDADD = $(t_common_ldadd) +t_keydb_SOURCES = t-keydb.c test-stubs.c $(common_source) +t_keydb_LDADD = $(LDADD) $(LIBGCRYPT_LIBS) $(GPG_ERROR_LIBS) \ + $(LIBICONV) $(t_common_ldadd) +t_keydb_get_keyblock_SOURCES = t-keydb-get-keyblock.c test-stubs.c \ + $(common_source) +t_keydb_get_keyblock_LDADD = $(LDADD) $(LIBGCRYPT_LIBS) $(GPG_ERROR_LIBS) \ + $(LIBICONV) $(t_common_ldadd) +t_stutter_SOURCES = t-stutter.c test-stubs.c \ + $(common_source) +t_stutter_LDADD = $(LDADD) $(LIBGCRYPT_LIBS) $(GPG_ERROR_LIBS) \ + $(LIBICONV) $(t_common_ldadd) + + +$(PROGRAMS): $(needed_libs) ../common/libgpgrl.a + +# NB: To install gpg and gpgv we use this -hook. This code has to +# duplicate most of the automake generated install-binPROGRAMS target +# so that directories are created and the transform feature works. +install-exec-hook: + @echo "running install-exec-hook"; \ + echo " $(MKDIR_P) '$(DESTDIR)$(bindir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(bindir)"; \ + for p in $(gpg2_hack_list); do \ + echo "$$p$(EXEEXT) $$p$(EXEEXT)"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + for f in $$files; do \ + if test $(use_gpg2_hack) = yes ; \ + then f2=`echo "$${f}" | sed 's/$(EXEEXT)$$//'`2$(EXEEXT); \ + else f2="$${f}" ;\ + fi ; \ + echo "$(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) \ + $${f} '$(DESTDIR)$(bindir)/$${f2}'"; \ + $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) \ + $${f} "$(DESTDIR)$(bindir)/$${f2}"; \ + done; \ + done + + +install-data-local: + $(mkinstalldirs) $(DESTDIR)$(pkgdatadir) + $(INSTALL_DATA) $(srcdir)/distsigkey.gpg \ + $(DESTDIR)$(pkgdatadir)/distsigkey.gpg + +# NB: For uninstalling gpg and gpgv we use -local because there is +# no need for a specific order the targets need to be run. +uninstall-local: + -@rm $(DESTDIR)$(pkgdatadir)/distsigkey.gpg + -@files=`for p in $(gpg2_hack_uninst); do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + echo " ( cd '$(DESTDIR)$(bindir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(bindir)" && rm -f $$files diff --git a/g10/Makefile.in b/g10/Makefile.in new file mode 100644 index 0000000..1774106 --- /dev/null +++ b/g10/Makefile.in @@ -0,0 +1,1453 @@ +# Makefile.in generated by automake 1.16.3 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2020 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + +# Copyright (C) 1998, 1999, 2000, 2001, 2002, +# 2003, 2006, 2010 Free Software Foundation, Inc. +# +# This file is part of GnuPG. +# +# GnuPG is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# GnuPG is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see . + +# cmacros.am - C macro definitions +# Copyright (C) 2004 Free Software Foundation, Inc. +# +# This file is part of GnuPG. +# +# GnuPG is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# GnuPG is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see . + +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +@HAVE_DOSISH_SYSTEM_FALSE@am__append_1 = -DGNUPG_BINDIR="\"$(bindir)\"" \ +@HAVE_DOSISH_SYSTEM_FALSE@ -DGNUPG_LIBEXECDIR="\"$(libexecdir)\"" \ +@HAVE_DOSISH_SYSTEM_FALSE@ -DGNUPG_LIBDIR="\"$(libdir)/@PACKAGE@\"" \ +@HAVE_DOSISH_SYSTEM_FALSE@ -DGNUPG_DATADIR="\"$(datadir)/@PACKAGE@\"" \ +@HAVE_DOSISH_SYSTEM_FALSE@ -DGNUPG_SYSCONFDIR="\"$(sysconfdir)/@PACKAGE@\"" \ +@HAVE_DOSISH_SYSTEM_FALSE@ -DGNUPG_LOCALSTATEDIR="\"$(localstatedir)\"" + + +# If a specific protect tool program has been defined, pass its name +# to cc. Note that these macros should not be used directly but via +# the gnupg_module_name function. +@GNUPG_AGENT_PGM_TRUE@am__append_2 = -DGNUPG_DEFAULT_AGENT="\"@GNUPG_AGENT_PGM@\"" +@GNUPG_PINENTRY_PGM_TRUE@am__append_3 = -DGNUPG_DEFAULT_PINENTRY="\"@GNUPG_PINENTRY_PGM@\"" +@GNUPG_SCDAEMON_PGM_TRUE@am__append_4 = -DGNUPG_DEFAULT_SCDAEMON="\"@GNUPG_SCDAEMON_PGM@\"" +@GNUPG_DIRMNGR_PGM_TRUE@am__append_5 = -DGNUPG_DEFAULT_DIRMNGR="\"@GNUPG_DIRMNGR_PGM@\"" +@GNUPG_PROTECT_TOOL_PGM_TRUE@am__append_6 = -DGNUPG_DEFAULT_PROTECT_TOOL="\"@GNUPG_PROTECT_TOOL_PGM@\"" +@GNUPG_DIRMNGR_LDAP_PGM_TRUE@am__append_7 = -DGNUPG_DEFAULT_DIRMNGR_LDAP="\"@GNUPG_DIRMNGR_LDAP_PGM@\"" +noinst_PROGRAMS = gpg$(EXEEXT) $(am__EXEEXT_1) $(am__EXEEXT_2) \ + $(am__EXEEXT_3) +@HAVE_W32CE_SYSTEM_FALSE@am__append_8 = gpgv +@MAINTAINER_MODE_TRUE@am__append_9 = gpgcompose +@DISABLE_TESTS_FALSE@TESTS = $(am__EXEEXT_3) +subdir = g10 +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/autobuild.m4 \ + $(top_srcdir)/m4/codeset.m4 $(top_srcdir)/m4/gettext.m4 \ + $(top_srcdir)/m4/gpg-error.m4 $(top_srcdir)/m4/iconv.m4 \ + $(top_srcdir)/m4/isc-posix.m4 $(top_srcdir)/m4/ksba.m4 \ + $(top_srcdir)/m4/lcmessage.m4 $(top_srcdir)/m4/ldap.m4 \ + $(top_srcdir)/m4/lib-ld.m4 $(top_srcdir)/m4/lib-link.m4 \ + $(top_srcdir)/m4/lib-prefix.m4 $(top_srcdir)/m4/libassuan.m4 \ + $(top_srcdir)/m4/libgcrypt.m4 $(top_srcdir)/m4/nls.m4 \ + $(top_srcdir)/m4/npth.m4 $(top_srcdir)/m4/ntbtls.m4 \ + $(top_srcdir)/m4/pkg.m4 $(top_srcdir)/m4/po.m4 \ + $(top_srcdir)/m4/progtest.m4 $(top_srcdir)/m4/readline.m4 \ + $(top_srcdir)/m4/socklen.m4 $(top_srcdir)/m4/sys_socket_h.m4 \ + $(top_srcdir)/m4/tar-ustar.m4 $(top_srcdir)/acinclude.m4 \ + $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON) +mkinstalldirs = $(SHELL) $(top_srcdir)/build-aux/mkinstalldirs +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = gpg.w32-manifest gpgv.w32-manifest +CONFIG_CLEAN_VPATH_FILES = +@HAVE_W32CE_SYSTEM_FALSE@am__EXEEXT_1 = gpgv$(EXEEXT) +@MAINTAINER_MODE_TRUE@am__EXEEXT_2 = gpgcompose$(EXEEXT) +am__EXEEXT_3 = t-rmd160$(EXEEXT) t-keydb$(EXEEXT) \ + t-keydb-get-keyblock$(EXEEXT) t-stutter$(EXEEXT) +PROGRAMS = $(noinst_PROGRAMS) +am__gpg_SOURCES_DIST = gpg.c keyedit.c keyedit.h server.c gpg.h dek.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 rmd160.c rmd160.h options.h \ + openfile.c keyid.c packet.h parse-packet.c cpr.c plaintext.c \ + sig-check.c keylist.c pkglue.c pkglue.h ecdh.c pkclist.c \ + skclist.c pubkey-enc.c passphrase.c decrypt.c decrypt-data.c \ + cipher.c encrypt.c sign.c verify.c revoke.c dearmor.c import.c \ + export.c migrate.c delkey.c keygen.c helptext.c keyserver.c \ + keyserver-internal.h call-dirmngr.c call-dirmngr.h photoid.c \ + photoid.h call-agent.c call-agent.h trust.c trustdb.c \ + trustdb.h tdbdump.c tdbio.c tdbio.h tofu.h tofu.c gpgsql.c \ + gpgsql.h card-util.c exec.c exec.h key-clean.c key-clean.h \ + key-check.c key-check.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) rmd160.$(OBJEXT) openfile.$(OBJEXT) \ + keyid.$(OBJEXT) parse-packet.$(OBJEXT) cpr.$(OBJEXT) \ + plaintext.$(OBJEXT) sig-check.$(OBJEXT) keylist.$(OBJEXT) \ + pkglue.$(OBJEXT) ecdh.$(OBJEXT) +@NO_TRUST_MODELS_FALSE@am__objects_3 = trustdb.$(OBJEXT) \ +@NO_TRUST_MODELS_FALSE@ tdbdump.$(OBJEXT) tdbio.$(OBJEXT) +@USE_TOFU_TRUE@am__objects_4 = tofu.$(OBJEXT) gpgsql.$(OBJEXT) +@ENABLE_CARD_SUPPORT_TRUE@am__objects_5 = card-util.$(OBJEXT) +am__objects_6 = server.$(OBJEXT) $(am__objects_2) pkclist.$(OBJEXT) \ + skclist.$(OBJEXT) pubkey-enc.$(OBJEXT) passphrase.$(OBJEXT) \ + decrypt.$(OBJEXT) decrypt-data.$(OBJEXT) cipher.$(OBJEXT) \ + encrypt.$(OBJEXT) sign.$(OBJEXT) verify.$(OBJEXT) \ + revoke.$(OBJEXT) dearmor.$(OBJEXT) import.$(OBJEXT) \ + export.$(OBJEXT) migrate.$(OBJEXT) delkey.$(OBJEXT) \ + keygen.$(OBJEXT) helptext.$(OBJEXT) keyserver.$(OBJEXT) \ + call-dirmngr.$(OBJEXT) photoid.$(OBJEXT) call-agent.$(OBJEXT) \ + trust.$(OBJEXT) $(am__objects_3) $(am__objects_4) \ + $(am__objects_5) exec.$(OBJEXT) key-clean.$(OBJEXT) \ + key-check.$(OBJEXT) +am_gpg_OBJECTS = gpg.$(OBJEXT) keyedit.$(OBJEXT) $(am__objects_6) +gpg_OBJECTS = $(am_gpg_OBJECTS) +am__DEPENDENCIES_1 = +am__DEPENDENCIES_2 = $(needed_libs) ../common/libgpgrl.a \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) +@HAVE_W32_SYSTEM_TRUE@am__DEPENDENCIES_3 = $(am__DEPENDENCIES_1) \ +@HAVE_W32_SYSTEM_TRUE@ gpg-w32info.o +gpg_DEPENDENCIES = $(am__DEPENDENCIES_2) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_3) \ + $(am__DEPENDENCIES_1) +gpg_LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(gpg_LDFLAGS) $(LDFLAGS) -o \ + $@ +am__gpgcompose_SOURCES_DIST = gpgcompose.c server.c gpg.h dek.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 rmd160.c rmd160.h options.h \ + openfile.c keyid.c packet.h parse-packet.c cpr.c plaintext.c \ + sig-check.c keylist.c pkglue.c pkglue.h ecdh.c pkclist.c \ + skclist.c pubkey-enc.c passphrase.c decrypt.c decrypt-data.c \ + cipher.c encrypt.c sign.c verify.c revoke.c dearmor.c import.c \ + export.c migrate.c delkey.c keygen.c helptext.c keyserver.c \ + keyserver-internal.h call-dirmngr.c call-dirmngr.h photoid.c \ + photoid.h call-agent.c call-agent.h trust.c trustdb.c \ + trustdb.h tdbdump.c tdbio.c tdbio.h tofu.h tofu.c gpgsql.c \ + gpgsql.h card-util.c exec.c exec.h key-clean.c key-clean.h \ + key-check.c key-check.h +am_gpgcompose_OBJECTS = gpgcompose.$(OBJEXT) $(am__objects_6) +gpgcompose_OBJECTS = $(am_gpgcompose_OBJECTS) +gpgcompose_DEPENDENCIES = $(am__DEPENDENCIES_2) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) +gpgcompose_LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(gpgcompose_LDFLAGS) \ + $(LDFLAGS) -o $@ +am__gpgv_SOURCES_DIST = gpgv.c gpg.h dek.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 rmd160.c \ + rmd160.h options.h openfile.c keyid.c packet.h parse-packet.c \ + cpr.c plaintext.c sig-check.c keylist.c pkglue.c pkglue.h \ + ecdh.c verify.c +am_gpgv_OBJECTS = gpgv.$(OBJEXT) $(am__objects_2) verify.$(OBJEXT) +gpgv_OBJECTS = $(am_gpgv_OBJECTS) +@HAVE_W32_SYSTEM_TRUE@am__DEPENDENCIES_4 = $(am__DEPENDENCIES_1) \ +@HAVE_W32_SYSTEM_TRUE@ gpgv-w32info.o +gpgv_DEPENDENCIES = $(am__DEPENDENCIES_2) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_4) $(am__DEPENDENCIES_1) +gpgv_LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(gpgv_LDFLAGS) $(LDFLAGS) \ + -o $@ +am__t_keydb_SOURCES_DIST = t-keydb.c test-stubs.c gpg.h dek.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 rmd160.c rmd160.h options.h \ + openfile.c keyid.c packet.h parse-packet.c cpr.c plaintext.c \ + sig-check.c keylist.c pkglue.c pkglue.h ecdh.c +am_t_keydb_OBJECTS = t-keydb.$(OBJEXT) test-stubs.$(OBJEXT) \ + $(am__objects_2) +t_keydb_OBJECTS = $(am_t_keydb_OBJECTS) +t_keydb_DEPENDENCIES = $(am__DEPENDENCIES_2) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) +am__t_keydb_get_keyblock_SOURCES_DIST = t-keydb-get-keyblock.c \ + test-stubs.c gpg.h dek.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 rmd160.c \ + rmd160.h options.h openfile.c keyid.c packet.h parse-packet.c \ + cpr.c plaintext.c sig-check.c keylist.c pkglue.c pkglue.h \ + ecdh.c +am_t_keydb_get_keyblock_OBJECTS = t-keydb-get-keyblock.$(OBJEXT) \ + test-stubs.$(OBJEXT) $(am__objects_2) +t_keydb_get_keyblock_OBJECTS = $(am_t_keydb_get_keyblock_OBJECTS) +t_keydb_get_keyblock_DEPENDENCIES = $(am__DEPENDENCIES_2) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) +am_t_rmd160_OBJECTS = t-rmd160.$(OBJEXT) rmd160.$(OBJEXT) +t_rmd160_OBJECTS = $(am_t_rmd160_OBJECTS) +t_rmd160_DEPENDENCIES = $(am__DEPENDENCIES_1) +am__t_stutter_SOURCES_DIST = t-stutter.c test-stubs.c gpg.h dek.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 rmd160.c rmd160.h options.h \ + openfile.c keyid.c packet.h parse-packet.c cpr.c plaintext.c \ + sig-check.c keylist.c pkglue.c pkglue.h ecdh.c +am_t_stutter_OBJECTS = t-stutter.$(OBJEXT) test-stubs.$(OBJEXT) \ + $(am__objects_2) +t_stutter_OBJECTS = $(am_t_stutter_OBJECTS) +t_stutter_DEPENDENCIES = $(am__DEPENDENCIES_2) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/build-aux/depcomp +am__maybe_remake_depfiles = depfiles +am__depfiles_remade = ./$(DEPDIR)/armor.Po ./$(DEPDIR)/build-packet.Po \ + ./$(DEPDIR)/call-agent.Po ./$(DEPDIR)/call-dirmngr.Po \ + ./$(DEPDIR)/card-util.Po ./$(DEPDIR)/cipher.Po \ + ./$(DEPDIR)/compress-bz2.Po ./$(DEPDIR)/compress.Po \ + ./$(DEPDIR)/cpr.Po ./$(DEPDIR)/dearmor.Po \ + ./$(DEPDIR)/decrypt-data.Po ./$(DEPDIR)/decrypt.Po \ + ./$(DEPDIR)/delkey.Po ./$(DEPDIR)/ecdh.Po \ + ./$(DEPDIR)/encrypt.Po ./$(DEPDIR)/exec.Po \ + ./$(DEPDIR)/export.Po ./$(DEPDIR)/free-packet.Po \ + ./$(DEPDIR)/getkey.Po ./$(DEPDIR)/gpg.Po \ + ./$(DEPDIR)/gpgcompose.Po ./$(DEPDIR)/gpgsql.Po \ + ./$(DEPDIR)/gpgv.Po ./$(DEPDIR)/helptext.Po \ + ./$(DEPDIR)/import.Po ./$(DEPDIR)/kbnode.Po \ + ./$(DEPDIR)/key-check.Po ./$(DEPDIR)/key-clean.Po \ + ./$(DEPDIR)/keydb.Po ./$(DEPDIR)/keyedit.Po \ + ./$(DEPDIR)/keygen.Po ./$(DEPDIR)/keyid.Po \ + ./$(DEPDIR)/keylist.Po ./$(DEPDIR)/keyring.Po \ + ./$(DEPDIR)/keyserver.Po ./$(DEPDIR)/mainproc.Po \ + ./$(DEPDIR)/mdfilter.Po ./$(DEPDIR)/migrate.Po \ + ./$(DEPDIR)/misc.Po ./$(DEPDIR)/openfile.Po \ + ./$(DEPDIR)/parse-packet.Po ./$(DEPDIR)/passphrase.Po \ + ./$(DEPDIR)/photoid.Po ./$(DEPDIR)/pkclist.Po \ + ./$(DEPDIR)/pkglue.Po ./$(DEPDIR)/plaintext.Po \ + ./$(DEPDIR)/progress.Po ./$(DEPDIR)/pubkey-enc.Po \ + ./$(DEPDIR)/revoke.Po ./$(DEPDIR)/rmd160.Po \ + ./$(DEPDIR)/server.Po ./$(DEPDIR)/seskey.Po \ + ./$(DEPDIR)/sig-check.Po ./$(DEPDIR)/sign.Po \ + ./$(DEPDIR)/skclist.Po ./$(DEPDIR)/t-keydb-get-keyblock.Po \ + ./$(DEPDIR)/t-keydb.Po ./$(DEPDIR)/t-rmd160.Po \ + ./$(DEPDIR)/t-stutter.Po ./$(DEPDIR)/tdbdump.Po \ + ./$(DEPDIR)/tdbio.Po ./$(DEPDIR)/test-stubs.Po \ + ./$(DEPDIR)/textfilter.Po ./$(DEPDIR)/tofu.Po \ + ./$(DEPDIR)/trust.Po ./$(DEPDIR)/trustdb.Po \ + ./$(DEPDIR)/verify.Po +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_@AM_V@) +am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) +am__v_CC_0 = @echo " CC " $@; +am__v_CC_1 = +CCLD = $(CC) +LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_@AM_V@) +am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) +am__v_CCLD_0 = @echo " CCLD " $@; +am__v_CCLD_1 = +SOURCES = $(gpg_SOURCES) $(gpgcompose_SOURCES) $(gpgv_SOURCES) \ + $(t_keydb_SOURCES) $(t_keydb_get_keyblock_SOURCES) \ + $(t_rmd160_SOURCES) $(t_stutter_SOURCES) +DIST_SOURCES = $(am__gpg_SOURCES_DIST) $(am__gpgcompose_SOURCES_DIST) \ + $(am__gpgv_SOURCES_DIST) $(am__t_keydb_SOURCES_DIST) \ + $(am__t_keydb_get_keyblock_SOURCES_DIST) $(t_rmd160_SOURCES) \ + $(am__t_stutter_SOURCES_DIST) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +am__tty_colors_dummy = \ + mgn= red= grn= lgn= blu= brg= std=; \ + am__color_tests=no +am__tty_colors = { \ + $(am__tty_colors_dummy); \ + if test "X$(AM_COLOR_TESTS)" = Xno; then \ + am__color_tests=no; \ + elif test "X$(AM_COLOR_TESTS)" = Xalways; then \ + am__color_tests=yes; \ + elif test "X$$TERM" != Xdumb && { test -t 1; } 2>/dev/null; then \ + am__color_tests=yes; \ + fi; \ + if test $$am__color_tests = yes; then \ + red=''; \ + grn=''; \ + lgn=''; \ + blu=''; \ + mgn=''; \ + brg=''; \ + std=''; \ + fi; \ +} +am__DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/gpg.w32-manifest.in \ + $(srcdir)/gpgv.w32-manifest.in $(top_srcdir)/am/cmacros.am \ + $(top_srcdir)/build-aux/depcomp \ + $(top_srcdir)/build-aux/mkinstalldirs +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AR = @AR@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +AWK_HEX_NUMBER_OPTION = @AWK_HEX_NUMBER_OPTION@ +BUILD_FILEVERSION = @BUILD_FILEVERSION@ +BUILD_HOSTNAME = @BUILD_HOSTNAME@ +BUILD_INCLUDED_LIBINTL = @BUILD_INCLUDED_LIBINTL@ +BUILD_REVISION = @BUILD_REVISION@ +BUILD_TIMESTAMP = @BUILD_TIMESTAMP@ +BUILD_VERSION = @BUILD_VERSION@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CC_FOR_BUILD = @CC_FOR_BUILD@ +CFLAGS = @CFLAGS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DL_LIBS = @DL_LIBS@ +DNSLIBS = @DNSLIBS@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +ENCFS = @ENCFS@ +EXEEXT = @EXEEXT@ +FUSERMOUNT = @FUSERMOUNT@ +GETTEXT_MACRO_VERSION = @GETTEXT_MACRO_VERSION@ +GMSGFMT = @GMSGFMT@ +GMSGFMT_015 = @GMSGFMT_015@ +GNUPG_AGENT_PGM = @GNUPG_AGENT_PGM@ +GNUPG_DIRMNGR_LDAP_PGM = @GNUPG_DIRMNGR_LDAP_PGM@ +GNUPG_DIRMNGR_PGM = @GNUPG_DIRMNGR_PGM@ +GNUPG_PINENTRY_PGM = @GNUPG_PINENTRY_PGM@ +GNUPG_PROTECT_TOOL_PGM = @GNUPG_PROTECT_TOOL_PGM@ +GNUPG_SCDAEMON_PGM = @GNUPG_SCDAEMON_PGM@ +GPGKEYS_LDAP = @GPGKEYS_LDAP@ +GPGRT_CONFIG = @GPGRT_CONFIG@ +GPG_ERROR_CFLAGS = @GPG_ERROR_CFLAGS@ +GPG_ERROR_CONFIG = @GPG_ERROR_CONFIG@ +GPG_ERROR_LIBS = @GPG_ERROR_LIBS@ +GPG_ERROR_MT_CFLAGS = @GPG_ERROR_MT_CFLAGS@ +GPG_ERROR_MT_LIBS = @GPG_ERROR_MT_LIBS@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +INTLLIBS = @INTLLIBS@ +INTL_MACOSX_LIBS = @INTL_MACOSX_LIBS@ +KSBA_CFLAGS = @KSBA_CFLAGS@ +KSBA_CONFIG = @KSBA_CONFIG@ +KSBA_LIBS = @KSBA_LIBS@ +LBER_LIBS = @LBER_LIBS@ +LDAPLIBS = @LDAPLIBS@ +LDAP_CPPFLAGS = @LDAP_CPPFLAGS@ +LDFLAGS = @LDFLAGS@ +LIBASSUAN_CFLAGS = @LIBASSUAN_CFLAGS@ +LIBASSUAN_CONFIG = @LIBASSUAN_CONFIG@ +LIBASSUAN_LIBS = @LIBASSUAN_LIBS@ +LIBGCRYPT_CFLAGS = @LIBGCRYPT_CFLAGS@ +LIBGCRYPT_CONFIG = @LIBGCRYPT_CONFIG@ +LIBGCRYPT_LIBS = @LIBGCRYPT_LIBS@ +LIBGNUTLS_CFLAGS = @LIBGNUTLS_CFLAGS@ +LIBGNUTLS_LIBS = @LIBGNUTLS_LIBS@ +LIBICONV = @LIBICONV@ +LIBINTL = @LIBINTL@ +LIBOBJS = @LIBOBJS@ +LIBREADLINE = @LIBREADLINE@ +LIBS = @LIBS@ +LIBUSB_CPPFLAGS = @LIBUSB_CPPFLAGS@ +LIBUSB_LIBS = @LIBUSB_LIBS@ +LIBUTIL_LIBS = @LIBUTIL_LIBS@ +LN_S = @LN_S@ +LTLIBICONV = @LTLIBICONV@ +LTLIBINTL = @LTLIBINTL@ +LTLIBOBJS = @LTLIBOBJS@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MKDIR_P = @MKDIR_P@ +MSGFMT = @MSGFMT@ +MSGFMT_015 = @MSGFMT_015@ +MSGMERGE = @MSGMERGE@ +NETLIBS = @NETLIBS@ +NPTH_CFLAGS = @NPTH_CFLAGS@ +NPTH_CONFIG = @NPTH_CONFIG@ +NPTH_LIBS = @NPTH_LIBS@ +NTBTLS_CFLAGS = @NTBTLS_CFLAGS@ +NTBTLS_CONFIG = @NTBTLS_CONFIG@ +NTBTLS_LIBS = @NTBTLS_LIBS@ +OBJEXT = @OBJEXT@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_GT = @PACKAGE_GT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PERL = @PERL@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +POSUB = @POSUB@ +RANLIB = @RANLIB@ +SENDMAIL = @SENDMAIL@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +SHRED = @SHRED@ +SQLITE3_CFLAGS = @SQLITE3_CFLAGS@ +SQLITE3_LIBS = @SQLITE3_LIBS@ +STRIP = @STRIP@ +SYSROOT = @SYSROOT@ +SYS_SOCKET_H = @SYS_SOCKET_H@ +TAR = @TAR@ +USE_C99_CFLAGS = @USE_C99_CFLAGS@ +USE_INCLUDED_LIBINTL = @USE_INCLUDED_LIBINTL@ +USE_NLS = @USE_NLS@ +VERSION = @VERSION@ +W32SOCKLIBS = @W32SOCKLIBS@ +WINDRES = @WINDRES@ +XGETTEXT = @XGETTEXT@ +XGETTEXT_015 = @XGETTEXT_015@ +XGETTEXT_EXTRA_OPTIONS = @XGETTEXT_EXTRA_OPTIONS@ +YAT2M = @YAT2M@ +ZLIBS = @ZLIBS@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_CC = @ac_ct_CC@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = $(datadir)/locale +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +EXTRA_DIST = distsigkey.gpg \ + ChangeLog-2011 \ + gpg-w32info.rc gpg.w32-manifest.in \ + gpgv-w32info.rc gpgv.w32-manifest.in \ + test.c t-keydb-keyring.kbx \ + t-keydb-get-keyblock.gpg t-stutter-data.asc \ + all-tests.scm + + +# NB: AM_CFLAGS may also be used by tools running on the build +# platform to create source files. +AM_CPPFLAGS = -DLOCALEDIR=\"$(localedir)\" $(am__append_1) \ + $(am__append_2) $(am__append_3) $(am__append_4) \ + $(am__append_5) $(am__append_6) $(am__append_7) +@HAVE_W32CE_SYSTEM_FALSE@extra_sys_libs = + +# Under Windows we use LockFileEx. WindowsCE provides this only on +# the WindowsMobile 6 platform and thus we need to use the coredll6 +# import library. We also want to use a stacksize of 256k instead of +# the 2MB which is the default with cegcc. 256k is the largest stack +# we use with pth. +@HAVE_W32CE_SYSTEM_TRUE@extra_sys_libs = -lcoredll6 +@HAVE_W32CE_SYSTEM_FALSE@extra_bin_ldflags = +@HAVE_W32CE_SYSTEM_TRUE@extra_bin_ldflags = -Wl,--stack=0x40000 +resource_objs = + +# Convenience macros +libcommon = ../common/libcommon.a +libcommonpth = ../common/libcommonpth.a +libcommontls = ../common/libcommontls.a +libcommontlsnpth = ../common/libcommontlsnpth.a +AM_CFLAGS = $(LIBGCRYPT_CFLAGS) $(SQLITE3_CFLAGS) \ + $(LIBASSUAN_CFLAGS) $(GPG_ERROR_CFLAGS) + +needed_libs = ../kbx/libkeybox.a $(libcommon) ../regexp/libregexp.a + +# Because there are no program specific transform macros we need to +# work around that to allow installing gpg as gpg2. +gpg2_hack_list = gpg gpgv +@USE_GPG2_HACK_FALSE@gpg2_hack_uninst = $(gpg2_hack_list) +@USE_GPG2_HACK_TRUE@gpg2_hack_uninst = gpg2 gpgv2 +@USE_GPG2_HACK_FALSE@use_gpg2_hack = no +@USE_GPG2_HACK_TRUE@use_gpg2_hack = yes +TESTS_ENVIRONMENT = \ + abs_top_srcdir=$(abs_top_srcdir) + +@ENABLE_BZIP2_SUPPORT_FALSE@bzip2_source = +@ENABLE_BZIP2_SUPPORT_TRUE@bzip2_source = compress-bz2.c +@ENABLE_CARD_SUPPORT_FALSE@card_source = +@ENABLE_CARD_SUPPORT_TRUE@card_source = card-util.c +@NO_TRUST_MODELS_FALSE@trust_source = trustdb.c trustdb.h tdbdump.c tdbio.c tdbio.h +@NO_TRUST_MODELS_TRUE@trust_source = +@USE_TOFU_FALSE@tofu_source = +@USE_TOFU_TRUE@tofu_source = tofu.h tofu.c gpgsql.c gpgsql.h +@HAVE_W32_SYSTEM_FALSE@gpg_robjs = +@HAVE_W32_SYSTEM_TRUE@gpg_robjs = $(resource_objs) gpg-w32info.o +@HAVE_W32_SYSTEM_FALSE@gpgv_robjs = +@HAVE_W32_SYSTEM_TRUE@gpgv_robjs = $(resource_objs) gpgv-w32info.o +common_source = \ + gpg.h \ + dek.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 \ + rmd160.c rmd160.h \ + options.h \ + openfile.c \ + keyid.c \ + packet.h \ + parse-packet.c \ + cpr.c \ + plaintext.c \ + sig-check.c \ + keylist.c \ + pkglue.c pkglue.h \ + ecdh.c + +gpg_sources = server.c \ + $(common_source) \ + pkclist.c \ + skclist.c \ + pubkey-enc.c \ + passphrase.c \ + decrypt.c \ + decrypt-data.c \ + cipher.c \ + encrypt.c \ + sign.c \ + verify.c \ + revoke.c \ + dearmor.c \ + import.c \ + export.c \ + migrate.c \ + delkey.c \ + keygen.c \ + helptext.c \ + keyserver.c \ + keyserver-internal.h \ + call-dirmngr.c call-dirmngr.h \ + photoid.c photoid.h \ + call-agent.c call-agent.h \ + trust.c $(trust_source) $(tofu_source) \ + $(card_source) \ + exec.c exec.h \ + key-clean.c key-clean.h \ + key-check.c key-check.h + +gpg_SOURCES = gpg.c \ + keyedit.c keyedit.h \ + $(gpg_sources) + +gpgcompose_SOURCES = gpgcompose.c $(gpg_sources) +gpgv_SOURCES = gpgv.c \ + $(common_source) \ + verify.c + + +#gpgd_SOURCES = gpgd.c \ +# ks-proto.h \ +# ks-proto.c \ +# ks-db.c \ +# ks-db.h \ +# $(common_source) +LDADD = $(needed_libs) ../common/libgpgrl.a \ + $(ZLIBS) $(LIBINTL) $(CAPLIBS) $(NETLIBS) + +gpg_LDADD = $(LDADD) $(LIBGCRYPT_LIBS) $(SQLITE3_LIBS) $(LIBREADLINE) \ + $(LIBASSUAN_LIBS) $(GPG_ERROR_LIBS) \ + $(LIBICONV) $(gpg_robjs) $(extra_sys_libs) + +gpg_LDFLAGS = $(extra_bin_ldflags) +gpgv_LDADD = $(LDADD) $(LIBGCRYPT_LIBS) \ + $(GPG_ERROR_LIBS) \ + $(LIBICONV) $(gpgv_robjs) $(extra_sys_libs) + +gpgv_LDFLAGS = $(extra_bin_ldflags) +gpgcompose_LDADD = $(LDADD) $(SQLITE3_LIBS) $(LIBGCRYPT_LIBS) $(LIBREADLINE) \ + $(LIBASSUAN_LIBS) $(GPG_ERROR_LIBS) \ + $(LIBICONV) $(extra_sys_libs) + +gpgcompose_LDFLAGS = $(extra_bin_ldflags) +t_common_ldadd = +module_tests = t-rmd160 t-keydb t-keydb-get-keyblock t-stutter +t_rmd160_SOURCES = t-rmd160.c rmd160.c +t_rmd160_LDADD = $(t_common_ldadd) +t_keydb_SOURCES = t-keydb.c test-stubs.c $(common_source) +t_keydb_LDADD = $(LDADD) $(LIBGCRYPT_LIBS) $(GPG_ERROR_LIBS) \ + $(LIBICONV) $(t_common_ldadd) + +t_keydb_get_keyblock_SOURCES = t-keydb-get-keyblock.c test-stubs.c \ + $(common_source) + +t_keydb_get_keyblock_LDADD = $(LDADD) $(LIBGCRYPT_LIBS) $(GPG_ERROR_LIBS) \ + $(LIBICONV) $(t_common_ldadd) + +t_stutter_SOURCES = t-stutter.c test-stubs.c \ + $(common_source) + +t_stutter_LDADD = $(LDADD) $(LIBGCRYPT_LIBS) $(GPG_ERROR_LIBS) \ + $(LIBICONV) $(t_common_ldadd) + +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .o .obj .rc +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(top_srcdir)/am/cmacros.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu g10/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --gnu g10/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + esac; +$(top_srcdir)/am/cmacros.am $(am__empty): + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): +gpg.w32-manifest: $(top_builddir)/config.status $(srcdir)/gpg.w32-manifest.in + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ +gpgv.w32-manifest: $(top_builddir)/config.status $(srcdir)/gpgv.w32-manifest.in + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ + +clean-noinstPROGRAMS: + -test -z "$(noinst_PROGRAMS)" || rm -f $(noinst_PROGRAMS) + +gpg$(EXEEXT): $(gpg_OBJECTS) $(gpg_DEPENDENCIES) $(EXTRA_gpg_DEPENDENCIES) + @rm -f gpg$(EXEEXT) + $(AM_V_CCLD)$(gpg_LINK) $(gpg_OBJECTS) $(gpg_LDADD) $(LIBS) + +gpgcompose$(EXEEXT): $(gpgcompose_OBJECTS) $(gpgcompose_DEPENDENCIES) $(EXTRA_gpgcompose_DEPENDENCIES) + @rm -f gpgcompose$(EXEEXT) + $(AM_V_CCLD)$(gpgcompose_LINK) $(gpgcompose_OBJECTS) $(gpgcompose_LDADD) $(LIBS) + +gpgv$(EXEEXT): $(gpgv_OBJECTS) $(gpgv_DEPENDENCIES) $(EXTRA_gpgv_DEPENDENCIES) + @rm -f gpgv$(EXEEXT) + $(AM_V_CCLD)$(gpgv_LINK) $(gpgv_OBJECTS) $(gpgv_LDADD) $(LIBS) + +t-keydb$(EXEEXT): $(t_keydb_OBJECTS) $(t_keydb_DEPENDENCIES) $(EXTRA_t_keydb_DEPENDENCIES) + @rm -f t-keydb$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(t_keydb_OBJECTS) $(t_keydb_LDADD) $(LIBS) + +t-keydb-get-keyblock$(EXEEXT): $(t_keydb_get_keyblock_OBJECTS) $(t_keydb_get_keyblock_DEPENDENCIES) $(EXTRA_t_keydb_get_keyblock_DEPENDENCIES) + @rm -f t-keydb-get-keyblock$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(t_keydb_get_keyblock_OBJECTS) $(t_keydb_get_keyblock_LDADD) $(LIBS) + +t-rmd160$(EXEEXT): $(t_rmd160_OBJECTS) $(t_rmd160_DEPENDENCIES) $(EXTRA_t_rmd160_DEPENDENCIES) + @rm -f t-rmd160$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(t_rmd160_OBJECTS) $(t_rmd160_LDADD) $(LIBS) + +t-stutter$(EXEEXT): $(t_stutter_OBJECTS) $(t_stutter_DEPENDENCIES) $(EXTRA_t_stutter_DEPENDENCIES) + @rm -f t-stutter$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(t_stutter_OBJECTS) $(t_stutter_LDADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/armor.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/build-packet.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/call-agent.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/call-dirmngr.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/card-util.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cipher.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/compress-bz2.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/compress.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cpr.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dearmor.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/decrypt-data.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/decrypt.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/delkey.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ecdh.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/encrypt.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/exec.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/export.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/free-packet.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/getkey.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gpg.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gpgcompose.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gpgsql.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gpgv.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/helptext.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/import.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/kbnode.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/key-check.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/key-clean.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/keydb.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/keyedit.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/keygen.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/keyid.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/keylist.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/keyring.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/keyserver.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mainproc.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mdfilter.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/migrate.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/misc.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/openfile.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/parse-packet.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/passphrase.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/photoid.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pkclist.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pkglue.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/plaintext.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/progress.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pubkey-enc.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/revoke.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rmd160.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/server.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/seskey.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sig-check.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sign.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/skclist.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/t-keydb-get-keyblock.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/t-keydb.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/t-rmd160.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/t-stutter.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tdbdump.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tdbio.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-stubs.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/textfilter.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tofu.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/trust.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/trustdb.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/verify.Po@am__quote@ # am--include-marker + +$(am__depfiles_remade): + @$(MKDIR_P) $(@D) + @echo '# dummy' >$@-t && $(am__mv) $@-t $@ + +am--depfiles: $(am__depfiles_remade) + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +check-TESTS: $(TESTS) + @failed=0; all=0; xfail=0; xpass=0; skip=0; \ + srcdir=$(srcdir); export srcdir; \ + list=' $(TESTS) '; \ + $(am__tty_colors); \ + if test -n "$$list"; then \ + for tst in $$list; do \ + if test -f ./$$tst; then dir=./; \ + elif test -f $$tst; then dir=; \ + else dir="$(srcdir)/"; fi; \ + if $(TESTS_ENVIRONMENT) $${dir}$$tst $(AM_TESTS_FD_REDIRECT); then \ + all=`expr $$all + 1`; \ + case " $(XFAIL_TESTS) " in \ + *[\ \ ]$$tst[\ \ ]*) \ + xpass=`expr $$xpass + 1`; \ + failed=`expr $$failed + 1`; \ + col=$$red; res=XPASS; \ + ;; \ + *) \ + col=$$grn; res=PASS; \ + ;; \ + esac; \ + elif test $$? -ne 77; then \ + all=`expr $$all + 1`; \ + case " $(XFAIL_TESTS) " in \ + *[\ \ ]$$tst[\ \ ]*) \ + xfail=`expr $$xfail + 1`; \ + col=$$lgn; res=XFAIL; \ + ;; \ + *) \ + failed=`expr $$failed + 1`; \ + col=$$red; res=FAIL; \ + ;; \ + esac; \ + else \ + skip=`expr $$skip + 1`; \ + col=$$blu; res=SKIP; \ + fi; \ + echo "$${col}$$res$${std}: $$tst"; \ + done; \ + if test "$$all" -eq 1; then \ + tests="test"; \ + All=""; \ + else \ + tests="tests"; \ + All="All "; \ + fi; \ + if test "$$failed" -eq 0; then \ + if test "$$xfail" -eq 0; then \ + banner="$$All$$all $$tests passed"; \ + else \ + if test "$$xfail" -eq 1; then failures=failure; else failures=failures; fi; \ + banner="$$All$$all $$tests behaved as expected ($$xfail expected $$failures)"; \ + fi; \ + else \ + if test "$$xpass" -eq 0; then \ + banner="$$failed of $$all $$tests failed"; \ + else \ + if test "$$xpass" -eq 1; then passes=pass; else passes=passes; fi; \ + banner="$$failed of $$all $$tests did not behave as expected ($$xpass unexpected $$passes)"; \ + fi; \ + fi; \ + dashes="$$banner"; \ + skipped=""; \ + if test "$$skip" -ne 0; then \ + if test "$$skip" -eq 1; then \ + skipped="($$skip test was not run)"; \ + else \ + skipped="($$skip tests were not run)"; \ + fi; \ + test `echo "$$skipped" | wc -c` -le `echo "$$banner" | wc -c` || \ + dashes="$$skipped"; \ + fi; \ + report=""; \ + if test "$$failed" -ne 0 && test -n "$(PACKAGE_BUGREPORT)"; then \ + report="Please report to $(PACKAGE_BUGREPORT)"; \ + test `echo "$$report" | wc -c` -le `echo "$$banner" | wc -c` || \ + dashes="$$report"; \ + fi; \ + dashes=`echo "$$dashes" | sed s/./=/g`; \ + if test "$$failed" -eq 0; then \ + col="$$grn"; \ + else \ + col="$$red"; \ + fi; \ + echo "$${col}$$dashes$${std}"; \ + echo "$${col}$$banner$${std}"; \ + test -z "$$skipped" || echo "$${col}$$skipped$${std}"; \ + test -z "$$report" || echo "$${col}$$report$${std}"; \ + echo "$${col}$$dashes$${std}"; \ + test "$$failed" -eq 0; \ + else :; fi + +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am + $(MAKE) $(AM_MAKEFLAGS) check-TESTS +check: check-am +all-am: Makefile $(PROGRAMS) +installdirs: +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-generic clean-noinstPROGRAMS mostlyclean-am + +distclean: distclean-am + -rm -f ./$(DEPDIR)/armor.Po + -rm -f ./$(DEPDIR)/build-packet.Po + -rm -f ./$(DEPDIR)/call-agent.Po + -rm -f ./$(DEPDIR)/call-dirmngr.Po + -rm -f ./$(DEPDIR)/card-util.Po + -rm -f ./$(DEPDIR)/cipher.Po + -rm -f ./$(DEPDIR)/compress-bz2.Po + -rm -f ./$(DEPDIR)/compress.Po + -rm -f ./$(DEPDIR)/cpr.Po + -rm -f ./$(DEPDIR)/dearmor.Po + -rm -f ./$(DEPDIR)/decrypt-data.Po + -rm -f ./$(DEPDIR)/decrypt.Po + -rm -f ./$(DEPDIR)/delkey.Po + -rm -f ./$(DEPDIR)/ecdh.Po + -rm -f ./$(DEPDIR)/encrypt.Po + -rm -f ./$(DEPDIR)/exec.Po + -rm -f ./$(DEPDIR)/export.Po + -rm -f ./$(DEPDIR)/free-packet.Po + -rm -f ./$(DEPDIR)/getkey.Po + -rm -f ./$(DEPDIR)/gpg.Po + -rm -f ./$(DEPDIR)/gpgcompose.Po + -rm -f ./$(DEPDIR)/gpgsql.Po + -rm -f ./$(DEPDIR)/gpgv.Po + -rm -f ./$(DEPDIR)/helptext.Po + -rm -f ./$(DEPDIR)/import.Po + -rm -f ./$(DEPDIR)/kbnode.Po + -rm -f ./$(DEPDIR)/key-check.Po + -rm -f ./$(DEPDIR)/key-clean.Po + -rm -f ./$(DEPDIR)/keydb.Po + -rm -f ./$(DEPDIR)/keyedit.Po + -rm -f ./$(DEPDIR)/keygen.Po + -rm -f ./$(DEPDIR)/keyid.Po + -rm -f ./$(DEPDIR)/keylist.Po + -rm -f ./$(DEPDIR)/keyring.Po + -rm -f ./$(DEPDIR)/keyserver.Po + -rm -f ./$(DEPDIR)/mainproc.Po + -rm -f ./$(DEPDIR)/mdfilter.Po + -rm -f ./$(DEPDIR)/migrate.Po + -rm -f ./$(DEPDIR)/misc.Po + -rm -f ./$(DEPDIR)/openfile.Po + -rm -f ./$(DEPDIR)/parse-packet.Po + -rm -f ./$(DEPDIR)/passphrase.Po + -rm -f ./$(DEPDIR)/photoid.Po + -rm -f ./$(DEPDIR)/pkclist.Po + -rm -f ./$(DEPDIR)/pkglue.Po + -rm -f ./$(DEPDIR)/plaintext.Po + -rm -f ./$(DEPDIR)/progress.Po + -rm -f ./$(DEPDIR)/pubkey-enc.Po + -rm -f ./$(DEPDIR)/revoke.Po + -rm -f ./$(DEPDIR)/rmd160.Po + -rm -f ./$(DEPDIR)/server.Po + -rm -f ./$(DEPDIR)/seskey.Po + -rm -f ./$(DEPDIR)/sig-check.Po + -rm -f ./$(DEPDIR)/sign.Po + -rm -f ./$(DEPDIR)/skclist.Po + -rm -f ./$(DEPDIR)/t-keydb-get-keyblock.Po + -rm -f ./$(DEPDIR)/t-keydb.Po + -rm -f ./$(DEPDIR)/t-rmd160.Po + -rm -f ./$(DEPDIR)/t-stutter.Po + -rm -f ./$(DEPDIR)/tdbdump.Po + -rm -f ./$(DEPDIR)/tdbio.Po + -rm -f ./$(DEPDIR)/test-stubs.Po + -rm -f ./$(DEPDIR)/textfilter.Po + -rm -f ./$(DEPDIR)/tofu.Po + -rm -f ./$(DEPDIR)/trust.Po + -rm -f ./$(DEPDIR)/trustdb.Po + -rm -f ./$(DEPDIR)/verify.Po + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: install-data-local + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: + @$(NORMAL_INSTALL) + $(MAKE) $(AM_MAKEFLAGS) install-exec-hook +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f ./$(DEPDIR)/armor.Po + -rm -f ./$(DEPDIR)/build-packet.Po + -rm -f ./$(DEPDIR)/call-agent.Po + -rm -f ./$(DEPDIR)/call-dirmngr.Po + -rm -f ./$(DEPDIR)/card-util.Po + -rm -f ./$(DEPDIR)/cipher.Po + -rm -f ./$(DEPDIR)/compress-bz2.Po + -rm -f ./$(DEPDIR)/compress.Po + -rm -f ./$(DEPDIR)/cpr.Po + -rm -f ./$(DEPDIR)/dearmor.Po + -rm -f ./$(DEPDIR)/decrypt-data.Po + -rm -f ./$(DEPDIR)/decrypt.Po + -rm -f ./$(DEPDIR)/delkey.Po + -rm -f ./$(DEPDIR)/ecdh.Po + -rm -f ./$(DEPDIR)/encrypt.Po + -rm -f ./$(DEPDIR)/exec.Po + -rm -f ./$(DEPDIR)/export.Po + -rm -f ./$(DEPDIR)/free-packet.Po + -rm -f ./$(DEPDIR)/getkey.Po + -rm -f ./$(DEPDIR)/gpg.Po + -rm -f ./$(DEPDIR)/gpgcompose.Po + -rm -f ./$(DEPDIR)/gpgsql.Po + -rm -f ./$(DEPDIR)/gpgv.Po + -rm -f ./$(DEPDIR)/helptext.Po + -rm -f ./$(DEPDIR)/import.Po + -rm -f ./$(DEPDIR)/kbnode.Po + -rm -f ./$(DEPDIR)/key-check.Po + -rm -f ./$(DEPDIR)/key-clean.Po + -rm -f ./$(DEPDIR)/keydb.Po + -rm -f ./$(DEPDIR)/keyedit.Po + -rm -f ./$(DEPDIR)/keygen.Po + -rm -f ./$(DEPDIR)/keyid.Po + -rm -f ./$(DEPDIR)/keylist.Po + -rm -f ./$(DEPDIR)/keyring.Po + -rm -f ./$(DEPDIR)/keyserver.Po + -rm -f ./$(DEPDIR)/mainproc.Po + -rm -f ./$(DEPDIR)/mdfilter.Po + -rm -f ./$(DEPDIR)/migrate.Po + -rm -f ./$(DEPDIR)/misc.Po + -rm -f ./$(DEPDIR)/openfile.Po + -rm -f ./$(DEPDIR)/parse-packet.Po + -rm -f ./$(DEPDIR)/passphrase.Po + -rm -f ./$(DEPDIR)/photoid.Po + -rm -f ./$(DEPDIR)/pkclist.Po + -rm -f ./$(DEPDIR)/pkglue.Po + -rm -f ./$(DEPDIR)/plaintext.Po + -rm -f ./$(DEPDIR)/progress.Po + -rm -f ./$(DEPDIR)/pubkey-enc.Po + -rm -f ./$(DEPDIR)/revoke.Po + -rm -f ./$(DEPDIR)/rmd160.Po + -rm -f ./$(DEPDIR)/server.Po + -rm -f ./$(DEPDIR)/seskey.Po + -rm -f ./$(DEPDIR)/sig-check.Po + -rm -f ./$(DEPDIR)/sign.Po + -rm -f ./$(DEPDIR)/skclist.Po + -rm -f ./$(DEPDIR)/t-keydb-get-keyblock.Po + -rm -f ./$(DEPDIR)/t-keydb.Po + -rm -f ./$(DEPDIR)/t-rmd160.Po + -rm -f ./$(DEPDIR)/t-stutter.Po + -rm -f ./$(DEPDIR)/tdbdump.Po + -rm -f ./$(DEPDIR)/tdbio.Po + -rm -f ./$(DEPDIR)/test-stubs.Po + -rm -f ./$(DEPDIR)/textfilter.Po + -rm -f ./$(DEPDIR)/tofu.Po + -rm -f ./$(DEPDIR)/trust.Po + -rm -f ./$(DEPDIR)/trustdb.Po + -rm -f ./$(DEPDIR)/verify.Po + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-local + +.MAKE: check-am install-am install-exec-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-TESTS \ + check-am clean clean-generic clean-noinstPROGRAMS \ + cscopelist-am ctags ctags-am distclean distclean-compile \ + distclean-generic distclean-tags distdir dvi dvi-am html \ + html-am info info-am install install-am install-data \ + install-data-am install-data-local install-dvi install-dvi-am \ + install-exec install-exec-am install-exec-hook install-html \ + install-html-am install-info install-info-am install-man \ + install-pdf install-pdf-am install-ps install-ps-am \ + install-strip installcheck installcheck-am installdirs \ + maintainer-clean maintainer-clean-generic mostlyclean \ + mostlyclean-compile mostlyclean-generic pdf pdf-am ps ps-am \ + tags tags-am uninstall uninstall-am uninstall-local + +.PRECIOUS: Makefile + + +@HAVE_W32_SYSTEM_TRUE@.rc.o: +@HAVE_W32_SYSTEM_TRUE@ $(WINDRES) $(DEFAULT_INCLUDES) $(INCLUDES) "$<" "$@" +@HAVE_W32_SYSTEM_TRUE@gpg-w32info.o : gpg.w32-manifest +@HAVE_W32_SYSTEM_TRUE@gpgv-w32info.o : gpgv.w32-manifest + +$(PROGRAMS): $(needed_libs) ../common/libgpgrl.a + +# NB: To install gpg and gpgv we use this -hook. This code has to +# duplicate most of the automake generated install-binPROGRAMS target +# so that directories are created and the transform feature works. +install-exec-hook: + @echo "running install-exec-hook"; \ + echo " $(MKDIR_P) '$(DESTDIR)$(bindir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(bindir)"; \ + for p in $(gpg2_hack_list); do \ + echo "$$p$(EXEEXT) $$p$(EXEEXT)"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + for f in $$files; do \ + if test $(use_gpg2_hack) = yes ; \ + then f2=`echo "$${f}" | sed 's/$(EXEEXT)$$//'`2$(EXEEXT); \ + else f2="$${f}" ;\ + fi ; \ + echo "$(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) \ + $${f} '$(DESTDIR)$(bindir)/$${f2}'"; \ + $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) \ + $${f} "$(DESTDIR)$(bindir)/$${f2}"; \ + done; \ + done + +install-data-local: + $(mkinstalldirs) $(DESTDIR)$(pkgdatadir) + $(INSTALL_DATA) $(srcdir)/distsigkey.gpg \ + $(DESTDIR)$(pkgdatadir)/distsigkey.gpg + +# NB: For uninstalling gpg and gpgv we use -local because there is +# no need for a specific order the targets need to be run. +uninstall-local: + -@rm $(DESTDIR)$(pkgdatadir)/distsigkey.gpg + -@files=`for p in $(gpg2_hack_uninst); do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + echo " ( cd '$(DESTDIR)$(bindir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(bindir)" && rm -f $$files + +# 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/all-tests.scm b/g10/all-tests.scm new file mode 100644 index 0000000..982220b --- /dev/null +++ b/g10/all-tests.scm @@ -0,0 +1,35 @@ +;; Copyright (C) 2017 g10 Code GmbH +;; +;; This file is part of GnuPG. +;; +;; GnuPG is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation; either version 3 of the License, or +;; (at your option) any later version. +;; +;; GnuPG is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. +;; +;; You should have received a copy of the GNU General Public License +;; along with this program; if not, see . + +(export all-tests + ;; Parse the Makefile.am to find all tests. + + (load (with-path "makefile.scm")) + + (define (expander filename port key) + (parse-makefile port key)) + + (define (parse filename key) + (parse-makefile-expand filename expander key)) + + (map (lambda (name) + (test::binary #f + (path-join "g10" name) + (path-join (getenv "objdir") "g10" name))) + (parse-makefile-expand (in-srcdir "g10" "Makefile.am") + (lambda (filename port key) (parse-makefile port key)) + "module_tests"))) diff --git a/g10/armor.c b/g10/armor.c new file mode 100644 index 0000000..36215a3 --- /dev/null +++ b/g10/armor.c @@ -0,0 +1,1570 @@ +/* armor.c - Armor flter + * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, + * 2007 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include +#include +#include +#include +#include +#include + +#include "gpg.h" +#include "../common/status.h" +#include "../common/iobuf.h" +#include "../common/util.h" +#include "filter.h" +#include "packet.h" +#include "options.h" +#include "main.h" +#include "../common/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 +}; + + +static int armor_filter ( void *opaque, int control, + iobuf_t chain, byte *buf, size_t *ret_len); + + + + +/* 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; + log_assert (afx->refcount); + 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; + + 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. See also + * parse-packet.c for details on this code. + * + * Note that the buffer BUF needs to be at least 2 bytes long. If in + * doubt that the second byte to 0. + * + * Returns: True if it seems to be armored + */ +static int +is_armored (const byte *buf) +{ + int ctb, pkttype; + int indeterminate_length_allowed; + + ctb = *buf; + if( !(ctb & 0x80) ) + /* The most significant bit of the CTB must be set. Since it is + cleared, this is not a binary OpenPGP message. Assume it is + armored. */ + return 1; + + pkttype = ctb & 0x40 ? (ctb & 0x3f) : ((ctb>>2)&0xf); + switch( pkttype ) { + case PKT_PUBKEY_ENC: + case PKT_SIGNATURE: + case PKT_SYMKEY_ENC: + case PKT_ONEPASS_SIG: + case PKT_SECRET_KEY: + case PKT_PUBLIC_KEY: + case PKT_SECRET_SUBKEY: + case PKT_MARKER: + case PKT_RING_TRUST: + case PKT_USER_ID: + case PKT_PUBLIC_SUBKEY: + case PKT_ATTRIBUTE: + case PKT_MDC: + indeterminate_length_allowed = 0; + break; + + case PKT_COMPRESSED: + case PKT_ENCRYPTED: + case PKT_ENCRYPTED_MDC: + case PKT_PLAINTEXT: + case PKT_OLD_COMMENT: + case PKT_COMMENT: + case PKT_GPG_CONTROL: + indeterminate_length_allowed = 1; + break; + + default: + /* Invalid packet type. */ + return 1; + } + + if (! indeterminate_length_allowed) + /* It is only legal to use an indeterminate length with a few + packet types. If a packet uses an indeterminate length, but + that is not allowed, then the data is not valid binary + OpenPGP data. */ + { + int new_format; + int indeterminate_length; + + new_format = !! (ctb & (1 << 6)); + if (new_format) + indeterminate_length = (buf[1] >= 224 && buf[1] < 255); + else + indeterminate_length = (ctb & 3) == 3; + + if (indeterminate_length) + return 1; + } + + /* The first CTB seems legit. It is probably not armored + data. */ + return 0; +} + + +/**************** + * 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[2]; + int n; + + /* fixme: there might be a problem with iobuf_peek */ + n = iobuf_peek (a, buf, 2); + if( n == -1 ) + return 0; /* EOF, doesn't matter whether armored or not */ + if( !n ) + return 1; /* can't check it: try armored */ + if (n != 2) + return 0; /* short buffer */ + 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 */ + + 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, "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; +} + +/* Returns true if this is a valid armor tag as per RFC-2440bis-21. */ +static int +is_armor_tag(const char *line) +{ + if(strncmp(line,"Version",7)==0 + || strncmp(line,"Comment",7)==0 + || strncmp(line,"MessageID",9)==0 + || strncmp(line,"Hash",4)==0 + || strncmp(line,"Charset",7)==0) + return 1; + + return 0; +} + +/**************** + * 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. 4880 clarified + this was only 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 = length_sans_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: ")); + es_write_sanitized (log_get_stream (), line, len, NULL, NULL); + log_printf ("\n"); + return -1; + } + + /* Chop off the whitespace we detected before */ + len=len2; + line[len2]='\0'; + + if( opt.verbose ) { + log_info(_("armor header: ")); + es_write_sanitized (log_get_stream (), line, len, NULL, NULL); + log_printf ("\n"); + } + + 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; + } + } + else if(!is_armor_tag(line)) + { + /* Section 6.2: "Unknown keys should be reported to the user, + but OpenPGP should continue to process the message." Note + that in a clearsigned message this applies to the signature + part (i.e. "BEGIN PGP SIGNATURE") and not the signed data + ("BEGIN PGP SIGNED MESSAGE"). The only key allowed in the + signed data section is "Hash". */ + + log_info(_("unknown armor header: ")); + es_write_sanitized (log_get_stream (), line, len, NULL, NULL); + log_printf ("\n"); + } + + 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 (len >= 2 && !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 = gpg_error (GPG_ERR_INV_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 = GPG_ERR_INV_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: ")); + es_write_sanitized (log_get_stream (), p, n, + NULL, NULL); + log_printf ("\n"); + } + + lastline = 1; + rc = -1; + } + } + else if(!afx->not_dash_escaped) + { + /* Bad dash-escaping. */ + log_info (_("invalid dash escaped line: ")); + es_write_sanitized (log_get_stream (), p, n, NULL, NULL); + log_printf ("\n"); + } + } + + /* Now handle the end-of-line canonicalization */ + if( !afx->not_dash_escaped ) + { + int crlf = n > 1 && p[n-2] == '\r' && p[n-1]=='\n'; + + afx->buffer_len= + trim_trailing_chars( &p[afx->buffer_pos], n-afx->buffer_pos, + " \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 gpg_error (GPG_ERR_INV_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; + } + } + + /* Occasionally a bug MTA will leave the = escaped as + =3D. If the 4 characters following that are valid + Radix64 characters and they are following by a new + line, assume that this is the case and skip the + 3D. */ + if (afx->buffer_pos + 6 < afx->buffer_len + && afx->buffer[afx->buffer_pos + 0] == '3' + && afx->buffer[afx->buffer_pos + 1] == 'D' + && asctobin[afx->buffer[afx->buffer_pos + 2]] != 255 + && asctobin[afx->buffer[afx->buffer_pos + 3]] != 255 + && asctobin[afx->buffer[afx->buffer_pos + 4]] != 255 + && asctobin[afx->buffer[afx->buffer_pos + 5]] != 255 + && afx->buffer[afx->buffer_pos + 6] == '\n') + { + afx->buffer_pos += 2; + afx->qp_detected = 1; + } + + if (!n) + 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 = GPG_ERR_INVALID_ARMOR; + } + else { + log_error(_("error in trailer line\n")); + rc = GPG_ERR_INVALID_ARMOR; + } +#endif + } + } + } + + if( !n && !onlypad ) + rc = -1; + + *retn = n; + return rc; +} + +/**************** + * This filter is used to handle the armor stuff + */ +static 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 ) { + /* Copy the data from AFX->BUFFER to BUF. */ + 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; + } + /* If there is still space in BUF, read directly into it. */ + for(; n < size; n++ ) { + if( (c=iobuf_get(a)) == -1 ) + break; + buf[n] = c & 0xff; + } + if( !n ) + /* We didn't get any data. EOF. */ + 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|8|16|32|64; + if( !hashes ) { + hashes |= 2; /* Default to SHA-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 & 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_t 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.emit_version) + { + iobuf_writestr (a, "Version: "GNUPG_NAME" v"); + for (s=VERSION; *s && *s != '.'; s++) + iobuf_writebyte (a, *s); + if (opt.emit_version > 1 && *s) + { + iobuf_writebyte (a, *s++); + for (; *s && *s != '.'; s++) + iobuf_writebyte (a, *s); + if (opt.emit_version > 2) + { + for (; *s && *s != '-' && !spacep (s); s++) + iobuf_writebyte (a, *s); + if (opt.emit_version > 3) + iobuf_writestr (a, " (" PRINTABLE_OS_NAME ")"); + } + } + iobuf_writestr(a,afx->eol); + } + + /* write the comment strings */ + for(;comment;comment=comment->next) + { + iobuf_writestr(a, "Comment: " ); + for( s=comment->d; *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; + if( idx ) { + c = bintoasc[(afx->radbuf[0]>>2)&077]; + iobuf_put(a, c); + if( idx == 1 ) { + c = bintoasc[((afx->radbuf[0] << 4) & 060) & 077]; + iobuf_put(a, c); + iobuf_put(a, '='); + iobuf_put(a, '='); + } + else { /* 2 */ + c = bintoasc[(((afx->radbuf[0]<<4)&060) + |((afx->radbuf[1]>>4)&017))&077]; + iobuf_put(a, c); + c = bintoasc[((afx->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 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 ) + mem2str (buf, "armor_filter", *ret_len); + 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[2]; + tmp[0] = c; + tmp[1] = 0; + 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 initialization */ + x->pos = 0; + x->state = STA_compare_header; /* fall through */ + 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; /* fall through */ + 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..a40ed0d --- /dev/null +++ b/g10/build-packet.c @@ -0,0 +1,1808 @@ +/* build-packet.c - assemble packets and write them + * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, + * 2006, 2010, 2011 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include +#include +#include +#include +#include + +#include "gpg.h" +#include "../common/util.h" +#include "packet.h" +#include "../common/status.h" +#include "../common/iobuf.h" +#include "../common/i18n.h" +#include "options.h" +#include "../common/host2net.h" + +static gpg_error_t do_ring_trust (iobuf_t out, PKT_ring_trust *rt); +static int do_user_id( IOBUF out, int ctb, PKT_user_id *uid ); +static int do_key (iobuf_t out, int ctb, PKT_public_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 ); + +/* Returns 1 if CTB is a new format ctb and 0 if CTB is an old format + ctb. */ +static int +ctb_new_format_p (int ctb) +{ + /* Bit 7 must always be set. */ + log_assert ((ctb & (1 << 7))); + /* Bit 6 indicates whether the packet is a new format packet. */ + return (ctb & (1 << 6)); +} + +/* Extract the packet type from a CTB. */ +static int +ctb_pkttype (int ctb) +{ + if (ctb_new_format_p (ctb)) + /* Bits 0 through 5 are the packet type. */ + return (ctb & ((1 << 6) - 1)); + else + /* Bits 2 through 5 are the packet type. */ + return (ctb & ((1 << 6) - 1)) >> 2; +} + + +/* Build a packet and write it to the stream OUT. + * Returns: 0 on success or on an error code. */ +int +build_packet (IOBUF out, PACKET *pkt) +{ + int rc = 0; + int new_ctb = 0; + int ctb, pkttype; + + if (DBG_PACKET) + log_debug ("build_packet() type=%d\n", pkt->pkttype); + log_assert (pkt->pkt.generic); + + switch ((pkttype = pkt->pkttype)) + { + case PKT_PUBLIC_KEY: + if (pkt->pkt.public_key->seckey_info) + pkttype = PKT_SECRET_KEY; + break; + case PKT_PUBLIC_SUBKEY: + if (pkt->pkt.public_key->seckey_info) + pkttype = PKT_SECRET_SUBKEY; + break; + 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: + case PKT_SECRET_SUBKEY: + case PKT_SECRET_KEY: + rc = do_key (out, ctb, pkt->pkt.public_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: + /* Ignore it (only written by build_packet_and_meta) */ + break; + 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; +} + + +/* Build a packet and write it to the stream OUT. This variant also + * writes the meta data using ring trust packets. Returns: 0 on + * success or on error code. */ +gpg_error_t +build_packet_and_meta (iobuf_t out, PACKET *pkt) +{ + gpg_error_t err; + PKT_ring_trust rt = {0}; + + err = build_packet (out, pkt); + if (err) + ; + else if (pkt->pkttype == PKT_SIGNATURE) + { + PKT_signature *sig = pkt->pkt.signature; + + rt.subtype = RING_TRUST_SIG; + /* Note: trustval is not yet used. */ + if (sig->flags.checked) + { + rt.sigcache = 1; + if (sig->flags.valid) + rt.sigcache |= 2; + } + err = do_ring_trust (out, &rt); + } + else if (pkt->pkttype == PKT_USER_ID + || pkt->pkttype == PKT_ATTRIBUTE) + { + PKT_user_id *uid = pkt->pkt.user_id; + + rt.subtype = RING_TRUST_UID; + rt.keyorg = uid->keyorg; + rt.keyupdate = uid->keyupdate; + rt.url = uid->updateurl; + err = do_ring_trust (out, &rt); + rt.url = NULL; + } + else if (pkt->pkttype == PKT_PUBLIC_KEY + || pkt->pkttype == PKT_SECRET_KEY) + { + PKT_public_key *pk = pkt->pkt.public_key; + + rt.subtype = RING_TRUST_KEY; + rt.keyorg = pk->keyorg; + rt.keyupdate = pk->keyupdate; + rt.url = pk->updateurl; + err = do_ring_trust (out, &rt); + rt.url = NULL; + + } + + return err; +} + + +/* + * Write the mpi A to OUT. + */ +gpg_error_t +gpg_mpi_write (iobuf_t out, gcry_mpi_t a) +{ + int rc; + + if (gcry_mpi_get_flag (a, GCRYMPI_FLAG_OPAQUE)) + { + unsigned int nbits; + const unsigned char *p; + unsigned char lenhdr[2]; + + /* gcry_log_debugmpi ("a", a); */ + p = gcry_mpi_get_opaque (a, &nbits); + if (p) + { + /* Strip leading zero bits. */ + for (; nbits >= 8 && !*p; p++, nbits -= 8) + ; + if (nbits >= 8 && !(*p & 0x80)) + if (--nbits >= 7 && !(*p & 0x40)) + if (--nbits >= 6 && !(*p & 0x20)) + if (--nbits >= 5 && !(*p & 0x10)) + if (--nbits >= 4 && !(*p & 0x08)) + if (--nbits >= 3 && !(*p & 0x04)) + if (--nbits >= 2 && !(*p & 0x02)) + if (--nbits >= 1 && !(*p & 0x01)) + --nbits; + } + /* gcry_log_debug (" [%u bit]\n", nbits); */ + /* gcry_log_debughex (" ", p, (nbits+7)/8); */ + lenhdr[0] = nbits >> 8; + lenhdr[1] = nbits; + rc = iobuf_write (out, lenhdr, 2); + if (!rc && p) + rc = iobuf_write (out, p, (nbits+7)/8); + } + else + { + char buffer[(MAX_EXTERN_MPI_BITS+7)/8+2]; /* 2 is for the mpi length. */ + size_t nbytes; + + nbytes = DIM(buffer); + rc = gcry_mpi_print (GCRYMPI_FMT_PGP, buffer, nbytes, &nbytes, a ); + if( !rc ) + rc = iobuf_write( out, buffer, nbytes ); + else if (gpg_err_code(rc) == GPG_ERR_TOO_SHORT ) + { + log_info ("mpi too large (%u bits)\n", gcry_mpi_get_nbits (a)); + /* The buffer was too small. We better tell the user about the MPI. */ + rc = gpg_error (GPG_ERR_TOO_LARGE); + } + } + + return rc; +} + + +/* + * Write an opaque MPI to the output stream without length info. + */ +gpg_error_t +gpg_mpi_write_nohdr (iobuf_t out, gcry_mpi_t a) +{ + int rc; + + if (gcry_mpi_get_flag (a, GCRYMPI_FLAG_OPAQUE)) + { + unsigned int nbits; + const void *p; + + p = gcry_mpi_get_opaque (a, &nbits); + rc = p ? iobuf_write (out, p, (nbits+7)/8) : 0; + } + else + rc = gpg_error (GPG_ERR_BAD_MPI); + + return rc; +} + + +/* Calculate the length of a packet described by PKT. */ +u32 +calc_packet_length( PACKET *pkt ) +{ + u32 n = 0; + int new_ctb = 0; + + log_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 gpg_error_t +write_fake_data (IOBUF out, gcry_mpi_t a) +{ + unsigned int n; + void *p; + + if (!a) + return 0; + if (!gcry_mpi_get_flag (a, GCRYMPI_FLAG_OPAQUE)) + return 0; /* e.g. due to generating a key with wrong usage. */ + p = gcry_mpi_get_opaque ( a, &n); + if (!p) + return 0; /* For example due to a read error in + parse-packet.c:read_rest. */ + return iobuf_write (out, p, (n+7)/8 ); +} + + +/* Write a ring trust meta packet. */ +static gpg_error_t +do_ring_trust (iobuf_t out, PKT_ring_trust *rt) +{ + unsigned int namelen = 0; + unsigned int pktlen = 6; + + if (rt->subtype == RING_TRUST_KEY || rt->subtype == RING_TRUST_UID) + { + if (rt->url) + namelen = strlen (rt->url); + pktlen += 1 + 4 + 1 + namelen; + } + + write_header (out, (0x80 | ((PKT_RING_TRUST & 15)<<2)), pktlen); + iobuf_put (out, rt->trustval); + iobuf_put (out, rt->sigcache); + iobuf_write (out, "gpg", 3); + iobuf_put (out, rt->subtype); + if (rt->subtype == RING_TRUST_KEY || rt->subtype == RING_TRUST_UID) + { + iobuf_put (out, rt->keyorg); + write_32 (out, rt->keyupdate); + iobuf_put (out, namelen); + if (namelen) + iobuf_write (out, rt->url, namelen); + } + + return 0; +} + + +/* Serialize the user id (RFC 4880, Section 5.11) or the user + * attribute UID (Section 5.12) and write it to OUT. + * + * CTB is the serialization's CTB. It specifies the header format and + * the packet's type. The header length must not be set. */ +static int +do_user_id( IOBUF out, int ctb, PKT_user_id *uid ) +{ + int rc; + int hdrlen; + + log_assert (ctb_pkttype (ctb) == PKT_USER_ID + || ctb_pkttype (ctb) == PKT_ATTRIBUTE); + + /* We need to take special care of a user ID with a length of 0: + * Without forcing HDRLEN to 2 in this case an indeterminate length + * packet would be written which is not allowed. Note that we are + * always called with a CTB indicating an old packet header format, + * so that forcing a 2 octet header works. We also check for the + * maximum allowed packet size by the parser using an arbitrary + * extra 10 bytes for header data. */ + if (uid->attrib_data) + { + if (uid->attrib_len > MAX_ATTR_PACKET_LENGTH - 10) + return gpg_error (GPG_ERR_TOO_LARGE); + hdrlen = uid->attrib_len? 0 : 2; + write_header2 (out, ctb, uid->attrib_len, hdrlen); + rc = iobuf_write( out, uid->attrib_data, uid->attrib_len ); + } + else + { + if (uid->len > MAX_UID_PACKET_LENGTH - 10) + return gpg_error (GPG_ERR_TOO_LARGE); + hdrlen = uid->len? 0 : 2; + write_header2 (out, ctb, uid->len, hdrlen); + rc = iobuf_write( out, uid->name, uid->len ); + } + return rc; +} + + +/* Serialize the key (RFC 4880, Section 5.5) described by PK and write + * it to OUT. + * + * This function serializes both primary keys and subkeys with or + * without a secret part. + * + * CTB is the serialization's CTB. It specifies the header format and + * the packet's type. The header length must not be set. + * + * PK->VERSION specifies the serialization format. A value of 0 means + * to use the default version. Currently, only version 4 packets are + * supported. + */ +static int +do_key (iobuf_t out, int ctb, PKT_public_key *pk) +{ + gpg_error_t err = 0; + /* The length of the body is stored in the packet's header, which + occurs before the body. Unfortunately, we don't know the length + of the packet's body until we've written all of the data! To + work around this, we first write the data into this temporary + buffer, then generate the header, and finally copy the contents + of this buffer to OUT. */ + iobuf_t a = iobuf_temp(); + int i, nskey, npkey; + + log_assert (pk->version == 0 || pk->version == 4); + log_assert (ctb_pkttype (ctb) == PKT_PUBLIC_KEY + || ctb_pkttype (ctb) == PKT_PUBLIC_SUBKEY + || ctb_pkttype (ctb) == PKT_SECRET_KEY + || ctb_pkttype (ctb) == PKT_SECRET_SUBKEY); + + /* Write the version number - if none is specified, use 4 */ + if ( !pk->version ) + iobuf_put ( a, 4 ); + else + iobuf_put ( a, pk->version ); + write_32 (a, pk->timestamp ); + + iobuf_put (a, pk->pubkey_algo ); + + /* Get number of secret and public parameters. They are held in one + array: the public ones followed by the secret ones. */ + nskey = pubkey_get_nskey (pk->pubkey_algo); + npkey = pubkey_get_npkey (pk->pubkey_algo); + + /* If we don't have any public parameters - which is for example 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, pk->pkey[0]); + goto leave; + } + log_assert (npkey < nskey); + + for (i=0; i < npkey; i++ ) + { + if ( (pk->pubkey_algo == PUBKEY_ALGO_ECDSA && (i == 0)) + || (pk->pubkey_algo == PUBKEY_ALGO_EDDSA && (i == 0)) + || (pk->pubkey_algo == PUBKEY_ALGO_ECDH && (i == 0 || i == 2))) + err = gpg_mpi_write_nohdr (a, pk->pkey[i]); + else + err = gpg_mpi_write (a, pk->pkey[i]); + if (err) + goto leave; + } + + + if (pk->seckey_info) + { + /* This is a secret key packet. */ + struct seckey_info *ski = pk->seckey_info; + + /* Build the header for protected (encrypted) secret parameters. */ + if (ski->is_protected) + { + /* OpenPGP protection according to rfc2440. */ + iobuf_put (a, ski->sha1chk? 0xfe : 0xff); + iobuf_put (a, ski->algo); + if (ski->s2k.mode >= 1000) + { + /* These modes are not possible in OpenPGP, we use them + to implement our extensions, 101 can be viewed 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, ski->s2k.hash_algo); + iobuf_write (a, "GNU", 3 ); + iobuf_put (a, ski->s2k.mode - 1000); + } + else + { + iobuf_put (a, ski->s2k.mode); + iobuf_put (a, ski->s2k.hash_algo); + } + + if (ski->s2k.mode == 1 || ski->s2k.mode == 3) + iobuf_write (a, ski->s2k.salt, 8); + + if (ski->s2k.mode == 3) + iobuf_put (a, ski->s2k.count); + + /* For our special modes 1001, 1002 we do not need an IV. */ + if (ski->s2k.mode != 1001 && ski->s2k.mode != 1002) + iobuf_write (a, ski->iv, ski->ivlen); + + } + else /* Not protected. */ + iobuf_put (a, 0 ); + + if (ski->s2k.mode == 1001) + ; /* GnuPG extension - don't write a secret key at all. */ + else if (ski->s2k.mode == 1002) + { + /* GnuPG extension - divert to OpenPGP smartcard. */ + /* Length of the serial number or 0 for no serial number. */ + iobuf_put (a, ski->ivlen ); + /* The serial number gets stored in the IV field. */ + iobuf_write (a, ski->iv, ski->ivlen); + } + else if (ski->is_protected) + { + /* The secret key is protected - write it out as it is. */ + byte *p; + unsigned int ndatabits; + + log_assert (gcry_mpi_get_flag (pk->pkey[npkey], GCRYMPI_FLAG_OPAQUE)); + p = gcry_mpi_get_opaque (pk->pkey[npkey], &ndatabits); + if (p) + iobuf_write (a, p, (ndatabits+7)/8 ); + } + else + { + /* Non-protected key. */ + for ( ; i < nskey; i++ ) + if ( (err = gpg_mpi_write (a, pk->pkey[i]))) + goto leave; + write_16 (a, ski->csum ); + } + } + + leave: + if (!err) + { + /* 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), 0); + /* And finally write it out to the real stream. */ + err = iobuf_write_temp (out, a); + } + + iobuf_close (a); /* Close the temporary buffer */ + return err; +} + + +/* Serialize the symmetric-key encrypted session key packet (RFC 4880, + * 5.3) described by ENC and write it to OUT. + * + * CTB is the serialization's CTB. It specifies the header format and + * the packet's type. The header length must not be set. */ +static int +do_symkey_enc( IOBUF out, int ctb, PKT_symkey_enc *enc ) +{ + int rc = 0; + IOBUF a = iobuf_temp(); + + log_assert (ctb_pkttype (ctb) == PKT_SYMKEY_ENC); + + /* The only acceptable version. */ + log_assert( enc->version == 4 ); + + /* RFC 4880, Section 3.7. */ + switch (enc->s2k.mode) + { + case 0: /* Simple S2K. */ + case 1: /* Salted S2K. */ + case 3: /* Iterated and salted S2K. */ + break; /* Reasonable values. */ + + 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) ); + rc = iobuf_write_temp( out, a ); + + iobuf_close(a); + return rc; +} + + +/* Serialize the public-key encrypted session key packet (RFC 4880, + 5.1) described by ENC and write it to OUT. + + CTB is the serialization's CTB. It specifies the header format and + the packet's type. The header length must not be set. */ +static int +do_pubkey_enc( IOBUF out, int ctb, PKT_pubkey_enc *enc ) +{ + int rc = 0; + int n, i; + IOBUF a = iobuf_temp(); + + log_assert (ctb_pkttype (ctb) == PKT_PUBKEY_ENC); + + iobuf_put (a, 3); /* Version. */ + + 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 && !rc ; i++ ) + { + if (enc->pubkey_algo == PUBKEY_ALGO_ECDH && i == 1) + rc = gpg_mpi_write_nohdr (a, enc->data[i]); + else + rc = gpg_mpi_write (a, enc->data[i]); + } + + if (!rc) + { + write_header (out, ctb, iobuf_get_temp_length(a) ); + rc = iobuf_write_temp (out, a); + } + iobuf_close(a); + return rc; +} + + +/* Calculate the length of the serialized plaintext packet PT (RFC + 4480, Section 5.9). */ +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; +} + +/* Serialize the plaintext packet (RFC 4880, 5.9) described by PT and + write it to OUT. + + The body of the message is stored in PT->BUF. The amount of data + to write is PT->LEN. (PT->BUF should be configured to return EOF + after this much data has been read.) If PT->LEN is 0 and CTB + indicates that this is a new format packet, then partial block mode + is assumed to have been enabled on OUT. On success, partial block + mode is disabled. + + If PT->BUF is NULL, the caller must write out the data. In + this case, if PT->LEN was 0, then partial body length mode was + enabled and the caller must disable it by calling + iobuf_set_partial_body_length_mode (out, 0). */ +static int +do_plaintext( IOBUF out, int ctb, PKT_plaintext *pt ) +{ + int rc = 0; + size_t nbytes; + + log_assert (ctb_pkttype (ctb) == PKT_PLAINTEXT); + + write_header(out, ctb, calc_plaintext( pt ) ); + log_assert (pt->mode == 'b' || pt->mode == 't' || pt->mode == 'u' + || pt->mode == 'm' + || pt->mode == 'l' || pt->mode == '1'); + iobuf_put(out, pt->mode ); + iobuf_put(out, pt->namelen ); + iobuf_write (out, pt->name, pt->namelen); + rc = write_32(out, pt->timestamp ); + if (rc) + return rc; + + if (pt->buf) + { + nbytes = iobuf_copy (out, pt->buf); + if (nbytes == (size_t)(-1) + && (iobuf_error (out) || iobuf_error (pt->buf))) + return iobuf_error (out)? iobuf_error (out):iobuf_error (pt->buf); + if(ctb_new_format_p (ctb) && !pt->len) + /* Turn off partial body length mode. */ + iobuf_set_partial_body_length_mode (out, 0); + if( pt->len && nbytes != pt->len ) + log_error("do_plaintext(): wrote %lu bytes but expected %lu bytes\n", + (ulong)nbytes, (ulong)pt->len ); + } + + return rc; +} + + + +/* Serialize the symmetrically encrypted data packet (RFC 4880, + Section 5.7) described by ED and write it to OUT. + + Note: this only writes the packets header! The call must then + follow up and write the initial random data and the body to OUT. + (If you use the encryption iobuf filter (cipher_filter), then this + is done automatically.) */ +static int +do_encrypted( IOBUF out, int ctb, PKT_encrypted *ed ) +{ + int rc = 0; + u32 n; + + log_assert (! ed->mdc_method); + log_assert (ctb_pkttype (ctb) == PKT_ENCRYPTED); + + 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; +} + +/* Serialize the symmetrically encrypted integrity protected data + packet (RFC 4880, Section 5.13) described by ED and write it to + OUT. + + Note: this only writes the packet's header! The caller must then + follow up and write the initial random data, the body and the MDC + packet to OUT. (If you use the encryption iobuf filter + (cipher_filter), then this is done automatically.) */ +static int +do_encrypted_mdc( IOBUF out, int ctb, PKT_encrypted *ed ) +{ + int rc = 0; + u32 n; + + log_assert (ed->mdc_method); + log_assert (ctb_pkttype (ctb) == PKT_ENCRYPTED_MDC); + + /* 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; +} + + +/* Serialize the compressed packet (RFC 4880, Section 5.6) described + by CD and write it to OUT. + + Note: this only writes the packet's header! The caller must then + follow up and write the body to OUT. */ +static int +do_compressed( IOBUF out, int ctb, PKT_compressed *cd ) +{ + int rc = 0; + + log_assert (ctb_pkttype (ctb) == PKT_COMPRESSED); + + /* We must use the old convention and don't use blockmode for the + 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 = buf32_to_size_t (buffer); + 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"); + log_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(buf32_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. + * PKSK is the signing key. + * Hmmm, should we delete those subpackets which are in a wrong area? + */ +void +build_sig_subpkt_from_sig (PKT_signature *sig, PKT_public_key *pksk) +{ + u32 u; + byte buf[1+MAX_FINGERPRINT_LEN]; + size_t fprlen; + + /* For v4 keys we need to write the ISSUER subpacket. We do not + * want that for a future v5 format. */ + if (pksk->version < 5) + { + 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); + } + + /* Write the new ISSUER_FPR subpacket. */ + fingerprint_from_pk (pksk, buf+1, &fprlen); + if (fprlen == 20) + { + buf[0] = pksk->version; + build_sig_subpkt (sig, SIGSUBPKT_ISSUER_FPR, buf, 21); + } + + /* Write the timestamp. */ + 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; +} + +/* Returns a human-readable string corresponding to the notation. + This ignores notation->value. The caller must free the result. */ +static char * +notation_value_to_human_readable_string (struct notation *notation) +{ + if(notation->bdat) + /* Binary data. */ + { + size_t len = notation->blen; + int i; + char preview[20]; + + for (i = 0; i < len && i < sizeof (preview) - 1; i ++) + if (isprint (notation->bdat[i])) + preview[i] = notation->bdat[i]; + else + preview[i] = '?'; + preview[i] = 0; + + return xasprintf (_("[ not human readable (%zu bytes: %s%s) ]"), + len, preview, i < len ? "..." : ""); + } + else + /* The value is human-readable. */ + return xstrdup (notation->value); +} + +/* Turn the notation described by the string STRING into a notation. + + STRING has the form: + + - -name - Delete the notation. + - name@domain.name=value - Normal notation + - !name@domain.name=value - Notation with critical bit set. + + The caller must free the result using free_notation(). */ +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); + memcpy(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; +} + +/* Like string_to_notation, but store opaque data rather than human + readable data. */ +struct notation * +blob_to_notation(const char *name, const char *data, size_t len) +{ + const char *s; + int saw_at=0; + struct notation *notation; + + notation=xmalloc_clear(sizeof(*notation)); + + if(*name=='-') + { + notation->flags.ignore=1; + name++; + } + + if(*name=='!') + { + notation->flags.critical=1; + name++; + } + + /* If and when the IETF assigns some official name tags, we'll have + to add them here. */ + + for( s=name ; *s; s++ ) + { + if( *s=='@') + saw_at++; + + /* -notationname is legal without an = sign */ + if(!*s && notation->flags.ignore) + break; + + if (*s == '=') + { + log_error(_("a notation name may not contain an '=' character\n")); + goto fail; + } + + if (!isascii (*s) || (!isgraph(*s) && !isspace(*s))) + { + log_error(_("a notation name must have only printable characters" + " or spaces\n") ); + goto fail; + } + } + + notation->name=xstrdup (name); + + 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; + } + + notation->bdat = xmalloc (len); + memcpy (notation->bdat, data, len); + notation->blen = len; + + notation->value = notation_value_to_human_readable_string (notation); + + 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; + int crit; + notation_t list = NULL; + + /* See RFC 4880, 5.2.3.16 for the format of notation data. In + short, a notation has: + + - 4 bytes of flags + - 2 byte name length (n1) + - 2 byte value length (n2) + - n1 bytes of name data + - n2 bytes of value data + */ + 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; + } + + /* name length. */ + n1=(p[4]<<8)|p[5]; + /* value length. */ + 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) + /* The value is human-readable. */ + { + n->value=xmalloc(n2+1); + memcpy(n->value,&p[8+n1],n2); + n->value[n2]='\0'; + n->flags.human = 1; + } + else + /* Binary data. */ + { + n->bdat=xmalloc(n2); + n->blen=n2; + memcpy(n->bdat,&p[8+n1],n2); + + n->value = notation_value_to_human_readable_string (n); + } + + n->flags.critical=crit; + + n->next=list; + list=n; + } + + return list; +} + +/* Release the resources associated with the *list* of notations. To + release a single notation, make sure that notation->next is + NULL. */ +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); + } +} + +/* Serialize the signature packet (RFC 4880, Section 5.2) described by + SIG and write it to OUT. */ +static int +do_signature( IOBUF out, int ctb, PKT_signature *sig ) +{ + int rc = 0; + int n, i; + IOBUF a = iobuf_temp(); + + log_assert (ctb_pkttype (ctb) == PKT_SIGNATURE); + + if ( !sig->version || sig->version == 3) + { + iobuf_put( a, 3 ); + + /* Version 3 packets don't support subpackets. Actually we + * should never get to here but real life is different and thus + * we now use a log_fatal instead of a log_assert here. */ + if (sig->hashed || sig->unhashed) + log_fatal ("trying to write a subpacket to a v3 signature (%d,%d)\n", + !!sig->hashed, !!sig->unhashed); + } + 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 && !rc ; i++ ) + rc = gpg_mpi_write (a, sig->data[i] ); + + if (!rc) + { + 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) ); + rc = iobuf_write_temp( out, a ); + } + + iobuf_close(a); + return rc; +} + + +/* Serialize the one-pass signature packet (RFC 4880, Section 5.4) + described by OPS and write it to OUT. */ +static int +do_onepass_sig( IOBUF out, int ctb, PKT_onepass_sig *ops ) +{ + log_assert (ctb_pkttype (ctb) == PKT_ONEPASS_SIG); + + write_header(out, ctb, 4 + 8 + 1); + + iobuf_put (out, 3); /* Version. */ + iobuf_put(out, ops->sig_class ); + iobuf_put(out, ops->digest_algo ); + iobuf_put(out, ops->pubkey_algo ); + write_32(out, ops->keyid[0] ); + write_32(out, ops->keyid[1] ); + iobuf_put(out, ops->last ); + + return 0; +} + + +/* Write a 16-bit quantity to OUT in big endian order. */ +static int +write_16(IOBUF out, u16 a) +{ + iobuf_put(out, a>>8); + if( iobuf_put(out,a) ) + return -1; + return 0; +} + +/* Write a 32-bit quantity to OUT in big endian order. */ +static int +write_32(IOBUF out, u32 a) +{ + iobuf_put(out, a>> 24); + iobuf_put(out, a>> 16); + iobuf_put(out, a>> 8); + return iobuf_put(out, a); +} + + +/**************** + * calculate the length of a header. + * + * LEN is the length of the packet's body. NEW_CTB is whether we are + * using a new or old format packet. + * + * This function does not handle indeterminate lengths or partial body + * lengths. (If you pass LEN as 0, then this function assumes you + * really mean an empty body.) + */ +static int +calc_header_length( u32 len, int new_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) +{ + (void)ctb; + + /* 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; +} + +/**************** + * Write a packet header to OUT. + * + * CTB is the ctb. It determines whether a new or old format packet + * header should be written. The length field is adjusted, but the + * CTB is otherwise written out as is. + * + * LEN is the length of the packet's body. + * + * If HDRLEN is set, then we don't necessarily use the most efficient + * encoding to store LEN, but the specified length. (If this is not + * possible, this is a bug.) In this case, LEN=0 means a 0 length + * packet. Note: setting HDRLEN is only supported for old format + * packets! + * + * If HDRLEN is not set, then the shortest encoding is used. In this + * case, LEN=0 means the body has an indeterminate length and a + * partial body length header (if a new format packet) or an + * indeterminate length header (if an old format packet) is written + * out. Further, if using partial body lengths, this enables partial + * body length mode on OUT. + */ +static int +write_header2( IOBUF out, int ctb, u32 len, int hdrlen ) +{ + if (ctb_new_format_p (ctb)) + return write_new_header( out, ctb, len, hdrlen ); + + /* An old format packet. Refer to RFC 4880, Section 4.2.1 to + understand how lengths are encoded in this case. */ + + /* The length encoding is stored in the two least significant bits. + Make sure they are cleared. */ + log_assert ((ctb & 3) == 0); + + log_assert (hdrlen == 0 || hdrlen == 2 || hdrlen == 3 || hdrlen == 5); + + if (hdrlen) + /* Header length is given. */ + { + if( hdrlen == 2 && len < 256 ) + /* 00 => 1 byte length. */ + ; + else if( hdrlen == 3 && len < 65536 ) + /* 01 => 2 byte length. If len < 256, this is not the most + compact encoding, but it is a correct encoding. */ + ctb |= 1; + else if (hdrlen == 5) + /* 10 => 4 byte length. If len < 65536, this is not the most + compact encoding, but it is a correct encoding. */ + ctb |= 2; + else + log_bug ("Can't encode length=%d in a %d byte header!\n", + len, hdrlen); + } + else + { + if( !len ) + /* 11 => Indeterminate length. */ + ctb |= 3; + else if( len < 256 ) + /* 00 => 1 byte length. */ + ; + else if( len < 65536 ) + /* 01 => 2 byte length. */ + ctb |= 1; + else + /* 10 => 4 byte length. */ + 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; +} + + +/* Write a new format header to OUT. + + CTB is the ctb. + + LEN is the length of the packet's body. If LEN is 0, then enables + partial body length mode (i.e., the body is of an indeterminant + length) on OUT. Note: this function cannot be used to generate a + header for a zero length packet. + + HDRLEN is the length of the packet's header. If HDRLEN is 0, the + shortest encoding is chosen based on the length of the packet's + body. Currently, values other than 0 are not supported. + + Returns 0 on success. */ +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_body_length_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; +} diff --git a/g10/call-agent.c b/g10/call-agent.c new file mode 100644 index 0000000..7e60542 --- /dev/null +++ b/g10/call-agent.c @@ -0,0 +1,2702 @@ +/* call-agent.c - Divert GPG operations to the agent. + * Copyright (C) 2001-2003, 2006-2011, 2013 Free Software Foundation, Inc. + * Copyright (C) 2013-2015 Werner Koch + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#ifdef HAVE_LOCALE_H +#include +#endif + +#include "gpg.h" +#include +#include "../common/util.h" +#include "../common/membuf.h" +#include "options.h" +#include "../common/i18n.h" +#include "../common/asshelp.h" +#include "../common/sysutils.h" +#include "call-agent.h" +#include "../common/status.h" +#include "../common/shareddefs.h" +#include "../common/host2net.h" + +#define CONTROL_D ('D' - 'A' + 1) + + +static assuan_context_t agent_ctx = NULL; +static int did_early_card_test; + +struct default_inq_parm_s +{ + ctrl_t ctrl; + assuan_context_t ctx; + struct { + u32 *keyid; + u32 *mainkeyid; + int pubkey_algo; + } keyinfo; +}; + +struct cipher_parm_s +{ + struct default_inq_parm_s *dflt; + assuan_context_t ctx; + unsigned char *ciphertext; + size_t ciphertextlen; +}; + +struct writecert_parm_s +{ + struct default_inq_parm_s *dflt; + const unsigned char *certdata; + size_t certdatalen; +}; + +struct writekey_parm_s +{ + struct default_inq_parm_s *dflt; + const unsigned char *keydata; + size_t keydatalen; +}; + +struct genkey_parm_s +{ + struct default_inq_parm_s *dflt; + const char *keyparms; + const char *passphrase; +}; + +struct import_key_parm_s +{ + struct default_inq_parm_s *dflt; + const void *key; + size_t keylen; +}; + + +struct cache_nonce_parm_s +{ + char **cache_nonce_addr; + char **passwd_nonce_addr; +}; + + +static gpg_error_t learn_status_cb (void *opaque, const char *line); + + + +/* If RC is not 0, write an appropriate status message. */ +static void +status_sc_op_failure (int rc) +{ + switch (gpg_err_code (rc)) + { + case 0: + break; + case GPG_ERR_CANCELED: + case GPG_ERR_FULLY_CANCELED: + write_status_text (STATUS_SC_OP_FAILURE, "1"); + break; + case GPG_ERR_BAD_PIN: + write_status_text (STATUS_SC_OP_FAILURE, "2"); + break; + default: + write_status (STATUS_SC_OP_FAILURE); + break; + } +} + + +/* This is the default inquiry callback. It mainly handles the + Pinentry notifications. */ +static gpg_error_t +default_inq_cb (void *opaque, const char *line) +{ + gpg_error_t err = 0; + struct default_inq_parm_s *parm = opaque; + + if (has_leading_keyword (line, "PINENTRY_LAUNCHED")) + { + err = gpg_proxy_pinentry_notify (parm->ctrl, line); + if (err) + log_error (_("failed to proxy %s inquiry to client\n"), + "PINENTRY_LAUNCHED"); + /* We do not pass errors to avoid breaking other code. */ + } + else if ((has_leading_keyword (line, "PASSPHRASE") + || has_leading_keyword (line, "NEW_PASSPHRASE")) + && opt.pinentry_mode == PINENTRY_MODE_LOOPBACK) + { + if (have_static_passphrase ()) + { + const char *s = get_static_passphrase (); + err = assuan_send_data (parm->ctx, s, strlen (s)); + } + else + { + char *pw; + char buf[32]; + + if (parm->keyinfo.keyid) + emit_status_need_passphrase (parm->ctrl, + parm->keyinfo.keyid, + parm->keyinfo.mainkeyid, + parm->keyinfo.pubkey_algo); + + snprintf (buf, sizeof (buf), "%u", 100); + write_status_text (STATUS_INQUIRE_MAXLEN, buf); + pw = cpr_get_hidden ("passphrase.enter", _("Enter passphrase: ")); + cpr_kill_prompt (); + if (*pw == CONTROL_D && !pw[1]) + err = gpg_error (GPG_ERR_CANCELED); + else + err = assuan_send_data (parm->ctx, pw, strlen (pw)); + xfree (pw); + } + } + else + log_debug ("ignoring gpg-agent inquiry '%s'\n", line); + + return err; +} + + +/* Print a warning if the server's version number is less than our + version number. Returns an error code on a connection problem. */ +static gpg_error_t +warn_version_mismatch (assuan_context_t ctx, const char *servername, int mode) +{ + gpg_error_t err; + char *serverversion; + const char *myversion = strusage (13); + + err = get_assuan_server_version (ctx, mode, &serverversion); + if (err) + log_log (gpg_err_code (err) == GPG_ERR_NOT_SUPPORTED? + GPGRT_LOG_INFO : GPGRT_LOG_ERROR, + _("error getting version from '%s': %s\n"), + servername, gpg_strerror (err)); + else if (compare_version_strings (serverversion, myversion) < 0) + { + char *warn; + + warn = xtryasprintf (_("server '%s' is older than us (%s < %s)"), + servername, serverversion, myversion); + if (!warn) + err = gpg_error_from_syserror (); + else + { + log_info (_("WARNING: %s\n"), warn); + if (!opt.quiet) + { + log_info (_("Note: Outdated servers may lack important" + " security fixes.\n")); + log_info (_("Note: Use the command \"%s\" to restart them.\n"), + "gpgconf --kill all"); + } + write_status_strings (STATUS_WARNING, "server_version_mismatch 0", + " ", warn, NULL); + xfree (warn); + } + } + xfree (serverversion); + return err; +} + + +#define FLAG_FOR_CARD_SUPPRESS_ERRORS 2 + +/* Try to connect to the agent via socket or fork it off and work by + pipes. Handle the server's initial greeting */ +static int +start_agent (ctrl_t ctrl, int flag_for_card) +{ + int rc; + + (void)ctrl; /* Not yet used. */ + + /* Fixme: We need a context for each thread or serialize the access + to the agent. */ + if (agent_ctx) + rc = 0; + else + { + rc = start_new_gpg_agent (&agent_ctx, + GPG_ERR_SOURCE_DEFAULT, + opt.agent_program, + opt.lc_ctype, opt.lc_messages, + opt.session_env, + opt.autostart, opt.verbose, DBG_IPC, + NULL, NULL); + if (!opt.autostart && gpg_err_code (rc) == GPG_ERR_NO_AGENT) + { + static int shown; + + if (!shown) + { + shown = 1; + log_info (_("no gpg-agent running in this session\n")); + } + } + else if (!rc + && !(rc = warn_version_mismatch (agent_ctx, GPG_AGENT_NAME, 0))) + { + /* Tell the agent that we support Pinentry notifications. + No error checking so that it will work also with older + agents. */ + assuan_transact (agent_ctx, "OPTION allow-pinentry-notify", + NULL, NULL, NULL, NULL, NULL, NULL); + /* Tell the agent about what version we are aware. This is + here used to indirectly enable GPG_ERR_FULLY_CANCELED. */ + assuan_transact (agent_ctx, "OPTION agent-awareness=2.1.0", + NULL, NULL, NULL, NULL, NULL, NULL); + /* Pass on the pinentry mode. */ + if (opt.pinentry_mode) + { + char *tmp = xasprintf ("OPTION pinentry-mode=%s", + str_pinentry_mode (opt.pinentry_mode)); + rc = assuan_transact (agent_ctx, tmp, + NULL, NULL, NULL, NULL, NULL, NULL); + xfree (tmp); + if (rc) + { + log_error ("setting pinentry mode '%s' failed: %s\n", + str_pinentry_mode (opt.pinentry_mode), + gpg_strerror (rc)); + write_status_error ("set_pinentry_mode", rc); + } + } + + /* Pass on the request origin. */ + if (opt.request_origin) + { + char *tmp = xasprintf ("OPTION pretend-request-origin=%s", + str_request_origin (opt.request_origin)); + rc = assuan_transact (agent_ctx, tmp, + NULL, NULL, NULL, NULL, NULL, NULL); + xfree (tmp); + if (rc) + { + log_error ("setting request origin '%s' failed: %s\n", + str_request_origin (opt.request_origin), + gpg_strerror (rc)); + write_status_error ("set_request_origin", rc); + } + } + + /* In DE_VS mode under Windows we require that the JENT RNG + * is active. */ +#ifdef HAVE_W32_SYSTEM + if (!rc && opt.compliance == CO_DE_VS) + { + if (assuan_transact (agent_ctx, "GETINFO jent_active", + NULL, NULL, NULL, NULL, NULL, NULL)) + { + rc = gpg_error (GPG_ERR_FORBIDDEN); + log_error (_("%s is not compliant with %s mode\n"), + GPG_AGENT_NAME, + gnupg_compliance_option_string (opt.compliance)); + write_status_error ("random-compliance", rc); + } + } +#endif /*HAVE_W32_SYSTEM*/ + + } + } + + if (!rc && flag_for_card && !did_early_card_test) + { + /* Request the serial number of the card for an early test. */ + struct agent_card_info_s info; + + memset (&info, 0, sizeof info); + + if (!(flag_for_card & FLAG_FOR_CARD_SUPPRESS_ERRORS)) + rc = warn_version_mismatch (agent_ctx, SCDAEMON_NAME, 2); + if (!rc) + rc = assuan_transact (agent_ctx, + opt.flags.use_only_openpgp_card? + "SCD SERIALNO openpgp" : "SCD SERIALNO", + NULL, NULL, NULL, NULL, + learn_status_cb, &info); + if (rc && !(flag_for_card & FLAG_FOR_CARD_SUPPRESS_ERRORS)) + { + switch (gpg_err_code (rc)) + { + case GPG_ERR_NOT_SUPPORTED: + case GPG_ERR_NO_SCDAEMON: + write_status_text (STATUS_CARDCTRL, "6"); + break; + case GPG_ERR_OBJ_TERM_STATE: + write_status_text (STATUS_CARDCTRL, "7"); + break; + default: + write_status_text (STATUS_CARDCTRL, "4"); + log_info ("selecting card failed: %s\n", gpg_strerror (rc)); + break; + } + } + + if (!rc && is_status_enabled () && info.serialno) + { + char *buf; + + buf = xasprintf ("3 %s", info.serialno); + write_status_text (STATUS_CARDCTRL, buf); + xfree (buf); + } + + agent_release_card_info (&info); + + if (!rc) + did_early_card_test = 1; + } + + + return rc; +} + + +/* 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. Function returns NULL to indicate + an out of memory status. */ +static char * +unescape_status_string (const unsigned char *s) +{ + return percent_plus_unescape (s, 0xff); +} + + +/* Take a 20 byte hexencoded string and put it into the provided + 20 byte buffer FPR in binary format. */ +static int +unhexify_fpr (const char *hexstr, unsigned char *fpr) +{ + const char *s; + int n; + + for (s=hexstr, n=0; hexdigitp (s); s++, n++) + ; + if ((*s && *s != ' ') || (n != 40)) + return 0; /* no fingerprint (invalid or wrong length). */ + for (s=hexstr, n=0; *s && n < 20; 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 = xtrymalloc (s + 1 - line); + if (p) + { + memcpy (p, line, s-line); + p[s-line] = 0; + } + return p; +} + + + +/* This is a dummy data line callback. */ +static gpg_error_t +dummy_data_cb (void *opaque, const void *buffer, size_t length) +{ + (void)opaque; + (void)buffer; + (void)length; + return 0; +} + +/* A simple callback used to return the serialnumber of a card. */ +static gpg_error_t +get_serialno_cb (void *opaque, const char *line) +{ + char **serialno = opaque; + const char *keyword = line; + const char *s; + int keywordlen, n; + + for (keywordlen=0; *line && !spacep (line); line++, keywordlen++) + ; + while (spacep (line)) + line++; + + if (keywordlen == 8 && !memcmp (keyword, "SERIALNO", keywordlen)) + { + if (*serialno) + return gpg_error (GPG_ERR_CONFLICT); /* Unexpected status line. */ + for (n=0,s=line; hexdigitp (s); s++, n++) + ; + if (!n || (n&1)|| !(spacep (s) || !*s) ) + return gpg_error (GPG_ERR_ASS_PARAMETER); + *serialno = xtrymalloc (n+1); + if (!*serialno) + return out_of_core (); + memcpy (*serialno, line, n); + (*serialno)[n] = 0; + } + + return 0; +} + + + +/* Release the card info structure INFO. */ +void +agent_release_card_info (struct agent_card_info_s *info) +{ + int i; + + if (!info) + return; + + xfree (info->reader); info->reader = NULL; + xfree (info->manufacturer_name); info->manufacturer_name = NULL; + xfree (info->serialno); info->serialno = NULL; + xfree (info->apptype); info->apptype = 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->cafpr1valid = info->cafpr2valid = info->cafpr3valid = 0; + info->fpr1valid = info->fpr2valid = info->fpr3valid = 0; + for (i=0; i < DIM(info->private_do); i++) + { + xfree (info->private_do[i]); + info->private_do[i] = NULL; + } +} + + +static gpg_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; + char *endp; + + for (keywordlen=0; *line && !spacep (line); line++, keywordlen++) + ; + while (spacep (line)) + line++; + + if (keywordlen == 6 && !memcmp (keyword, "READER", keywordlen)) + { + xfree (parm->reader); + parm->reader = unescape_status_string (line); + } + else if (keywordlen == 8 && !memcmp (keyword, "SERIALNO", keywordlen)) + { + xfree (parm->serialno); + parm->serialno = store_serialno (line); + parm->is_v2 = (strlen (parm->serialno) >= 16 + && xtoi_2 (parm->serialno+12) >= 2 ); + } + else if (keywordlen == 7 && !memcmp (keyword, "APPTYPE", keywordlen)) + { + xfree (parm->apptype); + parm->apptype = unescape_status_string (line); + } + else if (keywordlen == 9 && !memcmp (keyword, "DISP-NAME", keywordlen)) + { + xfree (parm->disp_name); + parm->disp_name = unescape_status_string (line); + } + else if (keywordlen == 9 && !memcmp (keyword, "DISP-LANG", keywordlen)) + { + xfree (parm->disp_lang); + parm->disp_lang = unescape_status_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_status_string (line); + } + else if (keywordlen == 10 && !memcmp (keyword, "LOGIN-DATA", keywordlen)) + { + xfree (parm->login_data); + parm->login_data = unescape_status_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_status_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 == 6 && !memcmp (keyword, "EXTCAP", keywordlen)) + { + char *p, *p2, *buf; + int abool; + + buf = p = unescape_status_string (line); + if (buf) + { + for (p = strtok (buf, " "); p; p = strtok (NULL, " ")) + { + p2 = strchr (p, '='); + if (p2) + { + *p2++ = 0; + abool = (*p2 == '1'); + if (!strcmp (p, "ki")) + parm->extcap.ki = abool; + else if (!strcmp (p, "aac")) + parm->extcap.aac = abool; + else if (!strcmp (p, "kdf")) + parm->extcap.kdf = abool; + else if (!strcmp (p, "si")) + parm->status_indicator = strtoul (p2, NULL, 10); + } + } + 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 == 11 && !memcmp (keyword, "KEYPAIRINFO", keywordlen)) + { + const char *hexgrp = line; + int no; + + while (*line && !spacep (line)) + line++; + while (spacep (line)) + line++; + if (strncmp (line, "OPENPGP.", 8)) + ; + else if ((no = atoi (line+8)) == 1) + unhexify_fpr (hexgrp, parm->grp1); + else if (no == 2) + unhexify_fpr (hexgrp, parm->grp2); + else if (no == 3) + unhexify_fpr (hexgrp, parm->grp3); + } + 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 == 8 && !memcmp (keyword, "KEY-ATTR", keywordlen)) + { + int keyno = 0; + int algo = PUBKEY_ALGO_RSA; + int n = 0; + + sscanf (line, "%d %d %n", &keyno, &algo, &n); + keyno--; + if (keyno < 0 || keyno >= DIM (parm->key_attr)) + return 0; + + parm->key_attr[keyno].algo = algo; + if (algo == PUBKEY_ALGO_RSA) + parm->key_attr[keyno].nbits = strtoul (line+n+3, NULL, 10); + else if (algo == PUBKEY_ALGO_ECDH || algo == PUBKEY_ALGO_ECDSA + || algo == PUBKEY_ALGO_EDDSA) + parm->key_attr[keyno].curve = openpgp_is_curve_supported (line + n, + NULL, NULL); + } + else if (keywordlen == 12 && !memcmp (keyword, "PRIVATE-DO-", 11) + && strchr("1234", keyword[11])) + { + int no = keyword[11] - '1'; + log_assert (no >= 0 && no <= 3); + xfree (parm->private_do[no]); + parm->private_do[no] = unescape_status_string (line); + } + else if (keywordlen == 12 && !memcmp (keyword, "MANUFACTURER", 12)) + { + xfree (parm->manufacturer_name); + parm->manufacturer_name = NULL; + parm->manufacturer_id = strtoul (line, &endp, 0); + while (endp && spacep (endp)) + endp++; + if (endp && *endp) + parm->manufacturer_name = xstrdup (endp); + } + else if (keywordlen == 3 && !memcmp (keyword, "KDF", 3)) + { + unsigned char *data = unescape_status_string (line); + + if (data[2] != 0x03) + parm->kdf_do_enabled = 0; + else if (data[22] != 0x85) + parm->kdf_do_enabled = 1; + else + parm->kdf_do_enabled = 2; + xfree (data); + } + + return 0; +} + + +/* Call the scdaemon to learn about a smartcard. Note that in + * contradiction to the function's name, gpg-agent's LEARN command is + * used and not the low-level "SCD LEARN". + * Used by: + * card-util.c + * keyedit_menu + * card_store_key_with_backup (Woth force to remove secret key data) + */ +int +agent_scd_learn (struct agent_card_info_s *info, int force) +{ + int rc; + struct default_inq_parm_s parm; + struct agent_card_info_s dummyinfo; + + if (!info) + info = &dummyinfo; + memset (info, 0, sizeof *info); + memset (&parm, 0, sizeof parm); + + rc = start_agent (NULL, 1); + if (rc) + return rc; + + parm.ctx = agent_ctx; + rc = assuan_transact (agent_ctx, + force ? "LEARN --sendinfo --force" : "LEARN --sendinfo", + dummy_data_cb, NULL, default_inq_cb, &parm, + learn_status_cb, info); + /* Also try to get the key attributes. */ + if (!rc) + agent_scd_getattr ("KEY-ATTR", info); + + if (info == &dummyinfo) + agent_release_card_info (info); + + return rc; +} + + + +/* Callback for the agent_scd_keypairinfo function. */ +static gpg_error_t +scd_keypairinfo_status_cb (void *opaque, const char *line) +{ + strlist_t *listaddr = opaque; + const char *keyword = line; + int keywordlen; + strlist_t sl; + char *p; + + for (keywordlen=0; *line && !spacep (line); line++, keywordlen++) + ; + while (spacep (line)) + line++; + + if (keywordlen == 11 && !memcmp (keyword, "KEYPAIRINFO", keywordlen)) + { + sl = append_to_strlist (listaddr, line); + p = sl->d; + /* Make sure that we only have two tokens so that future + * extensions of the format won't change the format expected by + * the caller. */ + while (*p && !spacep (p)) + p++; + if (*p) + { + while (spacep (p)) + p++; + while (*p && !spacep (p)) + p++; + if (*p) + { + *p++ = 0; + while (spacep (p)) + p++; + while (*p && !spacep (p)) + { + switch (*p++) + { + case 'c': sl->flags |= GCRY_PK_USAGE_CERT; break; + case 's': sl->flags |= GCRY_PK_USAGE_SIGN; break; + case 'e': sl->flags |= GCRY_PK_USAGE_ENCR; break; + case 'a': sl->flags |= GCRY_PK_USAGE_AUTH; break; + } + } + } + } + } + + return 0; +} + + +/* Read the keypairinfo lines of the current card directly from + * scdaemon. The list is returned as a string made up of the keygrip, + * a space and the keyref. The flags of the string carry the usage + * bits. */ +gpg_error_t +agent_scd_keypairinfo (ctrl_t ctrl, strlist_t *r_list) +{ + gpg_error_t err; + strlist_t list = NULL; + struct default_inq_parm_s inq_parm; + + *r_list = NULL; + err= start_agent (ctrl, 1); + if (err) + return err; + memset (&inq_parm, 0, sizeof inq_parm); + inq_parm.ctx = agent_ctx; + + err = assuan_transact (agent_ctx, "SCD LEARN --keypairinfo", + NULL, NULL, + default_inq_cb, &inq_parm, + scd_keypairinfo_status_cb, &list); + if (!err && !list) + err = gpg_error (GPG_ERR_NO_DATA); + if (err) + { + free_strlist (list); + return err; + } + *r_list = list; + return 0; +} + + + +/* Send an APDU to the current card. On success the status word is + * stored at R_SW. With HEXAPDU being NULL only a RESET command is + * send to scd. HEXAPDU may also be one of these special strings: + * + * "undefined" :: Send the command "SCD SERIALNO undefined" + * "lock" :: Send the command "SCD LOCK --wait" + * "trylock" :: Send the command "SCD LOCK" + * "unlock" :: Send the command "SCD UNLOCK" + * "reset-keep-lock" :: Send the command "SCD RESET --keep-lock" + * + * Used by: + * card-util.c + */ +gpg_error_t +agent_scd_apdu (const char *hexapdu, unsigned int *r_sw) +{ + gpg_error_t err; + + /* Start the agent but not with the card flag so that we do not + autoselect the openpgp application. */ + err = start_agent (NULL, 0); + if (err) + return err; + + if (!hexapdu) + { + err = assuan_transact (agent_ctx, "SCD RESET", + NULL, NULL, NULL, NULL, NULL, NULL); + + } + else if (!strcmp (hexapdu, "reset-keep-lock")) + { + err = assuan_transact (agent_ctx, "SCD RESET --keep-lock", + NULL, NULL, NULL, NULL, NULL, NULL); + } + else if (!strcmp (hexapdu, "lock")) + { + err = assuan_transact (agent_ctx, "SCD LOCK --wait", + NULL, NULL, NULL, NULL, NULL, NULL); + } + else if (!strcmp (hexapdu, "trylock")) + { + err = assuan_transact (agent_ctx, "SCD LOCK", + NULL, NULL, NULL, NULL, NULL, NULL); + } + else if (!strcmp (hexapdu, "unlock")) + { + err = assuan_transact (agent_ctx, "SCD UNLOCK", + NULL, NULL, NULL, NULL, NULL, NULL); + } + else if (!strcmp (hexapdu, "undefined")) + { + err = assuan_transact (agent_ctx, "SCD SERIALNO undefined", + NULL, NULL, NULL, NULL, NULL, NULL); + } + else + { + char line[ASSUAN_LINELENGTH]; + membuf_t mb; + unsigned char *data; + size_t datalen; + + init_membuf (&mb, 256); + + snprintf (line, DIM(line), "SCD APDU %s", hexapdu); + err = assuan_transact (agent_ctx, line, + put_membuf_cb, &mb, NULL, NULL, NULL, NULL); + if (!err) + { + data = get_membuf (&mb, &datalen); + if (!data) + err = gpg_error_from_syserror (); + else if (datalen < 2) /* Ooops */ + err = gpg_error (GPG_ERR_CARD); + else + { + *r_sw = buf16_to_uint (data+datalen-2); + } + xfree (data); + } + } + + return err; +} + + +/* Used by: + * card_store_subkey + * card_store_key_with_backup + */ +int +agent_keytocard (const char *hexgrip, int keyno, int force, + const char *serialno, const char *timestamp) +{ + int rc; + char line[ASSUAN_LINELENGTH]; + struct default_inq_parm_s parm; + + memset (&parm, 0, sizeof parm); + + snprintf (line, DIM(line), "KEYTOCARD %s%s %s OPENPGP.%d %s", + force?"--force ": "", hexgrip, serialno, keyno, timestamp); + + rc = start_agent (NULL, 1); + if (rc) + return rc; + parm.ctx = agent_ctx; + + rc = assuan_transact (agent_ctx, line, NULL, NULL, default_inq_cb, &parm, + NULL, NULL); + status_sc_op_failure (rc); + return rc; +} + +/* Object used with the agent_scd_getattr_one. */ +struct getattr_one_parm_s { + const char *keyword; /* Keyword to look for. */ + char *data; /* Malloced and unescaped data. */ + gpg_error_t err; /* Error code or 0 on success. */ +}; + + +/* Callback for agent_scd_getattr_one. */ +static gpg_error_t +getattr_one_status_cb (void *opaque, const char *line) +{ + struct getattr_one_parm_s *parm = opaque; + const char *s; + + if (parm->data) + return 0; /* We want only the first occurrence. */ + + if ((s=has_leading_keyword (line, parm->keyword))) + { + parm->data = percent_plus_unescape (s, 0xff); + if (!parm->data) + parm->err = gpg_error_from_syserror (); + } + + return 0; +} + + +/* Simplified version of agent_scd_getattr. This function returns + * only the first occurance of the attribute NAME and stores it at + * R_VALUE. A nul in the result is silennly replaced by 0xff. On + * error NULL is stored at R_VALUE. */ +gpg_error_t +agent_scd_getattr_one (const char *name, char **r_value) +{ + gpg_error_t err; + char line[ASSUAN_LINELENGTH]; + struct default_inq_parm_s inqparm; + struct getattr_one_parm_s parm; + + *r_value = NULL; + + if (!*name) + return gpg_error (GPG_ERR_INV_VALUE); + + memset (&inqparm, 0, sizeof inqparm); + inqparm.ctx = agent_ctx; + + memset (&parm, 0, sizeof parm); + parm.keyword = name; + + /* We assume that NAME does not need escaping. */ + if (12 + strlen (name) > DIM(line)-1) + return gpg_error (GPG_ERR_TOO_LARGE); + stpcpy (stpcpy (line, "SCD GETATTR "), name); + + err = start_agent (NULL, 1); + if (err) + return err; + + err = assuan_transact (agent_ctx, line, + NULL, NULL, + default_inq_cb, &inqparm, + getattr_one_status_cb, &parm); + if (!err && parm.err) + err = parm.err; + else if (!err && !parm.data) + err = gpg_error (GPG_ERR_NO_DATA); + + if (!err) + *r_value = parm.data; + else + xfree (parm.data); + + return err; +} + + + +/* Call the agent to retrieve a data object. This function returns + * the data in the same structure as used by the learn command. It is + * allowed to update such a structure using this command. + * + * Used by: + * build_sk_list + * enum_secret_keys + * get_signature_count + * card-util.c + * generate_keypair (KEY-ATTR) + * card_store_key_with_backup (SERIALNO) + * generate_card_subkeypair (KEY-ATTR) + */ +int +agent_scd_getattr (const char *name, struct agent_card_info_s *info) +{ + int rc; + char line[ASSUAN_LINELENGTH]; + struct default_inq_parm_s parm; + + memset (&parm, 0, sizeof parm); + + if (!*name) + return gpg_error (GPG_ERR_INV_VALUE); + + /* We assume that NAME does not need escaping. */ + if (12 + strlen (name) > DIM(line)-1) + return gpg_error (GPG_ERR_TOO_LARGE); + stpcpy (stpcpy (line, "SCD GETATTR "), name); + + rc = start_agent (NULL, 1); + if (rc) + return rc; + + parm.ctx = agent_ctx; + rc = assuan_transact (agent_ctx, line, NULL, NULL, default_inq_cb, &parm, + learn_status_cb, info); + if (!rc && !strcmp (name, "KEY-FPR")) + { + /* Let the agent create the shadow keys if not yet done. */ + if (info->fpr1valid) + assuan_transact (agent_ctx, "READKEY --card --no-data -- $SIGNKEYID", + NULL, NULL, NULL, NULL, NULL, NULL); + if (info->fpr2valid) + assuan_transact (agent_ctx, "READKEY --card --no-data -- $ENCRKEYID", + NULL, NULL, NULL, NULL, NULL, NULL); + if (info->fpr3valid) + assuan_transact (agent_ctx, "READKEY --card --no-data -- $AUTHKEYID", + NULL, NULL, NULL, NULL, NULL, NULL); + } + + return rc; +} + + + +/* Send an setattr command to the SCdaemon. + * Used by: + * card-util.c + */ +gpg_error_t +agent_scd_setattr (const char *name, const void *value_arg, size_t valuelen) +{ + gpg_error_t err; + const unsigned char *value = value_arg; + char line[ASSUAN_LINELENGTH]; + char *p; + struct default_inq_parm_s parm; + + memset (&parm, 0, sizeof parm); + + if (!*name || !valuelen) + return gpg_error (GPG_ERR_INV_VALUE); + + /* We assume that NAME does not need escaping. */ + if (12 + strlen (name) > DIM(line)-1) + return gpg_error (GPG_ERR_TOO_LARGE); + + p = stpcpy (stpcpy (line, "SCD SETATTR "), name); + *p++ = ' '; + for (; valuelen; value++, valuelen--) + { + if (p >= line + DIM(line)-5 ) + return gpg_error (GPG_ERR_TOO_LARGE); + if (*value < ' ' || *value == '+' || *value == '%') + { + sprintf (p, "%%%02X", *value); + p += 3; + } + else if (*value == ' ') + *p++ = '+'; + else + *p++ = *value; + } + *p = 0; + + err = start_agent (NULL, 1); + if (!err) + { + parm.ctx = agent_ctx; + err = assuan_transact (agent_ctx, line, NULL, NULL, + default_inq_cb, &parm, NULL, NULL); + } + + status_sc_op_failure (err); + return err; +} + + + +/* Handle a CERTDATA inquiry. Note, we only send the data, + assuan_transact takes care of flushing and writing the END + command. */ +static gpg_error_t +inq_writecert_parms (void *opaque, const char *line) +{ + int rc; + struct writecert_parm_s *parm = opaque; + + if (has_leading_keyword (line, "CERTDATA")) + { + rc = assuan_send_data (parm->dflt->ctx, + parm->certdata, parm->certdatalen); + } + else + rc = default_inq_cb (parm->dflt, line); + + return rc; +} + + +/* Send a WRITECERT command to the SCdaemon. + * Used by: + * card-util.c + */ +int +agent_scd_writecert (const char *certidstr, + const unsigned char *certdata, size_t certdatalen) +{ + int rc; + char line[ASSUAN_LINELENGTH]; + struct writecert_parm_s parms; + struct default_inq_parm_s dfltparm; + + memset (&dfltparm, 0, sizeof dfltparm); + + rc = start_agent (NULL, 1); + if (rc) + return rc; + + memset (&parms, 0, sizeof parms); + + snprintf (line, DIM(line), "SCD WRITECERT %s", certidstr); + dfltparm.ctx = agent_ctx; + parms.dflt = &dfltparm; + parms.certdata = certdata; + parms.certdatalen = certdatalen; + + rc = assuan_transact (agent_ctx, line, NULL, NULL, + inq_writecert_parms, &parms, NULL, NULL); + + return rc; +} + + + +/* Status callback for the SCD GENKEY command. */ +static gpg_error_t +scd_genkey_cb (void *opaque, const char *line) +{ + u32 *createtime = opaque; + const char *keyword = line; + int keywordlen; + + for (keywordlen=0; *line && !spacep (line); line++, keywordlen++) + ; + while (spacep (line)) + line++; + + if (keywordlen == 14 && !memcmp (keyword,"KEY-CREATED-AT", keywordlen)) + { + *createtime = (u32)strtoul (line, NULL, 10); + } + else if (keywordlen == 8 && !memcmp (keyword, "PROGRESS", keywordlen)) + { + write_status_text (STATUS_PROGRESS, line); + } + + return 0; +} + +/* Send a GENKEY command to the SCdaemon. If *CREATETIME is not 0, + * the value will be passed to SCDAEMON with --timestamp option so that + * the key is created with this. Otherwise, timestamp was generated by + * SCDEAMON. On success, creation time is stored back to + * CREATETIME. + * Used by: + * gen_card_key + */ +int +agent_scd_genkey (int keyno, int force, u32 *createtime) +{ + int rc; + char line[ASSUAN_LINELENGTH]; + gnupg_isotime_t tbuf; + struct default_inq_parm_s dfltparm; + + memset (&dfltparm, 0, sizeof dfltparm); + + rc = start_agent (NULL, 1); + if (rc) + return rc; + + if (*createtime) + epoch2isotime (tbuf, *createtime); + else + *tbuf = 0; + + snprintf (line, DIM(line), "SCD GENKEY %s%s %s %d", + *tbuf? "--timestamp=":"", tbuf, + force? "--force":"", + keyno); + + dfltparm.ctx = agent_ctx; + rc = assuan_transact (agent_ctx, line, + NULL, NULL, default_inq_cb, &dfltparm, + scd_genkey_cb, createtime); + + status_sc_op_failure (rc); + return rc; +} + + + +/* Return the serial number of the card or an appropriate error. The + * serial number is returned as a hexstring. With DEMAND the active + * card is switched to the card with that serialno. + * Used by: + * card-util.c + * build_sk_list + * enum_secret_keys + */ +int +agent_scd_serialno (char **r_serialno, const char *demand) +{ + int err; + char *serialno = NULL; + char line[ASSUAN_LINELENGTH]; + + err = start_agent (NULL, (1 | FLAG_FOR_CARD_SUPPRESS_ERRORS)); + if (err) + return err; + + if (!demand) + strcpy (line, "SCD SERIALNO"); + else + snprintf (line, DIM(line), "SCD SERIALNO --demand=%s", demand); + + err = assuan_transact (agent_ctx, line, + NULL, NULL, NULL, NULL, + get_serialno_cb, &serialno); + if (err) + { + xfree (serialno); + return err; + } + + *r_serialno = serialno; + return 0; +} + + + +/* Send a READCERT command to the SCdaemon. + * Used by: + * card-util.c + */ +int +agent_scd_readcert (const char *certidstr, + void **r_buf, size_t *r_buflen) +{ + int rc; + char line[ASSUAN_LINELENGTH]; + membuf_t data; + size_t len; + struct default_inq_parm_s dfltparm; + + memset (&dfltparm, 0, sizeof dfltparm); + + *r_buf = NULL; + rc = start_agent (NULL, 1); + if (rc) + return rc; + + dfltparm.ctx = agent_ctx; + + init_membuf (&data, 2048); + + snprintf (line, DIM(line), "SCD READCERT %s", certidstr); + rc = assuan_transact (agent_ctx, line, + put_membuf_cb, &data, + default_inq_cb, &dfltparm, + NULL, NULL); + if (rc) + { + xfree (get_membuf (&data, &len)); + return rc; + } + *r_buf = get_membuf (&data, r_buflen); + if (!*r_buf) + return gpg_error (GPG_ERR_ENOMEM); + + return 0; +} + + +/* This is a variant of agent_readkey which sends a READKEY command + * directly Scdaemon. On success a new s-expression is stored at + * R_RESULT. */ +gpg_error_t +agent_scd_readkey (const char *keyrefstr, gcry_sexp_t *r_result) +{ + gpg_error_t err; + char line[ASSUAN_LINELENGTH]; + membuf_t data; + unsigned char *buf; + size_t len, buflen; + struct default_inq_parm_s dfltparm; + + memset (&dfltparm, 0, sizeof dfltparm); + dfltparm.ctx = agent_ctx; + + *r_result = NULL; + err = start_agent (NULL, 1); + if (err) + return err; + + init_membuf (&data, 1024); + snprintf (line, DIM(line), "SCD READKEY %s", keyrefstr); + err = assuan_transact (agent_ctx, line, + put_membuf_cb, &data, + default_inq_cb, &dfltparm, + NULL, NULL); + if (err) + { + xfree (get_membuf (&data, &len)); + return err; + } + buf = get_membuf (&data, &buflen); + if (!buf) + return gpg_error_from_syserror (); + + err = gcry_sexp_new (r_result, buf, buflen, 0); + xfree (buf); + + return err; +} + + +/* This can be called for a quick and dirty update/creation of the + * shadow key stubs. */ +gpg_error_t +agent_update_shadow_keys (void) +{ + gpg_error_t err; + + err = start_agent (NULL, 1); + if (err) + return err; + + assuan_transact (agent_ctx, "READKEY --card --no-data -- $SIGNKEYID", + NULL, NULL, NULL, NULL, NULL, NULL); + assuan_transact (agent_ctx, "READKEY --card --no-data -- $ENCRKEYID", + NULL, NULL, NULL, NULL, NULL, NULL); + assuan_transact (agent_ctx, "READKEY --card --no-data -- $AUTHKEYID", + NULL, NULL, NULL, NULL, NULL, NULL); + + return err; +} + + + + +struct card_cardlist_parm_s { + int error; + strlist_t list; +}; + + +/* Callback function for agent_card_cardlist. */ +static gpg_error_t +card_cardlist_cb (void *opaque, const char *line) +{ + struct card_cardlist_parm_s *parm = opaque; + const char *keyword = line; + int keywordlen; + + for (keywordlen=0; *line && !spacep (line); line++, keywordlen++) + ; + while (spacep (line)) + line++; + + if (keywordlen == 8 && !memcmp (keyword, "SERIALNO", keywordlen)) + { + const char *s; + int n; + + for (n=0,s=line; hexdigitp (s); s++, n++) + ; + + if (!n || (n&1) || *s) + parm->error = gpg_error (GPG_ERR_ASS_PARAMETER); + else + add_to_strlist (&parm->list, line); + } + + return 0; +} + + +/* Return a list of currently available cards. + * Used by: + * card-util.c + * skclist.c + */ +int +agent_scd_cardlist (strlist_t *result) +{ + int err; + char line[ASSUAN_LINELENGTH]; + struct card_cardlist_parm_s parm; + + memset (&parm, 0, sizeof parm); + *result = NULL; + err = start_agent (NULL, 1); + if (err) + return err; + + strcpy (line, "SCD GETINFO card_list"); + + err = assuan_transact (agent_ctx, line, + NULL, NULL, NULL, NULL, + card_cardlist_cb, &parm); + if (!err && parm.error) + err = parm.error; + + if (!err) + *result = parm.list; + else + free_strlist (parm.list); + + return 0; +} + + + +/* Change the PIN of an OpenPGP card or reset the retry counter. + * CHVNO 1: Change the PIN + * 2: For v1 cards: Same as 1. + * For v2 cards: Reset the PIN using the Reset Code. + * 3: Change the admin PIN + * 101: Set a new PIN and reset the retry counter + * 102: For v1 cars: Same as 101. + * For v2 cards: Set a new Reset Code. + * SERIALNO is not used. + * Used by: + * card-util.c + */ +int +agent_scd_change_pin (int chvno, const char *serialno) +{ + int rc; + char line[ASSUAN_LINELENGTH]; + const char *reset = ""; + struct default_inq_parm_s dfltparm; + + memset (&dfltparm, 0, sizeof dfltparm); + + (void)serialno; + + if (chvno >= 100) + reset = "--reset"; + chvno %= 100; + + rc = start_agent (NULL, 1); + if (rc) + return rc; + dfltparm.ctx = agent_ctx; + + snprintf (line, DIM(line), "SCD PASSWD %s %d", reset, chvno); + rc = assuan_transact (agent_ctx, line, + NULL, NULL, + default_inq_cb, &dfltparm, + NULL, NULL); + 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. + * Used by: + * card-util.c + */ +int +agent_scd_checkpin (const char *serialno) +{ + int rc; + char line[ASSUAN_LINELENGTH]; + struct default_inq_parm_s dfltparm; + + memset (&dfltparm, 0, sizeof dfltparm); + + rc = start_agent (NULL, 1); + if (rc) + return rc; + dfltparm.ctx = agent_ctx; + + snprintf (line, DIM(line), "SCD CHECKPIN %s", serialno); + rc = assuan_transact (agent_ctx, line, + NULL, NULL, + default_inq_cb, &dfltparm, + NULL, NULL); + status_sc_op_failure (rc); + return rc; +} + + + +/* Note: All strings shall be UTF-8. On success the caller needs to + free the string stored at R_PASSPHRASE. On error NULL will be + stored at R_PASSPHRASE and an appropriate error code returned. + Only called from passphrase.c:passphrase_get - see there for more + comments on this ugly API. */ +gpg_error_t +agent_get_passphrase (const char *cache_id, + const char *err_msg, + const char *prompt, + const char *desc_msg, + int newsymkey, + int repeat, + int check, + char **r_passphrase) +{ + int rc; + char line[ASSUAN_LINELENGTH]; + char *arg1 = NULL; + char *arg2 = NULL; + char *arg3 = NULL; + char *arg4 = NULL; + membuf_t data; + struct default_inq_parm_s dfltparm; + int have_newsymkey; + + memset (&dfltparm, 0, sizeof dfltparm); + + *r_passphrase = NULL; + + rc = start_agent (NULL, 0); + if (rc) + return rc; + dfltparm.ctx = agent_ctx; + + /* Check that the gpg-agent understands the repeat option. */ + if (assuan_transact (agent_ctx, + "GETINFO cmd_has_option GET_PASSPHRASE repeat", + NULL, NULL, NULL, NULL, NULL, NULL)) + return gpg_error (GPG_ERR_NOT_SUPPORTED); + have_newsymkey = !(assuan_transact + (agent_ctx, + "GETINFO cmd_has_option GET_PASSPHRASE newsymkey", + NULL, NULL, NULL, NULL, NULL, NULL)); + + if (cache_id && *cache_id) + if (!(arg1 = percent_plus_escape (cache_id))) + goto no_mem; + if (err_msg && *err_msg) + if (!(arg2 = percent_plus_escape (err_msg))) + goto no_mem; + if (prompt && *prompt) + if (!(arg3 = percent_plus_escape (prompt))) + goto no_mem; + if (desc_msg && *desc_msg) + if (!(arg4 = percent_plus_escape (desc_msg))) + goto no_mem; + + /* CHECK && REPEAT or NEWSYMKEY is here an indication that a new + * passphrase for symmetric encryption is requested; if the agent + * supports this we enable the modern API by also passing --newsymkey. */ + snprintf (line, DIM(line), + "GET_PASSPHRASE --data --repeat=%d%s%s -- %s %s %s %s", + repeat, + ((repeat && check) || newsymkey)? " --check":"", + (have_newsymkey && newsymkey)? " --newsymkey":"", + arg1? arg1:"X", + arg2? arg2:"X", + arg3? arg3:"X", + arg4? arg4:"X"); + xfree (arg1); + xfree (arg2); + xfree (arg3); + xfree (arg4); + + init_membuf_secure (&data, 64); + rc = assuan_transact (agent_ctx, line, + put_membuf_cb, &data, + default_inq_cb, &dfltparm, + NULL, NULL); + + if (rc) + xfree (get_membuf (&data, NULL)); + else + { + put_membuf (&data, "", 1); + *r_passphrase = get_membuf (&data, NULL); + if (!*r_passphrase) + rc = gpg_error_from_syserror (); + } + return rc; + no_mem: + rc = gpg_error_from_syserror (); + xfree (arg1); + xfree (arg2); + xfree (arg3); + xfree (arg4); + return rc; +} + + +gpg_error_t +agent_clear_passphrase (const char *cache_id) +{ + int rc; + char line[ASSUAN_LINELENGTH]; + struct default_inq_parm_s dfltparm; + + memset (&dfltparm, 0, sizeof dfltparm); + + if (!cache_id || !*cache_id) + return 0; + + rc = start_agent (NULL, 0); + if (rc) + return rc; + dfltparm.ctx = agent_ctx; + + snprintf (line, DIM(line), "CLEAR_PASSPHRASE %s", cache_id); + return assuan_transact (agent_ctx, line, + NULL, NULL, + default_inq_cb, &dfltparm, + NULL, NULL); +} + + +/* Ask the agent to pop up a confirmation dialog with the text DESC + and an okay and cancel button. */ +gpg_error_t +gpg_agent_get_confirmation (const char *desc) +{ + int rc; + char *tmp; + char line[ASSUAN_LINELENGTH]; + struct default_inq_parm_s dfltparm; + + memset (&dfltparm, 0, sizeof dfltparm); + + rc = start_agent (NULL, 0); + if (rc) + return rc; + dfltparm.ctx = agent_ctx; + + tmp = percent_plus_escape (desc); + if (!tmp) + return gpg_error_from_syserror (); + snprintf (line, DIM(line), "GET_CONFIRMATION %s", tmp); + xfree (tmp); + + rc = assuan_transact (agent_ctx, line, + NULL, NULL, + default_inq_cb, &dfltparm, + NULL, NULL); + return rc; +} + + +/* Return the S2K iteration count as computed by gpg-agent. */ +gpg_error_t +agent_get_s2k_count (unsigned long *r_count) +{ + gpg_error_t err; + membuf_t data; + char *buf; + + *r_count = 0; + + err = start_agent (NULL, 0); + if (err) + return err; + + init_membuf (&data, 32); + err = assuan_transact (agent_ctx, "GETINFO s2k_count", + put_membuf_cb, &data, + NULL, NULL, NULL, NULL); + if (err) + xfree (get_membuf (&data, NULL)); + else + { + put_membuf (&data, "", 1); + buf = get_membuf (&data, NULL); + if (!buf) + err = gpg_error_from_syserror (); + else + { + *r_count = strtoul (buf, NULL, 10); + xfree (buf); + } + } + return err; +} + + + +/* Ask the agent whether a secret key for the given public key is + available. Returns 0 if available. */ +gpg_error_t +agent_probe_secret_key (ctrl_t ctrl, PKT_public_key *pk) +{ + gpg_error_t err; + char line[ASSUAN_LINELENGTH]; + char *hexgrip; + + err = start_agent (ctrl, 0); + if (err) + return err; + + err = hexkeygrip_from_pk (pk, &hexgrip); + if (err) + return err; + + snprintf (line, sizeof line, "HAVEKEY %s", hexgrip); + xfree (hexgrip); + + err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); + return err; +} + +/* Ask the agent whether a secret key is available for any of the + keys (primary or sub) in KEYBLOCK. Returns 0 if available. */ +gpg_error_t +agent_probe_any_secret_key (ctrl_t ctrl, kbnode_t keyblock) +{ + gpg_error_t err; + char line[ASSUAN_LINELENGTH]; + char *p; + kbnode_t kbctx, node; + int nkeys; + unsigned char grip[20]; + + err = start_agent (ctrl, 0); + if (err) + return err; + + err = gpg_error (GPG_ERR_NO_SECKEY); /* Just in case no key was + found in KEYBLOCK. */ + p = stpcpy (line, "HAVEKEY"); + for (kbctx=NULL, nkeys=0; (node = walk_kbnode (keyblock, &kbctx, 0)); ) + if (node->pkt->pkttype == PKT_PUBLIC_KEY + || node->pkt->pkttype == PKT_PUBLIC_SUBKEY + || node->pkt->pkttype == PKT_SECRET_KEY + || node->pkt->pkttype == PKT_SECRET_SUBKEY) + { + if (nkeys && ((p - line) + 41) > (ASSUAN_LINELENGTH - 2)) + { + err = assuan_transact (agent_ctx, line, + NULL, NULL, NULL, NULL, NULL, NULL); + if (err != gpg_err_code (GPG_ERR_NO_SECKEY)) + break; /* Seckey available or unexpected error - ready. */ + p = stpcpy (line, "HAVEKEY"); + nkeys = 0; + } + + err = keygrip_from_pk (node->pkt->pkt.public_key, grip); + if (err) + return err; + *p++ = ' '; + bin2hex (grip, 20, p); + p += 40; + nkeys++; + } + + if (!err && nkeys) + err = assuan_transact (agent_ctx, line, + NULL, NULL, NULL, NULL, NULL, NULL); + + return err; +} + + + +struct keyinfo_data_parm_s +{ + char *serialno; + int cleartext; +}; + + +static gpg_error_t +keyinfo_status_cb (void *opaque, const char *line) +{ + struct keyinfo_data_parm_s *data = opaque; + int is_smartcard; + char *s; + + if ((s = has_leading_keyword (line, "KEYINFO")) && data) + { + /* Parse the arguments: + * 0 1 2 3 4 5 + * + */ + char *fields[6]; + + if (split_fields (s, fields, DIM (fields)) == 6) + { + is_smartcard = (fields[1][0] == 'T'); + if (is_smartcard && !data->serialno && strcmp (fields[2], "-")) + data->serialno = xtrystrdup (fields[2]); + /* 'P' for protected, 'C' for clear */ + data->cleartext = (fields[5][0] == 'C'); + } + } + return 0; +} + + +/* Return the serial number for a secret key. If the returned serial + number is NULL, the key is not stored on a smartcard. Caller needs + to free R_SERIALNO. + + if r_cleartext is not NULL, the referenced int will be set to 1 if + the agent's copy of the key is stored in the clear, or 0 otherwise +*/ +gpg_error_t +agent_get_keyinfo (ctrl_t ctrl, const char *hexkeygrip, + char **r_serialno, int *r_cleartext) +{ + gpg_error_t err; + char line[ASSUAN_LINELENGTH]; + struct keyinfo_data_parm_s keyinfo; + + memset (&keyinfo, 0,sizeof keyinfo); + + *r_serialno = NULL; + + err = start_agent (ctrl, 0); + if (err) + return err; + + if (!hexkeygrip || strlen (hexkeygrip) != 40) + return gpg_error (GPG_ERR_INV_VALUE); + + snprintf (line, DIM(line), "KEYINFO %s", hexkeygrip); + + err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, + keyinfo_status_cb, &keyinfo); + if (!err && keyinfo.serialno) + { + /* Sanity check for bad characters. */ + if (strpbrk (keyinfo.serialno, ":\n\r")) + err = GPG_ERR_INV_VALUE; + } + if (err) + xfree (keyinfo.serialno); + else + { + *r_serialno = keyinfo.serialno; + if (r_cleartext) + *r_cleartext = keyinfo.cleartext; + } + return err; +} + + +/* Status callback for agent_import_key, agent_export_key and + agent_genkey. */ +static gpg_error_t +cache_nonce_status_cb (void *opaque, const char *line) +{ + struct cache_nonce_parm_s *parm = opaque; + const char *s; + + if ((s = has_leading_keyword (line, "CACHE_NONCE"))) + { + if (parm->cache_nonce_addr) + { + xfree (*parm->cache_nonce_addr); + *parm->cache_nonce_addr = xtrystrdup (s); + } + } + else if ((s = has_leading_keyword (line, "PASSWD_NONCE"))) + { + if (parm->passwd_nonce_addr) + { + xfree (*parm->passwd_nonce_addr); + *parm->passwd_nonce_addr = xtrystrdup (s); + } + } + else if ((s = has_leading_keyword (line, "PROGRESS"))) + { + if (opt.enable_progress_filter) + write_status_text (STATUS_PROGRESS, s); + } + + return 0; +} + + + +/* Handle a KEYPARMS inquiry. Note, we only send the data, + assuan_transact takes care of flushing and writing the end */ +static gpg_error_t +inq_genkey_parms (void *opaque, const char *line) +{ + struct genkey_parm_s *parm = opaque; + gpg_error_t err; + + if (has_leading_keyword (line, "KEYPARAM")) + { + err = assuan_send_data (parm->dflt->ctx, + parm->keyparms, strlen (parm->keyparms)); + } + else if (has_leading_keyword (line, "NEWPASSWD") && parm->passphrase) + { + err = assuan_send_data (parm->dflt->ctx, + parm->passphrase, strlen (parm->passphrase)); + } + else + err = default_inq_cb (parm->dflt, line); + + return err; +} + + +/* Call the agent to generate a new key. KEYPARMS is the usual + S-expression giving the parameters of the key. gpg-agent passes it + gcry_pk_genkey. If NO_PROTECTION is true the agent is advised not + to protect the generated key. If NO_PROTECTION is not set and + PASSPHRASE is not NULL the agent is requested to protect the key + with that passphrase instead of asking for one. TIMESTAMP is the + creation time of the key or zero. */ +gpg_error_t +agent_genkey (ctrl_t ctrl, char **cache_nonce_addr, char **passwd_nonce_addr, + const char *keyparms, int no_protection, + const char *passphrase, time_t timestamp, gcry_sexp_t *r_pubkey) +{ + gpg_error_t err; + struct genkey_parm_s gk_parm; + struct cache_nonce_parm_s cn_parm; + struct default_inq_parm_s dfltparm; + membuf_t data; + size_t len; + unsigned char *buf; + char timestamparg[16 + 16]; /* The 2nd 16 is sizeof(gnupg_isotime_t) */ + char line[ASSUAN_LINELENGTH]; + + memset (&dfltparm, 0, sizeof dfltparm); + dfltparm.ctrl = ctrl; + + *r_pubkey = NULL; + err = start_agent (ctrl, 0); + if (err) + return err; + dfltparm.ctx = agent_ctx; + + if (timestamp) + { + strcpy (timestamparg, " --timestamp="); + epoch2isotime (timestamparg+13, timestamp); + } + else + *timestamparg = 0; + + if (passwd_nonce_addr && *passwd_nonce_addr) + ; /* A RESET would flush the passwd nonce cache. */ + else + { + err = assuan_transact (agent_ctx, "RESET", + NULL, NULL, NULL, NULL, NULL, NULL); + if (err) + return err; + } + + init_membuf (&data, 1024); + gk_parm.dflt = &dfltparm; + gk_parm.keyparms = keyparms; + gk_parm.passphrase = passphrase; + snprintf (line, sizeof line, "GENKEY%s%s%s%s%s%s", + *timestamparg? timestamparg : "", + no_protection? " --no-protection" : + passphrase ? " --inq-passwd" : + /* */ "", + passwd_nonce_addr && *passwd_nonce_addr? " --passwd-nonce=":"", + passwd_nonce_addr && *passwd_nonce_addr? *passwd_nonce_addr:"", + cache_nonce_addr && *cache_nonce_addr? " ":"", + cache_nonce_addr && *cache_nonce_addr? *cache_nonce_addr:""); + cn_parm.cache_nonce_addr = cache_nonce_addr; + cn_parm.passwd_nonce_addr = NULL; + err = assuan_transact (agent_ctx, line, + put_membuf_cb, &data, + inq_genkey_parms, &gk_parm, + cache_nonce_status_cb, &cn_parm); + if (err) + { + xfree (get_membuf (&data, &len)); + return err; + } + + buf = get_membuf (&data, &len); + if (!buf) + err = gpg_error_from_syserror (); + else + { + err = gcry_sexp_sscan (r_pubkey, NULL, buf, len); + xfree (buf); + } + return err; +} + + + +/* Call the agent to read the public key part for a given keygrip. If + FROMCARD is true, the key is directly read from the current + smartcard. In this case HEXKEYGRIP should be the keyID + (e.g. OPENPGP.3). */ +gpg_error_t +agent_readkey (ctrl_t ctrl, int fromcard, const char *hexkeygrip, + unsigned char **r_pubkey) +{ + gpg_error_t err; + membuf_t data; + size_t len; + unsigned char *buf; + char line[ASSUAN_LINELENGTH]; + struct default_inq_parm_s dfltparm; + + memset (&dfltparm, 0, sizeof dfltparm); + dfltparm.ctrl = ctrl; + + *r_pubkey = NULL; + err = start_agent (ctrl, 0); + if (err) + return err; + dfltparm.ctx = agent_ctx; + + err = assuan_transact (agent_ctx, "RESET",NULL, NULL, NULL, NULL, NULL, NULL); + if (err) + return err; + + snprintf (line, DIM(line), "READKEY %s%s", fromcard? "--card ":"", + hexkeygrip); + + init_membuf (&data, 1024); + err = assuan_transact (agent_ctx, line, + put_membuf_cb, &data, + default_inq_cb, &dfltparm, + NULL, NULL); + if (err) + { + xfree (get_membuf (&data, &len)); + return err; + } + buf = get_membuf (&data, &len); + if (!buf) + return gpg_error_from_syserror (); + if (!gcry_sexp_canon_len (buf, len, NULL, NULL)) + { + xfree (buf); + return gpg_error (GPG_ERR_INV_SEXP); + } + *r_pubkey = buf; + return 0; +} + + + +/* Call the agent to do a sign operation using the key identified by + the hex string KEYGRIP. DESC is a description of the key to be + displayed if the agent needs to ask for the PIN. DIGEST and + DIGESTLEN is the hash value to sign and DIGESTALGO the algorithm id + used to compute the digest. If CACHE_NONCE is used the agent is + advised to first try a passphrase associated with that nonce. */ +gpg_error_t +agent_pksign (ctrl_t ctrl, const char *cache_nonce, + const char *keygrip, const char *desc, + u32 *keyid, u32 *mainkeyid, int pubkey_algo, + unsigned char *digest, size_t digestlen, int digestalgo, + gcry_sexp_t *r_sigval) +{ + gpg_error_t err; + char line[ASSUAN_LINELENGTH]; + membuf_t data; + struct default_inq_parm_s dfltparm; + + memset (&dfltparm, 0, sizeof dfltparm); + dfltparm.ctrl = ctrl; + dfltparm.keyinfo.keyid = keyid; + dfltparm.keyinfo.mainkeyid = mainkeyid; + dfltparm.keyinfo.pubkey_algo = pubkey_algo; + + *r_sigval = NULL; + err = start_agent (ctrl, 0); + if (err) + return err; + dfltparm.ctx = agent_ctx; + + if (digestlen*2 + 50 > DIM(line)) + return gpg_error (GPG_ERR_GENERAL); + + err = assuan_transact (agent_ctx, "RESET", + NULL, NULL, NULL, NULL, NULL, NULL); + if (err) + return err; + + snprintf (line, DIM(line), "SIGKEY %s", keygrip); + err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); + if (err) + return err; + + if (desc) + { + snprintf (line, DIM(line), "SETKEYDESC %s", desc); + err = assuan_transact (agent_ctx, line, + NULL, NULL, NULL, NULL, NULL, NULL); + if (err) + return err; + } + + snprintf (line, sizeof line, "SETHASH %d ", digestalgo); + bin2hex (digest, digestlen, line + strlen (line)); + err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); + if (err) + return err; + + init_membuf (&data, 1024); + + snprintf (line, sizeof line, "PKSIGN%s%s", + cache_nonce? " -- ":"", + cache_nonce? cache_nonce:""); + err = assuan_transact (agent_ctx, line, + put_membuf_cb, &data, + default_inq_cb, &dfltparm, + NULL, NULL); + if (err) + xfree (get_membuf (&data, NULL)); + else + { + unsigned char *buf; + size_t len; + + buf = get_membuf (&data, &len); + if (!buf) + err = gpg_error_from_syserror (); + else + { + err = gcry_sexp_sscan (r_sigval, NULL, buf, len); + xfree (buf); + } + } + return err; +} + + + +/* Handle a CIPHERTEXT inquiry. Note, we only send the data, + assuan_transact takes care of flushing and writing the END. */ +static gpg_error_t +inq_ciphertext_cb (void *opaque, const char *line) +{ + struct cipher_parm_s *parm = opaque; + int rc; + + if (has_leading_keyword (line, "CIPHERTEXT")) + { + assuan_begin_confidential (parm->ctx); + rc = assuan_send_data (parm->dflt->ctx, + parm->ciphertext, parm->ciphertextlen); + assuan_end_confidential (parm->ctx); + } + else + rc = default_inq_cb (parm->dflt, line); + + return rc; +} + + +/* Check whether there is any padding info from the agent. */ +static gpg_error_t +padding_info_cb (void *opaque, const char *line) +{ + int *r_padding = opaque; + const char *s; + + if ((s=has_leading_keyword (line, "PADDING"))) + { + *r_padding = atoi (s); + } + + return 0; +} + + +/* Call the agent to do a decrypt operation using the key identified + by the hex string KEYGRIP and the input data S_CIPHERTEXT. On the + success the decoded value is stored verbatim at R_BUF and its + length at R_BUF; the callers needs to release it. KEYID, MAINKEYID + and PUBKEY_ALGO are used to construct additional promots or status + messages. The padding information is stored at R_PADDING with -1 + for not known. */ +gpg_error_t +agent_pkdecrypt (ctrl_t ctrl, const char *keygrip, const char *desc, + u32 *keyid, u32 *mainkeyid, int pubkey_algo, + gcry_sexp_t s_ciphertext, + unsigned char **r_buf, size_t *r_buflen, int *r_padding) +{ + gpg_error_t err; + char line[ASSUAN_LINELENGTH]; + membuf_t data; + size_t n, len; + char *p, *buf, *endp; + struct default_inq_parm_s dfltparm; + + memset (&dfltparm, 0, sizeof dfltparm); + dfltparm.ctrl = ctrl; + dfltparm.keyinfo.keyid = keyid; + dfltparm.keyinfo.mainkeyid = mainkeyid; + dfltparm.keyinfo.pubkey_algo = pubkey_algo; + + if (!keygrip || strlen(keygrip) != 40 + || !s_ciphertext || !r_buf || !r_buflen || !r_padding) + return gpg_error (GPG_ERR_INV_VALUE); + + *r_buf = NULL; + *r_padding = -1; + + err = start_agent (ctrl, 0); + if (err) + return err; + dfltparm.ctx = agent_ctx; + + err = assuan_transact (agent_ctx, "RESET", + NULL, NULL, NULL, NULL, NULL, NULL); + if (err) + return err; + + snprintf (line, sizeof line, "SETKEY %s", keygrip); + err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); + if (err) + return err; + + if (desc) + { + snprintf (line, DIM(line), "SETKEYDESC %s", desc); + err = assuan_transact (agent_ctx, line, + NULL, NULL, NULL, NULL, NULL, NULL); + if (err) + return err; + } + + init_membuf_secure (&data, 1024); + { + struct cipher_parm_s parm; + + parm.dflt = &dfltparm; + parm.ctx = agent_ctx; + err = make_canon_sexp (s_ciphertext, &parm.ciphertext, &parm.ciphertextlen); + if (err) + return err; + err = assuan_transact (agent_ctx, "PKDECRYPT", + put_membuf_cb, &data, + inq_ciphertext_cb, &parm, + padding_info_cb, r_padding); + xfree (parm.ciphertext); + } + if (err) + { + xfree (get_membuf (&data, &len)); + return err; + } + + buf = get_membuf (&data, &len); + if (!buf) + return gpg_error_from_syserror (); + + if (len == 0 || *buf != '(') + { + xfree (buf); + return gpg_error (GPG_ERR_INV_SEXP); + } + + if (len < 12 || memcmp (buf, "(5:value", 8) ) /* "(5:valueN:D)" */ + { + xfree (buf); + return gpg_error (GPG_ERR_INV_SEXP); + } + while (buf[len-1] == 0) + len--; + if (buf[len-1] != ')') + return gpg_error (GPG_ERR_INV_SEXP); + len--; /* Drop the final close-paren. */ + p = buf + 8; /* Skip leading parenthesis and the value tag. */ + len -= 8; /* Count only the data of the second part. */ + + n = strtoul (p, &endp, 10); + if (!n || *endp != ':') + { + xfree (buf); + return gpg_error (GPG_ERR_INV_SEXP); + } + endp++; + if (endp-p+n > len) + { + xfree (buf); + return gpg_error (GPG_ERR_INV_SEXP); /* Oops: Inconsistent S-Exp. */ + } + + memmove (buf, endp, n); + + *r_buflen = n; + *r_buf = buf; + return 0; +} + + + +/* Retrieve a key encryption key from the agent. With FOREXPORT true + the key shall be used for export, with false for import. On success + the new key is stored at R_KEY and its length at R_KEKLEN. */ +gpg_error_t +agent_keywrap_key (ctrl_t ctrl, int forexport, void **r_kek, size_t *r_keklen) +{ + gpg_error_t err; + membuf_t data; + size_t len; + unsigned char *buf; + char line[ASSUAN_LINELENGTH]; + struct default_inq_parm_s dfltparm; + + memset (&dfltparm, 0, sizeof dfltparm); + dfltparm.ctrl = ctrl; + + *r_kek = NULL; + err = start_agent (ctrl, 0); + if (err) + return err; + dfltparm.ctx = agent_ctx; + + snprintf (line, DIM(line), "KEYWRAP_KEY %s", + forexport? "--export":"--import"); + + init_membuf_secure (&data, 64); + err = assuan_transact (agent_ctx, line, + put_membuf_cb, &data, + default_inq_cb, &dfltparm, + NULL, NULL); + if (err) + { + xfree (get_membuf (&data, &len)); + return err; + } + buf = get_membuf (&data, &len); + if (!buf) + return gpg_error_from_syserror (); + *r_kek = buf; + *r_keklen = len; + return 0; +} + + + +/* Handle the inquiry for an IMPORT_KEY command. */ +static gpg_error_t +inq_import_key_parms (void *opaque, const char *line) +{ + struct import_key_parm_s *parm = opaque; + gpg_error_t err; + + if (has_leading_keyword (line, "KEYDATA")) + { + err = assuan_send_data (parm->dflt->ctx, parm->key, parm->keylen); + } + else + err = default_inq_cb (parm->dflt, line); + + return err; +} + + +/* Call the agent to import a key into the agent. */ +gpg_error_t +agent_import_key (ctrl_t ctrl, const char *desc, char **cache_nonce_addr, + const void *key, size_t keylen, int unattended, int force, + u32 *keyid, u32 *mainkeyid, int pubkey_algo, u32 timestamp) +{ + gpg_error_t err; + struct import_key_parm_s parm; + struct cache_nonce_parm_s cn_parm; + char timestamparg[16 + 16]; /* The 2nd 16 is sizeof(gnupg_isotime_t) */ + char line[ASSUAN_LINELENGTH]; + struct default_inq_parm_s dfltparm; + + memset (&dfltparm, 0, sizeof dfltparm); + dfltparm.ctrl = ctrl; + dfltparm.keyinfo.keyid = keyid; + dfltparm.keyinfo.mainkeyid = mainkeyid; + dfltparm.keyinfo.pubkey_algo = pubkey_algo; + + err = start_agent (ctrl, 0); + if (err) + return err; + dfltparm.ctx = agent_ctx; + + if (timestamp) + { + strcpy (timestamparg, " --timestamp="); + epoch2isotime (timestamparg+13, timestamp); + } + else + *timestamparg = 0; + + if (desc) + { + snprintf (line, DIM(line), "SETKEYDESC %s", desc); + err = assuan_transact (agent_ctx, line, + NULL, NULL, NULL, NULL, NULL, NULL); + if (err) + return err; + } + + parm.dflt = &dfltparm; + parm.key = key; + parm.keylen = keylen; + + snprintf (line, sizeof line, "IMPORT_KEY%s%s%s%s%s", + *timestamparg? timestamparg : "", + unattended? " --unattended":"", + force? " --force":"", + cache_nonce_addr && *cache_nonce_addr? " ":"", + cache_nonce_addr && *cache_nonce_addr? *cache_nonce_addr:""); + cn_parm.cache_nonce_addr = cache_nonce_addr; + cn_parm.passwd_nonce_addr = NULL; + err = assuan_transact (agent_ctx, line, + NULL, NULL, + inq_import_key_parms, &parm, + cache_nonce_status_cb, &cn_parm); + return err; +} + + + +/* Receive a secret key from the agent. HEXKEYGRIP is the hexified + keygrip, DESC a prompt to be displayed with the agent's passphrase + question (needs to be plus+percent escaped). if OPENPGP_PROTECTED + is not zero, ensure that the key material is returned in RFC + 4880-compatible passphrased-protected form. If CACHE_NONCE_ADDR is + not NULL the agent is advised to first try a passphrase associated + with that nonce. On success the key is stored as a canonical + S-expression at R_RESULT and R_RESULTLEN. */ +gpg_error_t +agent_export_key (ctrl_t ctrl, const char *hexkeygrip, const char *desc, + int openpgp_protected, char **cache_nonce_addr, + unsigned char **r_result, size_t *r_resultlen, + u32 *keyid, u32 *mainkeyid, int pubkey_algo) +{ + gpg_error_t err; + struct cache_nonce_parm_s cn_parm; + membuf_t data; + size_t len; + unsigned char *buf; + char line[ASSUAN_LINELENGTH]; + struct default_inq_parm_s dfltparm; + + memset (&dfltparm, 0, sizeof dfltparm); + dfltparm.ctrl = ctrl; + dfltparm.keyinfo.keyid = keyid; + dfltparm.keyinfo.mainkeyid = mainkeyid; + dfltparm.keyinfo.pubkey_algo = pubkey_algo; + + *r_result = NULL; + + err = start_agent (ctrl, 0); + if (err) + return err; + dfltparm.ctx = agent_ctx; + + if (desc) + { + snprintf (line, DIM(line), "SETKEYDESC %s", desc); + err = assuan_transact (agent_ctx, line, + NULL, NULL, NULL, NULL, NULL, NULL); + if (err) + return err; + } + + snprintf (line, DIM(line), "EXPORT_KEY %s%s%s %s", + openpgp_protected ? "--openpgp ":"", + cache_nonce_addr && *cache_nonce_addr? "--cache-nonce=":"", + cache_nonce_addr && *cache_nonce_addr? *cache_nonce_addr:"", + hexkeygrip); + + init_membuf_secure (&data, 1024); + cn_parm.cache_nonce_addr = cache_nonce_addr; + cn_parm.passwd_nonce_addr = NULL; + err = assuan_transact (agent_ctx, line, + put_membuf_cb, &data, + default_inq_cb, &dfltparm, + cache_nonce_status_cb, &cn_parm); + if (err) + { + xfree (get_membuf (&data, &len)); + return err; + } + buf = get_membuf (&data, &len); + if (!buf) + return gpg_error_from_syserror (); + *r_result = buf; + *r_resultlen = len; + return 0; +} + + + +/* Ask the agent to delete the key identified by HEXKEYGRIP. If DESC + is not NULL, display DESC instead of the default description + message. If FORCE is true the agent is advised not to ask for + confirmation. */ +gpg_error_t +agent_delete_key (ctrl_t ctrl, const char *hexkeygrip, const char *desc, + int force) +{ + gpg_error_t err; + char line[ASSUAN_LINELENGTH]; + struct default_inq_parm_s dfltparm; + + memset (&dfltparm, 0, sizeof dfltparm); + dfltparm.ctrl = ctrl; + + err = start_agent (ctrl, 0); + if (err) + return err; + + if (!hexkeygrip || strlen (hexkeygrip) != 40) + return gpg_error (GPG_ERR_INV_VALUE); + + if (desc) + { + snprintf (line, DIM(line), "SETKEYDESC %s", desc); + err = assuan_transact (agent_ctx, line, + NULL, NULL, NULL, NULL, NULL, NULL); + if (err) + return err; + } + + snprintf (line, DIM(line), "DELETE_KEY%s %s", + force? " --force":"", hexkeygrip); + err = assuan_transact (agent_ctx, line, NULL, NULL, + default_inq_cb, &dfltparm, + NULL, NULL); + return err; +} + + + +/* Ask the agent to change the passphrase of the key identified by + * HEXKEYGRIP. If DESC is not NULL, display DESC instead of the + * default description message. If CACHE_NONCE_ADDR is not NULL the + * agent is advised to first try a passphrase associated with that + * nonce. If PASSWD_NONCE_ADDR is not NULL the agent will try to use + * the passphrase associated with that nonce for the new passphrase. + * If VERIFY is true the passphrase is only verified. */ +gpg_error_t +agent_passwd (ctrl_t ctrl, const char *hexkeygrip, const char *desc, int verify, + char **cache_nonce_addr, char **passwd_nonce_addr) +{ + gpg_error_t err; + struct cache_nonce_parm_s cn_parm; + char line[ASSUAN_LINELENGTH]; + struct default_inq_parm_s dfltparm; + + memset (&dfltparm, 0, sizeof dfltparm); + dfltparm.ctrl = ctrl; + + err = start_agent (ctrl, 0); + if (err) + return err; + dfltparm.ctx = agent_ctx; + + if (!hexkeygrip || strlen (hexkeygrip) != 40) + return gpg_error (GPG_ERR_INV_VALUE); + + if (desc) + { + snprintf (line, DIM(line), "SETKEYDESC %s", desc); + err = assuan_transact (agent_ctx, line, + NULL, NULL, NULL, NULL, NULL, NULL); + if (err) + return err; + } + + if (verify) + snprintf (line, DIM(line), "PASSWD %s%s --verify %s", + cache_nonce_addr && *cache_nonce_addr? "--cache-nonce=":"", + cache_nonce_addr && *cache_nonce_addr? *cache_nonce_addr:"", + hexkeygrip); + else + snprintf (line, DIM(line), "PASSWD %s%s %s%s %s", + cache_nonce_addr && *cache_nonce_addr? "--cache-nonce=":"", + cache_nonce_addr && *cache_nonce_addr? *cache_nonce_addr:"", + passwd_nonce_addr && *passwd_nonce_addr? "--passwd-nonce=":"", + passwd_nonce_addr && *passwd_nonce_addr? *passwd_nonce_addr:"", + hexkeygrip); + cn_parm.cache_nonce_addr = cache_nonce_addr; + cn_parm.passwd_nonce_addr = passwd_nonce_addr; + err = assuan_transact (agent_ctx, line, NULL, NULL, + default_inq_cb, &dfltparm, + cache_nonce_status_cb, &cn_parm); + return err; +} + + +/* Return the version reported by gpg-agent. */ +gpg_error_t +agent_get_version (ctrl_t ctrl, char **r_version) +{ + gpg_error_t err; + + err = start_agent (ctrl, 0); + if (err) + return err; + + err = get_assuan_server_version (agent_ctx, 0, r_version); + return err; +} diff --git a/g10/call-agent.h b/g10/call-agent.h new file mode 100644 index 0000000..dbc6e2f --- /dev/null +++ b/g10/call-agent.h @@ -0,0 +1,224 @@ +/* call-agent.h - Divert operations to the agent + * Copyright (C) 2003 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ +#ifndef GNUPG_G10_CALL_AGENT_H +#define GNUPG_G10_CALL_AGENT_H + +struct key_attr { + int algo; /* Algorithm identifier. */ + union { + unsigned int nbits; /* Supported keysize. */ + const char *curve; /* Name of curve. */ + }; +}; + +struct agent_card_info_s +{ + int error; /* private. */ + char *reader; /* Reader information. */ + char *apptype; /* Malloced application type string. */ + unsigned int manufacturer_id; + char *manufacturer_name; /* malloced. */ + 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; + char grp1[20]; /* The keygrip for OPENPGP.1 */ + char grp2[20]; /* The keygrip for OPENPGP.2 */ + char grp3[20]; /* The keygrip for OPENPGP.3 */ + 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 is_v2; /* True if this is a v2 card. */ + int chvmaxlen[3]; /* Maximum allowed length of a CHV. */ + int chvretry[3]; /* Allowed retries for the CHV; 0 = blocked. */ + struct key_attr key_attr[3]; + struct { + unsigned int ki:1; /* Key import available. */ + unsigned int aac:1; /* Algorithm attributes are changeable. */ + unsigned int kdf:1; /* KDF object to support PIN hashing available. */ + } extcap; + unsigned int status_indicator; + int kdf_do_enabled; /* Non-zero if card has a KDF object, 0 if not. */ +}; + + + +/* Release the card info structure. */ +void agent_release_card_info (struct agent_card_info_s *info); + +/* Return card info. */ +int agent_scd_learn (struct agent_card_info_s *info, int force); + +/* Get the keypariinfo directly from scdaemon. */ +gpg_error_t agent_scd_keypairinfo (ctrl_t ctrl, strlist_t *r_list); + +/* Return list of cards. */ +int agent_scd_cardlist (strlist_t *result); + +/* Return the serial number, possibly select by DEMAND. */ +int agent_scd_serialno (char **r_serialno, const char *demand); + +/* Send an APDU to the card. */ +gpg_error_t agent_scd_apdu (const char *hexapdu, unsigned int *r_sw); + +/* Get attribute NAME from the card and store at R_VALUE. */ +gpg_error_t agent_scd_getattr_one (const char *name, char **r_value); + +/* Update INFO with the attribute NAME. */ +int agent_scd_getattr (const char *name, struct agent_card_info_s *info); + +/* Send the KEYTOCARD command. */ +int agent_keytocard (const char *hexgrip, int keyno, int force, + const char *serialno, const char *timestamp); + +/* Send a SETATTR command to the SCdaemon. */ +gpg_error_t agent_scd_setattr (const char *name, + const void *value, size_t valuelen); + +/* Send a WRITECERT command to the SCdaemon. */ +int agent_scd_writecert (const char *certidstr, + const unsigned char *certdata, size_t certdatalen); + +/* Send a GENKEY command to the SCdaemon. */ +int agent_scd_genkey (int keyno, int force, u32 *createtime); + +/* Send a READCERT command to the SCdaemon. */ +int agent_scd_readcert (const char *certidstr, + void **r_buf, size_t *r_buflen); + +/* Send a READKEY command to the SCdaemon. */ +gpg_error_t agent_scd_readkey (const char *keyrefstr, gcry_sexp_t *r_result); + +/* Update common shadow key stubs. */ +gpg_error_t agent_update_shadow_keys (void); + +/* Change the PIN of an OpenPGP card or reset the retry counter. */ +int agent_scd_change_pin (int chvno, const char *serialno); + +/* Send the CHECKPIN command to the SCdaemon. */ +int agent_scd_checkpin (const char *serialno); + +/* Send the GET_PASSPHRASE command to the agent. */ +gpg_error_t agent_get_passphrase (const char *cache_id, + const char *err_msg, + const char *prompt, + const char *desc_msg, + int newsymkey, + int repeat, + int check, + char **r_passphrase); + +/* Send the CLEAR_PASSPHRASE command to the agent. */ +gpg_error_t agent_clear_passphrase (const char *cache_id); + +/* Present the prompt DESC and ask the user to confirm. */ +gpg_error_t gpg_agent_get_confirmation (const char *desc); + +/* Return the S2K iteration count as computed by gpg-agent. */ +gpg_error_t agent_get_s2k_count (unsigned long *r_count); + +/* Check whether a secret key for public key PK is available. Returns + 0 if the secret key is available. */ +gpg_error_t agent_probe_secret_key (ctrl_t ctrl, PKT_public_key *pk); + +/* Ask the agent whether a secret key is availabale for any of the + keys (primary or sub) in KEYBLOCK. Returns 0 if available. */ +gpg_error_t agent_probe_any_secret_key (ctrl_t ctrl, kbnode_t keyblock); + + +/* Return infos about the secret key with HEXKEYGRIP. */ +gpg_error_t agent_get_keyinfo (ctrl_t ctrl, const char *hexkeygrip, + char **r_serialno, int *r_cleartext); + +/* Generate a new key. */ +gpg_error_t agent_genkey (ctrl_t ctrl, + char **cache_nonce_addr, char **passwd_nonce_addr, + const char *keyparms, int no_protection, + const char *passphrase, time_t timestamp, + gcry_sexp_t *r_pubkey); + +/* Read a public key. */ +gpg_error_t agent_readkey (ctrl_t ctrl, int fromcard, const char *hexkeygrip, + unsigned char **r_pubkey); + +/* Create a signature. */ +gpg_error_t agent_pksign (ctrl_t ctrl, const char *cache_nonce, + const char *hexkeygrip, const char *desc, + u32 *keyid, u32 *mainkeyid, int pubkey_algo, + unsigned char *digest, size_t digestlen, + int digestalgo, + gcry_sexp_t *r_sigval); + +/* Decrypt a ciphertext. */ +gpg_error_t agent_pkdecrypt (ctrl_t ctrl, const char *keygrip, const char *desc, + u32 *keyid, u32 *mainkeyid, int pubkey_algo, + gcry_sexp_t s_ciphertext, + unsigned char **r_buf, size_t *r_buflen, + int *r_padding); + +/* Retrieve a key encryption key. */ +gpg_error_t agent_keywrap_key (ctrl_t ctrl, int forexport, + void **r_kek, size_t *r_keklen); + +/* Send a key to the agent. */ +gpg_error_t agent_import_key (ctrl_t ctrl, const char *desc, + char **cache_nonce_addr, const void *key, + size_t keylen, int unattended, int force, + u32 *keyid, u32 *mainkeyid, int pubkey_algo, + u32 timestamp); + +/* Receive a key from the agent. */ +gpg_error_t agent_export_key (ctrl_t ctrl, const char *keygrip, + const char *desc, int openpgp_protected, + char **cache_nonce_addr, + unsigned char **r_result, size_t *r_resultlen, + u32 *keyid, u32 *mainkeyid, int pubkey_algo); + +/* Delete a key from the agent. */ +gpg_error_t agent_delete_key (ctrl_t ctrl, const char *hexkeygrip, + const char *desc, int force); + +/* Change the passphrase of a key. */ +gpg_error_t agent_passwd (ctrl_t ctrl, const char *hexkeygrip, const char *desc, + int verify, + char **cache_nonce_addr, char **passwd_nonce_addr); +/* Get the version reported by gpg-agent. */ +gpg_error_t agent_get_version (ctrl_t ctrl, char **r_version); + + +#endif /*GNUPG_G10_CALL_AGENT_H*/ diff --git a/g10/call-dirmngr.c b/g10/call-dirmngr.c new file mode 100644 index 0000000..ef91c54 --- /dev/null +++ b/g10/call-dirmngr.c @@ -0,0 +1,1401 @@ +/* call-dirmngr.c - GPG operations to the Dirmngr. + * Copyright (C) 2011 Free Software Foundation, Inc. + * Copyright (C) 2015 g10 Code GmbH + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#ifdef HAVE_LOCALE_H +# include +#endif + +#include "gpg.h" +#include +#include "../common/util.h" +#include "../common/membuf.h" +#include "options.h" +#include "../common/i18n.h" +#include "../common/asshelp.h" +#include "../common/status.h" +#include "keyserver-internal.h" +#include "call-dirmngr.h" + + +/* Keys retrieved from the web key directory should be small. There + * is only one UID and we can expect that the number of subkeys is + * reasonable. So we set a generous limit of 256 KiB. */ +#define MAX_WKD_RESULT_LENGTH (256 * 1024) + + +/* Parameter structure used to gather status info. Note that it is + * also used for WKD requests. */ +struct ks_status_parm_s +{ + const char *keyword; /* Look for this keyword or NULL for "SOURCE". */ + char *source; +}; + + +/* Parameter structure used with the KS_SEARCH command. */ +struct ks_search_parm_s +{ + gpg_error_t lasterr; /* Last error code. */ + membuf_t saveddata; /* Buffer to build complete lines. */ + char *helpbuf; /* NULL or malloced buffer. */ + size_t helpbufsize; /* Allocated size of HELPBUF. */ + gpg_error_t (*data_cb)(void*, int, char*); /* Callback. */ + void *data_cb_value; /* First argument for DATA_CB. */ + struct ks_status_parm_s *stparm; /* Link to the status parameter. */ +}; + + +/* Parameter structure used with the KS_GET command. */ +struct ks_get_parm_s +{ + estream_t memfp; +}; + + +/* Parameter structure used with the KS_PUT command. */ +struct ks_put_parm_s +{ + assuan_context_t ctx; + kbnode_t keyblock; /* The optional keyblock. */ + const void *data; /* The key in OpenPGP binary format. */ + size_t datalen; /* The length of DATA. */ +}; + + +/* Parameter structure used with the DNS_CERT command. */ +struct dns_cert_parm_s +{ + estream_t memfp; + unsigned char *fpr; + size_t fprlen; + char *url; +}; + + +/* Data used to associate an session with dirmngr contexts. We can't + use a simple one to one mapping because we sometimes need two + connections to the dirmngr; for example while doing a listing and + being in a data callback we may want to retrieve a key. The local + dirmngr data takes care of this. At the end of the session the + function dirmngr_deinit_session_data is called by gpg.c to cleanup + these resources. Note that gpg.h defines a typedef dirmngr_local_t + for this structure. */ +struct dirmngr_local_s +{ + /* Link to other contexts which are used simultaneously. */ + struct dirmngr_local_s *next; + + /* The active Assuan context. */ + assuan_context_t ctx; + + /* Flag set when the keyserver names have been send. */ + int set_keyservers_done; + + /* Flag set to true while an operation is running on CTX. */ + int is_active; +}; + + + +/* Deinitialize all session data of dirmngr pertaining to CTRL. */ +void +gpg_dirmngr_deinit_session_data (ctrl_t ctrl) +{ + dirmngr_local_t dml; + + while ((dml = ctrl->dirmngr_local)) + { + ctrl->dirmngr_local = dml->next; + if (dml->is_active) + log_error ("oops: trying to cleanup an active dirmngr context\n"); + else + assuan_release (dml->ctx); + xfree (dml); + } +} + + +/* Print a warning if the server's version number is less than our + version number. Returns an error code on a connection problem. */ +static gpg_error_t +warn_version_mismatch (assuan_context_t ctx, const char *servername) +{ + gpg_error_t err; + char *serverversion; + const char *myversion = strusage (13); + + err = get_assuan_server_version (ctx, 0, &serverversion); + if (err) + log_error (_("error getting version from '%s': %s\n"), + servername, gpg_strerror (err)); + else if (compare_version_strings (serverversion, myversion) < 0) + { + char *warn; + + warn = xtryasprintf (_("server '%s' is older than us (%s < %s)"), + servername, serverversion, myversion); + if (!warn) + err = gpg_error_from_syserror (); + else + { + log_info (_("WARNING: %s\n"), warn); + if (!opt.quiet) + { + log_info (_("Note: Outdated servers may lack important" + " security fixes.\n")); + log_info (_("Note: Use the command \"%s\" to restart them.\n"), + "gpgconf --kill all"); + } + + write_status_strings (STATUS_WARNING, "server_version_mismatch 0", + " ", warn, NULL); + xfree (warn); + } + } + xfree (serverversion); + return err; +} + + +/* Try to connect to the Dirmngr via a socket or spawn it if possible. + Handle the server's initial greeting and set global options. */ +static gpg_error_t +create_context (ctrl_t ctrl, assuan_context_t *r_ctx) +{ + gpg_error_t err; + assuan_context_t ctx; + + *r_ctx = NULL; + + if (opt.disable_dirmngr) + return gpg_error (GPG_ERR_NO_DIRMNGR); + + err = start_new_dirmngr (&ctx, + GPG_ERR_SOURCE_DEFAULT, + opt.dirmngr_program, + opt.autostart, opt.verbose, DBG_IPC, + NULL /*gpg_status2*/, ctrl); + if (!opt.autostart && gpg_err_code (err) == GPG_ERR_NO_DIRMNGR) + { + static int shown; + + if (!shown) + { + shown = 1; + log_info (_("no dirmngr running in this session\n")); + } + } + else if (!err && !(err = warn_version_mismatch (ctx, DIRMNGR_NAME))) + { + char *line; + + /* Tell the dirmngr that we want to collect audit event. */ + /* err = assuan_transact (agent_ctx, "OPTION audit-events=1", */ + /* NULL, NULL, NULL, NULL, NULL, NULL); */ + if (opt.keyserver_options.http_proxy) + { + line = xtryasprintf ("OPTION http-proxy=%s", + opt.keyserver_options.http_proxy); + if (!line) + err = gpg_error_from_syserror (); + else + { + err = assuan_transact (ctx, line, NULL, NULL, NULL, + NULL, NULL, NULL); + xfree (line); + } + } + + if (err) + ; + else if ((opt.keyserver_options.options & KEYSERVER_HONOR_KEYSERVER_URL)) + { + /* Tell the dirmngr that this possibly privacy invading + option is in use. If Dirmngr is running in Tor mode, it + will return an error. */ + err = assuan_transact (ctx, "OPTION honor-keyserver-url-used", + NULL, NULL, NULL, NULL, NULL, NULL); + if (gpg_err_code (err) == GPG_ERR_FORBIDDEN) + log_error (_("keyserver option \"%s\"" + " may not be used in %s mode\n"), + "honor-keyserver-url", "Tor"); + else if (gpg_err_code (err) == GPG_ERR_UNKNOWN_OPTION) + err = 0; /* Old dirmngr versions do not support this option. */ + } + } + + if (err) + assuan_release (ctx); + else + { + /* audit_log_ok (ctrl->audit, AUDIT_DIRMNGR_READY, err); */ + *r_ctx = ctx; + } + + return err; +} + + +/* Get a context for accessing dirmngr. If no context is available a + new one is created and - if required - dirmngr started. On success + an assuan context is stored at R_CTX. This context may only be + released by means of close_context. Note that NULL is stored at + R_CTX on error. */ +static gpg_error_t +open_context (ctrl_t ctrl, assuan_context_t *r_ctx) +{ + gpg_error_t err; + dirmngr_local_t dml; + + *r_ctx = NULL; + for (;;) + { + for (dml = ctrl->dirmngr_local; dml && dml->is_active; dml = dml->next) + ; + if (dml) + { + /* Found an inactive local session - return that. */ + log_assert (!dml->is_active); + + /* But first do the per session init if not yet done. */ + if (!dml->set_keyservers_done) + { + keyserver_spec_t ksi; + + /* Set all configured keyservers. We clear existing + keyservers so that any keyserver configured in GPG + overrides keyservers possibly still configured in Dirmngr + for the session (Note that the keyserver list of a + session in Dirmngr survives a RESET. */ + for (ksi = opt.keyserver; ksi; ksi = ksi->next) + { + char *line; + + line = xtryasprintf + ("KEYSERVER%s %s", + ksi == opt.keyserver? " --clear":"", ksi->uri); + if (!line) + err = gpg_error_from_syserror (); + else + { + err = assuan_transact (dml->ctx, line, NULL, NULL, NULL, + NULL, NULL, NULL); + xfree (line); + } + + if (err) + return err; + } + + dml->set_keyservers_done = 1; + } + + dml->is_active = 1; + + *r_ctx = dml->ctx; + return 0; + } + + dml = xtrycalloc (1, sizeof *dml); + if (!dml) + return gpg_error_from_syserror (); + err = create_context (ctrl, &dml->ctx); + if (err) + { + xfree (dml); + return err; + } + + /* To be on the nPth thread safe site we need to add it to a + list; this is far easier than to have a lock for this + function. It should not happen anyway but the code is free + because we need it for the is_active check above. */ + dml->next = ctrl->dirmngr_local; + ctrl->dirmngr_local = dml; + } +} + + +/* Close the assuan context CTX or return it to a pool of unused + contexts. If CTX is NULL, the function does nothing. */ +static void +close_context (ctrl_t ctrl, assuan_context_t ctx) +{ + dirmngr_local_t dml; + + if (!ctx) + return; + + for (dml = ctrl->dirmngr_local; dml; dml = dml->next) + { + if (dml->ctx == ctx) + { + if (!dml->is_active) + log_fatal ("closing inactive dirmngr context %p\n", ctx); + dml->is_active = 0; + return; + } + } + log_fatal ("closing unknown dirmngr ctx %p\n", ctx); +} + + +/* Clear the set_keyservers_done flag on context CTX. */ +static void +clear_context_flags (ctrl_t ctrl, assuan_context_t ctx) +{ + dirmngr_local_t dml; + + if (!ctx) + return; + + for (dml = ctrl->dirmngr_local; dml; dml = dml->next) + { + if (dml->ctx == ctx) + { + if (!dml->is_active) + log_fatal ("clear_context_flags on inactive dirmngr ctx %p\n", ctx); + dml->set_keyservers_done = 0; + return; + } + } + log_fatal ("clear_context_flags on unknown dirmngr ctx %p\n", ctx); +} + + + +/* Status callback for ks_list, ks_get, ks_search, and wkd_get */ +static gpg_error_t +ks_status_cb (void *opaque, const char *line) +{ + struct ks_status_parm_s *parm = opaque; + gpg_error_t err = 0; + const char *s, *s2; + const char *warn = NULL; + int is_note = 0; + char *p; + + if ((s = has_leading_keyword (line, parm->keyword? parm->keyword : "SOURCE"))) + { + /* Note that the arg for "S SOURCE" is the URL of a keyserver. */ + if (!parm->source) + { + parm->source = xtrystrdup (s); + if (!parm->source) + err = gpg_error_from_syserror (); + else + { + p = strchr (parm->source, ':'); + if (p && p[1] == '/' && p[2] == '/') + { + /* This is a real URL like "ldap://foo:389/bla,bla" + * Strip off the local part. */ + if ((p = strchr (p+3, '/'))) + *p = 0; + } + else + { + /* This is an LDAP config entry like + * "foo:389:user:pass:base:flags" + * we strip off everything beyound the port. */ + if ((p = strchr (p+1, ':'))) + { + if (p[-1] == ':') + p[-1] = 0; /* No port given. */ + else + *p = 0; + } + } + } + } + } + else if ((s = has_leading_keyword (line, "WARNING")) + || (is_note = !!(s = has_leading_keyword (line, "NOTE")))) + { + if ((s2 = has_leading_keyword (s, "wkd_cached_result"))) + { + if (opt.verbose) + warn = _("WKD uses a cached result"); + } + else if ((s2 = has_leading_keyword (s, "tor_not_running"))) + warn = _("Tor is not running"); + else if ((s2 = has_leading_keyword (s, "tor_config_problem"))) + warn = _("Tor is not properly configured"); + else if ((s2 = has_leading_keyword (s, "dns_config_problem"))) + warn = _("DNS is not properly configured"); + else if ((s2 = has_leading_keyword (s, "http_redirect"))) + warn = _("unacceptable HTTP redirect from server"); + else if ((s2 = has_leading_keyword (s, "http_redirect_cleanup"))) + warn = _("unacceptable HTTP redirect from server was cleaned up"); + else if ((s2 = has_leading_keyword (s, "tls_cert_error"))) + warn = _("server uses an invalid certificate"); + else + warn = NULL; + + if (warn) + { + if (is_note) + log_info (_("Note: %s\n"), warn); + else + log_info (_("WARNING: %s\n"), warn); + if (s2) + { + while (*s2 && !spacep (s2)) + s2++; + while (*s2 && spacep (s2)) + s2++; + if (*s2) + print_further_info ("%s", s2); + } + } + } + + return err; +} + + + +/* Run the "KEYSERVER" command to return the name of the used + keyserver at R_KEYSERVER. */ +gpg_error_t +gpg_dirmngr_ks_list (ctrl_t ctrl, char **r_keyserver) +{ + gpg_error_t err; + assuan_context_t ctx; + struct ks_status_parm_s stparm; + + memset (&stparm, 0, sizeof stparm); + stparm.keyword = "KEYSERVER"; + if (r_keyserver) + *r_keyserver = NULL; + + err = open_context (ctrl, &ctx); + if (err) + return err; + + err = assuan_transact (ctx, "KEYSERVER", NULL, NULL, + NULL, NULL, ks_status_cb, &stparm); + if (err) + goto leave; + if (!stparm.source) + { + err = gpg_error (GPG_ERR_NO_KEYSERVER); + goto leave; + } + + if (r_keyserver) + *r_keyserver = stparm.source; + else + xfree (stparm.source); + stparm.source = NULL; + + leave: + xfree (stparm.source); + close_context (ctrl, ctx); + return err; +} + + + +/* Data callback for the KS_SEARCH command. */ +static gpg_error_t +ks_search_data_cb (void *opaque, const void *data, size_t datalen) +{ + gpg_error_t err = 0; + struct ks_search_parm_s *parm = opaque; + const char *line, *s; + size_t rawlen, linelen; + char fixedbuf[256]; + + if (parm->lasterr) + return 0; + + if (parm->stparm->source) + { + err = parm->data_cb (parm->data_cb_value, 1, parm->stparm->source); + if (err) + { + parm->lasterr = err; + return err; + } + /* Clear it so that we won't get back here unless the server + accidentally sends a second source status line. Note that + will not see all accidentally sent source lines because it + depends on whether data lines have been send in between. */ + xfree (parm->stparm->source); + parm->stparm->source = NULL; + } + + if (!data) + return 0; /* Ignore END commands. */ + + put_membuf (&parm->saveddata, data, datalen); + + again: + line = peek_membuf (&parm->saveddata, &rawlen); + if (!line) + { + parm->lasterr = gpg_error_from_syserror (); + return parm->lasterr; /* Tell the server about our problem. */ + } + if ((s = memchr (line, '\n', rawlen))) + { + linelen = s - line; /* That is the length excluding the LF. */ + if (linelen + 1 < sizeof fixedbuf) + { + /* We can use the static buffer. */ + memcpy (fixedbuf, line, linelen); + fixedbuf[linelen] = 0; + if (linelen && fixedbuf[linelen-1] == '\r') + fixedbuf[linelen-1] = 0; + err = parm->data_cb (parm->data_cb_value, 0, fixedbuf); + } + else + { + if (linelen + 1 >= parm->helpbufsize) + { + xfree (parm->helpbuf); + parm->helpbufsize = linelen + 1 + 1024; + parm->helpbuf = xtrymalloc (parm->helpbufsize); + if (!parm->helpbuf) + { + parm->lasterr = gpg_error_from_syserror (); + return parm->lasterr; + } + } + memcpy (parm->helpbuf, line, linelen); + parm->helpbuf[linelen] = 0; + if (linelen && parm->helpbuf[linelen-1] == '\r') + parm->helpbuf[linelen-1] = 0; + err = parm->data_cb (parm->data_cb_value, 0, parm->helpbuf); + } + if (err) + parm->lasterr = err; + else + { + clear_membuf (&parm->saveddata, linelen+1); + goto again; /* There might be another complete line. */ + } + } + + return err; +} + + +/* Run the KS_SEARCH command using the search string SEARCHSTR. All + data lines are passed to the CB function. That function is called + with CB_VALUE as its first argument, a 0 as second argument, and + the decoded data line as third argument. The callback function may + modify the data line and it is guaranteed that this data line is a + complete line with a terminating 0 character but without the + linefeed. NULL is passed to the callback to indicate EOF. */ +gpg_error_t +gpg_dirmngr_ks_search (ctrl_t ctrl, const char *searchstr, + gpg_error_t (*cb)(void*, int, char *), void *cb_value) +{ + gpg_error_t err; + assuan_context_t ctx; + struct ks_status_parm_s stparm; + struct ks_search_parm_s parm; + char line[ASSUAN_LINELENGTH]; + + err = open_context (ctrl, &ctx); + if (err) + return err; + + { + char *escsearchstr = percent_plus_escape (searchstr); + if (!escsearchstr) + { + err = gpg_error_from_syserror (); + close_context (ctrl, ctx); + return err; + } + snprintf (line, sizeof line, "KS_SEARCH -- %s", escsearchstr); + xfree (escsearchstr); + } + + memset (&stparm, 0, sizeof stparm); + memset (&parm, 0, sizeof parm); + init_membuf (&parm.saveddata, 1024); + parm.data_cb = cb; + parm.data_cb_value = cb_value; + parm.stparm = &stparm; + + err = assuan_transact (ctx, line, ks_search_data_cb, &parm, + NULL, NULL, ks_status_cb, &stparm); + if (!err) + err = cb (cb_value, 0, NULL); /* Send EOF. */ + else if (parm.stparm->source) + { + /* Error but we received a SOURCE status. Tell via callback but + * ignore errors. */ + parm.data_cb (parm.data_cb_value, 1, parm.stparm->source); + } + + xfree (get_membuf (&parm.saveddata, NULL)); + xfree (parm.helpbuf); + xfree (stparm.source); + + close_context (ctrl, ctx); + return err; +} + + + +/* Data callback for the KS_GET and KS_FETCH commands. */ +static gpg_error_t +ks_get_data_cb (void *opaque, const void *data, size_t datalen) +{ + gpg_error_t err = 0; + struct ks_get_parm_s *parm = opaque; + size_t nwritten; + + if (!data) + return 0; /* Ignore END commands. */ + + if (es_write (parm->memfp, data, datalen, &nwritten)) + err = gpg_error_from_syserror (); + + return err; +} + + +/* Run the KS_GET command using the patterns in the array PATTERN. On + success an estream object is returned to retrieve the keys. On + error an error code is returned and NULL stored at R_FP. + + The pattern may only use search specification which a keyserver can + use to retrieve keys. Because we know the format of the pattern we + don't need to escape the patterns before sending them to the + server. + + Bit values for FLAGS are: + - KEYSERVER_IMPORT_FLAG_QUICK :: dirmngr shall use a shorter timeout. + - KEYSERVER_IMPORT_FLAG_LDAP :: dirmngr shall only use LDAP or NTDS. + + If R_SOURCE is not NULL the source of the data is stored as a + malloced string there. If a source is not known NULL is stored. + Note that this may even be returned after an error. + + If there are too many patterns the function returns an error. That + could be fixed by issuing several search commands or by + implementing a different interface. However with long keyids we + are able to ask for (1000-10-1)/(2+8+1) = 90 keys at once. */ +gpg_error_t +gpg_dirmngr_ks_get (ctrl_t ctrl, char **pattern, + keyserver_spec_t override_keyserver, + unsigned int flags, + estream_t *r_fp, char **r_source) +{ + gpg_error_t err; + assuan_context_t ctx; + struct ks_status_parm_s stparm; + struct ks_get_parm_s parm; + char *line = NULL; + size_t linelen; + membuf_t mb; + int idx; + + memset (&stparm, 0, sizeof stparm); + memset (&parm, 0, sizeof parm); + + *r_fp = NULL; + if (r_source) + *r_source = NULL; + + err = open_context (ctrl, &ctx); + if (err) + return err; + + /* If we have an override keyserver we first indicate that the next + user of the context needs to again setup the global keyservers and + then we send the override keyserver. */ + if (override_keyserver) + { + clear_context_flags (ctrl, ctx); + line = xtryasprintf ("KEYSERVER --clear %s", override_keyserver->uri); + if (!line) + { + err = gpg_error_from_syserror (); + goto leave; + } + err = assuan_transact (ctx, line, NULL, NULL, NULL, + NULL, NULL, NULL); + if (err) + goto leave; + + xfree (line); + line = NULL; + } + + /* Lump all patterns into one string. */ + init_membuf (&mb, 1024); + put_membuf_str (&mb, "KS_GET"); + if ((flags & KEYSERVER_IMPORT_FLAG_QUICK)) + put_membuf_str (&mb, " --quick"); + if ((flags & KEYSERVER_IMPORT_FLAG_LDAP)) + put_membuf_str (&mb, " --ldap"); + put_membuf_str (&mb, " --"); + for (idx=0; pattern[idx]; idx++) + { + put_membuf (&mb, " ", 1); /* Append Delimiter. */ + put_membuf_str (&mb, pattern[idx]); + } + put_membuf (&mb, "", 1); /* Append Nul. */ + line = get_membuf (&mb, &linelen); + if (!line) + { + err = gpg_error_from_syserror (); + goto leave; + } + if (linelen + 2 >= ASSUAN_LINELENGTH) + { + err = gpg_error (GPG_ERR_TOO_MANY); + goto leave; + } + + parm.memfp = es_fopenmem (0, "rwb"); + if (!parm.memfp) + { + err = gpg_error_from_syserror (); + goto leave; + } + err = assuan_transact (ctx, line, ks_get_data_cb, &parm, + NULL, NULL, ks_status_cb, &stparm); + if (err) + goto leave; + + es_rewind (parm.memfp); + *r_fp = parm.memfp; + parm.memfp = NULL; + + + leave: + if (r_source && stparm.source) + { + *r_source = stparm.source; + stparm.source = NULL; + } + es_fclose (parm.memfp); + xfree (stparm.source); + xfree (line); + close_context (ctrl, ctx); + return err; +} + + +/* Run the KS_FETCH and pass URL as argument. On success an estream + object is returned to retrieve the keys. On error an error code is + returned and NULL stored at R_FP. + + The url is expected to point to a small set of keys; in many cases + only to one key. However, schemes like finger may return several + keys. Note that the configured keyservers are ignored by the + KS_FETCH command. */ +gpg_error_t +gpg_dirmngr_ks_fetch (ctrl_t ctrl, const char *url, estream_t *r_fp) +{ + gpg_error_t err; + assuan_context_t ctx; + struct ks_get_parm_s parm; + char *line = NULL; + + memset (&parm, 0, sizeof parm); + + *r_fp = NULL; + + err = open_context (ctrl, &ctx); + if (err) + return err; + + line = strconcat ("KS_FETCH -- ", url, NULL); + if (!line) + { + err = gpg_error_from_syserror (); + goto leave; + } + if (strlen (line) + 2 >= ASSUAN_LINELENGTH) + { + err = gpg_error (GPG_ERR_TOO_LARGE); + goto leave; + } + + parm.memfp = es_fopenmem (0, "rwb"); + if (!parm.memfp) + { + err = gpg_error_from_syserror (); + goto leave; + } + err = assuan_transact (ctx, line, ks_get_data_cb, &parm, + NULL, NULL, NULL, NULL); + if (err) + goto leave; + + es_rewind (parm.memfp); + *r_fp = parm.memfp; + parm.memfp = NULL; + + leave: + es_fclose (parm.memfp); + xfree (line); + close_context (ctrl, ctx); + return err; +} + + + +static void +record_output (estream_t output, + pkttype_t type, + const char *validity, + int pub_key_length, /* The public key length or -1. */ + int pub_key_algo, /* The public key algo or -1. */ + const u32 *keyid, /* 2 ulongs or NULL. */ + u32 creation_date, /* The creation date or 0. */ + u32 expiration_date, /* The expiration date or 0. */ + const char *userid) /* The userid or NULL. */ +{ + const char *type_str = NULL; + + switch (type) + { + case PKT_PUBLIC_KEY: + type_str = "pub"; + break; + case PKT_PUBLIC_SUBKEY: + type_str = "sub"; + break; + case PKT_USER_ID: + type_str = "uid"; + break; + case PKT_SIGNATURE: + type_str = "sig"; + break; + default: + log_assert (! "Unhandled type."); + } + es_fprintf (output, "%s:%s:", + type_str, + validity ? validity : ""); + + if (pub_key_length > 0) + es_fprintf (output, "%d", pub_key_length); + es_fputc (':', output); + + if (pub_key_algo != -1) + es_fprintf (output, "%d", pub_key_algo); + es_fputc (':', output); + + if (keyid) + es_fprintf (output, "%08lX%08lX", (ulong) keyid[0], (ulong) keyid[1]); + + es_fprintf (output, ":%s:", colon_strtime (creation_date)); + es_fprintf (output, "%s:::", colon_strtime (expiration_date)); + + if (userid) + es_write_sanitized (output, userid, strlen (userid), ":", NULL); + else + es_fputc (':', output); + es_fputs (":::::::::\n", output); + +} + + +/* Handle the KS_PUT inquiries. */ +static gpg_error_t +ks_put_inq_cb (void *opaque, const char *line) +{ + struct ks_put_parm_s *parm = opaque; + gpg_error_t err = 0; + + if (has_leading_keyword (line, "KEYBLOCK")) + { + if (parm->data) + err = assuan_send_data (parm->ctx, parm->data, parm->datalen); + } + else if (has_leading_keyword (line, "KEYBLOCK_INFO")) + { + kbnode_t node; + estream_t fp; + char hexfpr[2*MAX_FINGERPRINT_LEN+1]; + + /* Parse the keyblock and send info lines back to the server. */ + fp = es_fopenmem (0, "rw,samethread"); + if (!fp) + err = gpg_error_from_syserror (); + + /* Note: the output format for the INFO block follows the colon + format as described in doc/DETAILS. We don't actually reuse + the functionality from g10/keylist.c to produce the output, + because we don't need all of it and some of it is quite + expensive to generate. + + The fields are (the starred fields are the ones we need): + + * Field 1 - Type of record + * Field 2 - Validity + * Field 3 - Key length + * Field 4 - Public key algorithm + * Field 5 - KeyID + * Field 6 - Creation date + * Field 7 - Expiration date + Field 8 - Certificate S/N, UID hash, trust signature info + Field 9 - Ownertrust + * Field 10 - User-ID + Field 11 - Signature class + Field 12 - Key capabilities + Field 13 - Issuer certificate fingerprint or other info + Field 14 - Flag field + Field 15 - S/N of a token + Field 16 - Hash algorithm + Field 17 - Curve name + */ + for (node = parm->keyblock; !err && node; node=node->next) + { + switch (node->pkt->pkttype) + { + case PKT_PUBLIC_KEY: + case PKT_PUBLIC_SUBKEY: + { + PKT_public_key *pk = node->pkt->pkt.public_key; + + char validity[3]; + int i; + + i = 0; + if (pk->flags.revoked) + validity[i ++] = 'r'; + if (pk->has_expired) + validity[i ++] = 'e'; + validity[i] = '\0'; + + keyid_from_pk (pk, NULL); + + record_output (fp, node->pkt->pkttype, validity, + nbits_from_pk (pk), pk->pubkey_algo, + pk->keyid, pk->timestamp, pk->expiredate, + NULL); + es_fprintf (fp, "fpr:::::::::%s:\n", + hexfingerprint (pk, hexfpr, sizeof hexfpr)); + } + break; + + case PKT_USER_ID: + { + PKT_user_id *uid = node->pkt->pkt.user_id; + + if (!uid->attrib_data) + { + char validity[3]; + int i; + + i = 0; + if (uid->flags.revoked) + validity[i ++] = 'r'; + if (uid->flags.expired) + validity[i ++] = 'e'; + validity[i] = '\0'; + + record_output (fp, node->pkt->pkttype, validity, + -1, -1, NULL, + uid->created, uid->expiredate, + uid->name); + } + } + break; + + default: + continue; + } + /* Given that the last operation was an es_fprintf we should + get the correct ERRNO if ferror indicates an error. */ + if (es_ferror (fp)) + err = gpg_error_from_syserror (); + } + + /* Without an error and if we have an keyblock at all, send the + data back. */ + if (!err && parm->keyblock) + { + int rc; + char buffer[512]; + size_t nread; + + es_rewind (fp); + while (!(rc=es_read (fp, buffer, sizeof buffer, &nread)) && nread) + { + err = assuan_send_data (parm->ctx, buffer, nread); + if (err) + break; + } + if (!err && rc) + err = gpg_error_from_syserror (); + } + es_fclose (fp); + } + else + return gpg_error (GPG_ERR_ASS_UNKNOWN_INQUIRE); + + return err; +} + + +/* Send a key to the configured server. {DATA,DATLEN} contains the + key in OpenPGP binary transport format. If KEYBLOCK is not NULL it + has the internal representaion of that key; this is for example + used to convey meta data to LDAP keyservers. */ +gpg_error_t +gpg_dirmngr_ks_put (ctrl_t ctrl, void *data, size_t datalen, kbnode_t keyblock) +{ + gpg_error_t err; + assuan_context_t ctx; + struct ks_put_parm_s parm; + + memset (&parm, 0, sizeof parm); + + /* We are going to parse the keyblock, thus we better make sure the + all information is readily available. */ + if (keyblock) + merge_keys_and_selfsig (ctrl, keyblock); + + err = open_context (ctrl, &ctx); + if (err) + return err; + + parm.ctx = ctx; + parm.keyblock = keyblock; + parm.data = data; + parm.datalen = datalen; + + err = assuan_transact (ctx, "KS_PUT", NULL, NULL, + ks_put_inq_cb, &parm, NULL, NULL); + + close_context (ctrl, ctx); + return err; +} + + + +/* Data callback for the DNS_CERT and WKD_GET commands. */ +static gpg_error_t +dns_cert_data_cb (void *opaque, const void *data, size_t datalen) +{ + struct dns_cert_parm_s *parm = opaque; + gpg_error_t err = 0; + size_t nwritten; + + if (!data) + return 0; /* Ignore END commands. */ + if (!parm->memfp) + return 0; /* Data is not required. */ + + if (es_write (parm->memfp, data, datalen, &nwritten)) + err = gpg_error_from_syserror (); + + return err; +} + + +/* Status callback for the DNS_CERT command. */ +static gpg_error_t +dns_cert_status_cb (void *opaque, const char *line) +{ + struct dns_cert_parm_s *parm = opaque; + gpg_error_t err = 0; + const char *s; + size_t nbytes; + + if ((s = has_leading_keyword (line, "FPR"))) + { + char *buf; + + if (!(buf = xtrystrdup (s))) + err = gpg_error_from_syserror (); + else if (parm->fpr) + err = gpg_error (GPG_ERR_DUP_KEY); + else if (!hex2str (buf, buf, strlen (buf)+1, &nbytes)) + err = gpg_error_from_syserror (); + else if (nbytes < 20) + err = gpg_error (GPG_ERR_TOO_SHORT); + else + { + parm->fpr = xtrymalloc (nbytes); + if (!parm->fpr) + err = gpg_error_from_syserror (); + else + memcpy (parm->fpr, buf, (parm->fprlen = nbytes)); + } + xfree (buf); + } + else if ((s = has_leading_keyword (line, "URL")) && *s) + { + if (parm->url) + err = gpg_error (GPG_ERR_DUP_KEY); + else if (!(parm->url = xtrystrdup (s))) + err = gpg_error_from_syserror (); + } + + return err; +} + +/* Ask the dirmngr for a DNS CERT record. Depending on the found + subtypes different return values are set: + + - For a PGP subtype a new estream with that key will be returned at + R_KEY and the other return parameters are set to NULL/0. + + - For an IPGP subtype the fingerprint is stored as a malloced block + at (R_FPR,R_FPRLEN). If an URL is available it is stored as a + malloced string at R_URL; NULL is stored if there is no URL. + + If CERTTYPE is DNS_CERTTYPE_ANY this function returns the first + CERT record found with a supported type; it is expected that only + one CERT record is used. If CERTTYPE is one of the supported + certtypes, only records with this certtype are considered and the + first one found is returned. All R_* args are optional. + + If CERTTYPE is NULL the DANE method is used to fetch the key. + */ +gpg_error_t +gpg_dirmngr_dns_cert (ctrl_t ctrl, const char *name, const char *certtype, + estream_t *r_key, + unsigned char **r_fpr, size_t *r_fprlen, + char **r_url) +{ + gpg_error_t err; + assuan_context_t ctx; + struct dns_cert_parm_s parm; + char *line = NULL; + + memset (&parm, 0, sizeof parm); + if (r_key) + *r_key = NULL; + if (r_fpr) + *r_fpr = NULL; + if (r_fprlen) + *r_fprlen = 0; + if (r_url) + *r_url = NULL; + + err = open_context (ctrl, &ctx); + if (err) + return err; + + line = es_bsprintf ("DNS_CERT %s %s", certtype? certtype : "--dane", name); + if (!line) + { + err = gpg_error_from_syserror (); + goto leave; + } + if (strlen (line) + 2 >= ASSUAN_LINELENGTH) + { + err = gpg_error (GPG_ERR_TOO_LARGE); + goto leave; + } + + parm.memfp = es_fopenmem (0, "rwb"); + if (!parm.memfp) + { + err = gpg_error_from_syserror (); + goto leave; + } + err = assuan_transact (ctx, line, dns_cert_data_cb, &parm, + NULL, NULL, dns_cert_status_cb, &parm); + if (err) + goto leave; + + if (r_key) + { + es_rewind (parm.memfp); + *r_key = parm.memfp; + parm.memfp = NULL; + } + + if (r_fpr && parm.fpr) + { + *r_fpr = parm.fpr; + parm.fpr = NULL; + } + if (r_fprlen) + *r_fprlen = parm.fprlen; + + if (r_url && parm.url) + { + *r_url = parm.url; + parm.url = NULL; + } + + leave: + xfree (parm.fpr); + xfree (parm.url); + es_fclose (parm.memfp); + xfree (line); + close_context (ctrl, ctx); + return err; +} + + +/* Ask the dirmngr for PKA info. On success the retrieved fingerprint + is returned in a malloced buffer at R_FPR and its length is stored + at R_FPRLEN. If an URL is available it is stored as a malloced + string at R_URL. On error all return values are set to NULL/0. */ +gpg_error_t +gpg_dirmngr_get_pka (ctrl_t ctrl, const char *userid, + unsigned char **r_fpr, size_t *r_fprlen, + char **r_url) +{ + gpg_error_t err; + assuan_context_t ctx; + struct dns_cert_parm_s parm; + char *line = NULL; + + memset (&parm, 0, sizeof parm); + if (r_fpr) + *r_fpr = NULL; + if (r_fprlen) + *r_fprlen = 0; + if (r_url) + *r_url = NULL; + + err = open_context (ctrl, &ctx); + if (err) + return err; + + line = es_bsprintf ("DNS_CERT --pka -- %s", userid); + if (!line) + { + err = gpg_error_from_syserror (); + goto leave; + } + if (strlen (line) + 2 >= ASSUAN_LINELENGTH) + { + err = gpg_error (GPG_ERR_TOO_LARGE); + goto leave; + } + + err = assuan_transact (ctx, line, dns_cert_data_cb, &parm, + NULL, NULL, dns_cert_status_cb, &parm); + if (err) + goto leave; + + if (r_fpr && parm.fpr) + { + *r_fpr = parm.fpr; + parm.fpr = NULL; + } + if (r_fprlen) + *r_fprlen = parm.fprlen; + + if (r_url && parm.url) + { + *r_url = parm.url; + parm.url = NULL; + } + + leave: + xfree (parm.fpr); + xfree (parm.url); + xfree (line); + close_context (ctrl, ctx); + return err; +} + + + +/* Ask the dirmngr to retrieve a key via the Web Key Directory + * protocol. If QUICK is set the dirmngr is advised to use a shorter + * timeout. On success a new estream with the key stored at R_KEY and the + * url of the lookup (if any) stored at R_URL. Note that + */ +gpg_error_t +gpg_dirmngr_wkd_get (ctrl_t ctrl, const char *name, int quick, + estream_t *r_key, char **r_url) +{ + gpg_error_t err; + assuan_context_t ctx; + struct ks_status_parm_s stparm = { NULL }; + struct dns_cert_parm_s parm = { NULL }; + char *line = NULL; + + if (r_key) + *r_key = NULL; + + if (r_url) + *r_url = NULL; + + err = open_context (ctrl, &ctx); + if (err) + return err; + + line = es_bsprintf ("WKD_GET%s -- %s", quick?" --quick":"", name); + if (!line) + { + err = gpg_error_from_syserror (); + goto leave; + } + if (strlen (line) + 2 >= ASSUAN_LINELENGTH) + { + err = gpg_error (GPG_ERR_TOO_LARGE); + goto leave; + } + + parm.memfp = es_fopenmem (MAX_WKD_RESULT_LENGTH, "rwb"); + if (!parm.memfp) + { + err = gpg_error_from_syserror (); + goto leave; + } + err = assuan_transact (ctx, line, dns_cert_data_cb, &parm, + NULL, NULL, ks_status_cb, &stparm); + if (gpg_err_code (err) == GPG_ERR_ENOSPC) + err = gpg_error (GPG_ERR_TOO_LARGE); + if (err) + goto leave; + + if (r_key) + { + es_rewind (parm.memfp); + *r_key = parm.memfp; + parm.memfp = NULL; + } + + if (r_url) + { + *r_url = stparm.source; + stparm.source = NULL; + } + + leave: + xfree (stparm.source); + xfree (parm.fpr); + xfree (parm.url); + es_fclose (parm.memfp); + xfree (line); + close_context (ctrl, ctx); + return err; +} diff --git a/g10/call-dirmngr.h b/g10/call-dirmngr.h new file mode 100644 index 0000000..429661a --- /dev/null +++ b/g10/call-dirmngr.h @@ -0,0 +1,48 @@ +/* call-dirmngr.h - GPG operations to the Dirmngr + * Copyright (C) 2011 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ +#ifndef GNUPG_G10_CALL_DIRMNGR_H +#define GNUPG_G10_CALL_DIRMNGR_H + +void gpg_dirmngr_deinit_session_data (ctrl_t ctrl); + +gpg_error_t gpg_dirmngr_ks_list (ctrl_t ctrl, char **r_keyserver); +gpg_error_t gpg_dirmngr_ks_search (ctrl_t ctrl, const char *searchstr, + gpg_error_t (*cb)(void*, int, char *), + void *cb_value); +gpg_error_t gpg_dirmngr_ks_get (ctrl_t ctrl, char *pattern[], + keyserver_spec_t override_keyserver, + unsigned int flags, + estream_t *r_fp, char **r_source); +gpg_error_t gpg_dirmngr_ks_fetch (ctrl_t ctrl, + const char *url, estream_t *r_fp); +gpg_error_t gpg_dirmngr_ks_put (ctrl_t ctrl, void *data, size_t datalen, + kbnode_t keyblock); +gpg_error_t gpg_dirmngr_dns_cert (ctrl_t ctrl, + const char *name, const char *certtype, + estream_t *r_key, + unsigned char **r_fpr, size_t *r_fprlen, + char **r_url); +gpg_error_t gpg_dirmngr_get_pka (ctrl_t ctrl, const char *userid, + unsigned char **r_fpr, size_t *r_fprlen, + char **r_url); +gpg_error_t gpg_dirmngr_wkd_get (ctrl_t ctrl, const char *name, int quick, + estream_t *r_key, char **r_url); + + +#endif /*GNUPG_G10_CALL_DIRMNGR_H*/ diff --git a/g10/card-util.c b/g10/card-util.c new file mode 100644 index 0000000..25c284e --- /dev/null +++ b/g10/card-util.c @@ -0,0 +1,2489 @@ +/* card-util.c - Utility functions for the OpenPGP card. + * Copyright (C) 2003-2005, 2009 Free Software Foundation, Inc. + * Copyright (C) 2003-2005, 2009 Werner Koch + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include +#include +#include +#include +#include +#ifdef HAVE_LIBREADLINE +# define GNUPG_LIBREADLINE_H_INCLUDED +# include +#endif /*HAVE_LIBREADLINE*/ + +#if GNUPG_MAJOR_VERSION != 1 +# include "gpg.h" +#endif /*GNUPG_MAJOR_VERSION != 1*/ +#include "../common/util.h" +#include "../common/i18n.h" +#include "../common/ttyio.h" +#include "../common/status.h" +#include "options.h" +#include "main.h" +#include "keyserver-internal.h" + +#if GNUPG_MAJOR_VERSION == 1 +# include "cardglue.h" +#else /*GNUPG_MAJOR_VERSION!=1*/ +# include "call-agent.h" +#endif /*GNUPG_MAJOR_VERSION!=1*/ + +#define CONTROL_D ('D' - 'A' + 1) + + +static void +write_sc_op_status (gpg_error_t err) +{ + switch (gpg_err_code (err)) + { + case 0: + write_status (STATUS_SC_OP_SUCCESS); + break; +#if GNUPG_MAJOR_VERSION != 1 + case GPG_ERR_CANCELED: + case GPG_ERR_FULLY_CANCELED: + write_status_text (STATUS_SC_OP_FAILURE, "1"); + break; + case GPG_ERR_BAD_PIN: + write_status_text (STATUS_SC_OP_FAILURE, "2"); + break; + default: + write_status (STATUS_SC_OP_FAILURE); + break; +#endif /* GNUPG_MAJOR_VERSION != 1 */ + } +} + + +/* Change the PIN of an OpenPGP card. This is an interactive + function. */ +void +change_pin (int unblock_v2, int allow_admin) +{ + struct agent_card_info_s info; + int rc; + + rc = agent_scd_learn (&info, 0); + 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]"); + + if (opt.batch) + { + agent_release_card_info (&info); + log_error (_("can't do this in batch mode\n")); + return; + } + + + if (unblock_v2) + { + if (!info.is_v2) + log_error (_("This command is only available for version 2 cards\n")); + else if (!info.chvretry[1]) + log_error (_("Reset Code not or not anymore available\n")); + else + { + rc = agent_scd_change_pin (2, info.serialno); + write_sc_op_status (rc); + if (rc) + tty_printf ("Error changing the PIN: %s\n", gpg_strerror (rc)); + else + tty_printf ("PIN changed.\n"); + } + } + else if (!allow_admin) + { + rc = agent_scd_change_pin (1, info.serialno); + write_sc_op_status (rc); + if (rc) + tty_printf ("Error changing the PIN: %s\n", gpg_strerror (rc)); + else + 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" + "4 - set the Reset Code\n" + "Q - quit\n"); + tty_printf ("\n"); + + answer = cpr_get("cardutil.change_pin.menu",_("Your selection? ")); + cpr_kill_prompt(); + if (strlen (answer) != 1) + continue; + + if (*answer == '1') + { + /* Change PIN. */ + rc = agent_scd_change_pin (1, info.serialno); + write_sc_op_status (rc); + if (rc) + tty_printf ("Error changing the PIN: %s\n", gpg_strerror (rc)); + else + tty_printf ("PIN changed.\n"); + } + else if (*answer == '2') + { + /* Unblock PIN. */ + rc = agent_scd_change_pin (101, info.serialno); + write_sc_op_status (rc); + if (rc) + tty_printf ("Error unblocking the PIN: %s\n", gpg_strerror (rc)); + else + tty_printf ("PIN unblocked and new PIN set.\n"); + } + else if (*answer == '3') + { + /* Change Admin PIN. */ + rc = agent_scd_change_pin (3, info.serialno); + write_sc_op_status (rc); + if (rc) + tty_printf ("Error changing the PIN: %s\n", gpg_strerror (rc)); + else + tty_printf ("PIN changed.\n"); + } + else if (*answer == '4') + { + /* Set a new Reset Code. */ + rc = agent_scd_change_pin (102, info.serialno); + write_sc_op_status (rc); + if (rc) + tty_printf ("Error setting the Reset Code: %s\n", + gpg_strerror (rc)); + else + tty_printf ("Reset Code set.\n"); + } + else if (*answer == 'q' || *answer == 'Q') + { + break; + } + } + + agent_release_card_info (&info); +} + + +static void +print_sha1_fpr (estream_t 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 (estream_t fp, const unsigned char *fpr) +{ + int i; + + if (fpr) + { + for (i=0; i < 20 ; i++, fpr++) + es_fprintf (fp, "%02X", *fpr); + } + es_putc (':', fp); +} + + +static void +print_keygrip (estream_t fp, const unsigned char *grp) +{ + int i; + + if (opt.with_keygrip) + { + tty_fprintf (fp, " keygrip ....: "); + for (i=0; i < 20 ; i++, grp++) + tty_fprintf (fp, "%02X", *grp); + tty_fprintf (fp, "\n"); + } +} + + +static void +print_name (estream_t 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_buffer2 (fp, name, strlen (name), '\n'); + else + tty_print_utf8_string2 (NULL, name, strlen (name), 0); + } + else + tty_fprintf (fp, _("[not set]")); + tty_fprintf (fp, "\n"); +} + +static void +print_isoname (estream_t fp, const char *text, + const char *tag, const char *name) +{ + if (opt.with_colons) + es_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) + es_write_sanitized (fp, given, strlen (given), ":", NULL); + else if (fp) + print_utf8_buffer2 (fp, given, strlen (given), '\n'); + else + tty_print_utf8_string2 (NULL, given, strlen (given), 0); + + if (opt.with_colons) + es_putc (':', fp); + else if (*buf) + tty_fprintf (fp, " "); + } + + if (opt.with_colons) + es_write_sanitized (fp, buf, strlen (buf), ":", NULL); + else if (fp) + print_utf8_buffer2 (fp, buf, strlen (buf), '\n'); + else + tty_print_utf8_string2 (NULL, buf, strlen (buf), 0); + xfree (buf); + } + else + { + if (opt.with_colons) + es_putc (':', fp); + else + tty_fprintf (fp, _("[not set]")); + } + + if (opt.with_colons) + es_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); +} + + +/* Return true if the SHA1 fingerprint FPR consists only of 0xFF. */ +static int +fpr_is_ff (const char *fpr) +{ + int i; + + for (i=0; i < 20 && fpr[i] == '\xff'; i++) + ; + return (i == 20); +} + + +/* Print all available information about the current card. */ +static void +current_card_status (ctrl_t ctrl, estream_t fp, + char *serialno, size_t serialnobuflen) +{ + struct agent_card_info_s info; + PKT_public_key *pk = xcalloc (1, sizeof *pk); + kbnode_t keyblock = NULL; + int rc; + unsigned int uval; + const unsigned char *thefpr; + int i; + char *pesc; + + if (serialno && serialnobuflen) + *serialno = 0; + + rc = agent_scd_learn (&info, 0); + if (rc) + { + if (opt.with_colons) + es_fputs ("AID:::\n", fp); + log_error (_("OpenPGP card not available: %s\n"), gpg_strerror (rc)); + xfree (pk); + return; + } + + if (opt.with_colons) + es_fprintf (fp, "Reader:%s:", info.reader? info.reader : ""); + else + tty_fprintf (fp, "Reader ...........: %s\n", + info.reader? info.reader : "[none]"); + if (opt.with_colons) + es_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 ) + { + const char *name1, *name2; + if (info.apptype && !ascii_strcasecmp (info.apptype, "openpgp")) + goto openpgp; + else if (info.apptype && !ascii_strcasecmp (info.apptype, "NKS")) + { + name1 = "netkey"; + name2 = "NetKey"; + } + else if (info.apptype && !ascii_strcasecmp (info.apptype, "DINSIG")) + { + name1 = "dinsig"; + name2 = "DINSIG"; + } + else if (info.apptype && !ascii_strcasecmp (info.apptype, "P15")) + { + name1 = "pkcs15"; + name2 = "PKCS#15"; + } + else if (info.apptype && !ascii_strcasecmp (info.apptype, "GELDKARTE")) + { + name1 = "geldkarte"; + name2 = "Geldkarte"; + } + else if (info.apptype && !ascii_strcasecmp (info.apptype, "PIV")) + { + name1 = "piv"; + name2 = "PIV"; + } + else + { + name1 = "unknown"; + name2 = "Unknown"; + } + + if (opt.with_colons) + es_fprintf (fp, "%s-card:\n", name1); + else + tty_fprintf (fp, "Application type .: %s\n", name2); + + /* Try to update/create the shadow key here for non-OpenPGP cards. */ + agent_update_shadow_keys (); + + agent_release_card_info (&info); + xfree (pk); + return; + } + + openpgp: + if (!serialno) + ; + else if (strlen (info.serialno)+1 > serialnobuflen) + log_error ("serial number longer than expected\n"); + else + strcpy (serialno, info.serialno); + + if (opt.with_colons) + es_fputs ("openpgp-card:\n", fp); + else + tty_fprintf (fp, "Application type .: %s\n", "OpenPGP"); + + /* Try to update/create the shadow key here for OpenPGP cards. */ + agent_update_shadow_keys (); + + if (opt.with_colons) + { + es_fprintf (fp, "version:%.4s:\n", info.serialno+12); + uval = xtoi_2(info.serialno+16)*256 + xtoi_2 (info.serialno+18); + pesc = (info.manufacturer_name + ? percent_escape (info.manufacturer_name, NULL) : NULL); + es_fprintf (fp, "vendor:%04x:%s:\n", uval, pesc? pesc:""); + xfree (pesc); + es_fprintf (fp, "serial:%.8s:\n", info.serialno+20); + + print_isoname (fp, "Name of cardholder: ", "name", info.disp_name); + + es_fputs ("lang:", fp); + if (info.disp_lang) + es_write_sanitized (fp, info.disp_lang, strlen (info.disp_lang), + ":", NULL); + es_fputs (":\n", fp); + + es_fprintf (fp, "sex:%c:\n", (info.disp_sex == 1? 'm': + info.disp_sex == 2? 'f' : 'u')); + + es_fputs ("url:", fp); + if (info.pubkey_url) + es_write_sanitized (fp, info.pubkey_url, strlen (info.pubkey_url), + ":", NULL); + es_fputs (":\n", fp); + + es_fputs ("login:", fp); + if (info.login_data) + es_write_sanitized (fp, info.login_data, strlen (info.login_data), + ":", NULL); + es_fputs (":\n", fp); + + es_fprintf (fp, "forcepin:%d:::\n", !info.chv1_cached); + for (i=0; i < DIM (info.key_attr); i++) + if (info.key_attr[i].algo == PUBKEY_ALGO_RSA) + es_fprintf (fp, "keyattr:%d:%d:%u:\n", i+1, + info.key_attr[i].algo, info.key_attr[i].nbits); + else if (info.key_attr[i].algo == PUBKEY_ALGO_ECDH + || info.key_attr[i].algo == PUBKEY_ALGO_ECDSA + || info.key_attr[i].algo == PUBKEY_ALGO_EDDSA) + es_fprintf (fp, "keyattr:%d:%d:%s:\n", i+1, + info.key_attr[i].algo, info.key_attr[i].curve); + es_fprintf (fp, "maxpinlen:%d:%d:%d:\n", + info.chvmaxlen[0], info.chvmaxlen[1], info.chvmaxlen[2]); + es_fprintf (fp, "pinretry:%d:%d:%d:\n", + info.chvretry[0], info.chvretry[1], info.chvretry[2]); + es_fprintf (fp, "sigcount:%lu:::\n", info.sig_counter); + if (info.extcap.kdf) + { + const char *setup; + + if (info.kdf_do_enabled == 0) + setup = "off"; + else if (info.kdf_do_enabled == 1) + setup = "single"; + else + setup = "on"; + + es_fprintf (fp, "kdf:%s:\n", setup); + } + + for (i=0; i < 4; i++) + { + if (info.private_do[i]) + { + es_fprintf (fp, "private_do:%d:", i+1); + es_write_sanitized (fp, info.private_do[i], + strlen (info.private_do[i]), ":", NULL); + es_fputs (":\n", fp); + } + } + + es_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); + es_putc ('\n', fp); + es_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); + es_putc ('\n', fp); + es_fprintf (fp, "fprtime:%lu:%lu:%lu:\n", + (unsigned long)info.fpr1time, (unsigned long)info.fpr2time, + (unsigned long)info.fpr3time); + es_fputs ("grp:", fp); + print_sha1_fpr_colon (fp, info.grp1); + print_sha1_fpr_colon (fp, info.grp2); + print_sha1_fpr_colon (fp, info.grp3); + es_putc ('\n', fp); + } + 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", + info.manufacturer_name? info.manufacturer_name : "?"); + 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, "Salutation .......: %s\n", + info.disp_sex == 1? _("Mr."): + info.disp_sex == 2? _("Ms.") : ""); + 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")); + if (info.key_attr[0].algo) + { + tty_fprintf (fp, "Key attributes ...:"); + for (i=0; i < DIM (info.key_attr); i++) + if (info.key_attr[i].algo == PUBKEY_ALGO_RSA) + tty_fprintf (fp, " rsa%u", info.key_attr[i].nbits); + else if (info.key_attr[i].algo == PUBKEY_ALGO_ECDH + || info.key_attr[i].algo == PUBKEY_ALGO_ECDSA + || info.key_attr[i].algo == PUBKEY_ALGO_EDDSA) + { + const char *curve_for_print = "?"; + + if (info.key_attr[i].curve) + { + const char *oid; + oid = openpgp_curve_to_oid (info.key_attr[i].curve, + NULL, NULL); + if (oid) + curve_for_print = openpgp_oid_to_curve (oid, 0); + } + tty_fprintf (fp, " %s", curve_for_print); + } + tty_fprintf (fp, "\n"); + } + 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); + if (info.extcap.kdf) + { + const char *setup; + + if (info.kdf_do_enabled == 0) + setup = "off"; + else if (info.kdf_do_enabled == 1) + setup = "single"; + else + setup = "on"; + + tty_fprintf (fp, "KDF setting ......: %s\n", setup); + } + 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)); + print_keygrip (fp, info.grp1); + } + 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)); + print_keygrip (fp, info.grp2); + } + 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)); + print_keygrip (fp, info.grp3); + } + tty_fprintf (fp, "General key info..: "); + + thefpr = (info.fpr1valid? info.fpr1 : info.fpr2valid? info.fpr2 : + info.fpr3valid? info.fpr3 : NULL); + /* If the fingerprint is all 0xff, the key has no asssociated + OpenPGP certificate. */ + if ( thefpr && !fpr_is_ff (thefpr) + && !get_pubkey_byfprint (ctrl, pk, &keyblock, thefpr, 20)) + { + print_pubkey_info (ctrl, fp, pk); + if (keyblock) + print_card_key_info (fp, keyblock); + } + else + tty_fprintf (fp, "[none]\n"); + } + + release_kbnode (keyblock); + free_public_key (pk); + agent_release_card_info (&info); +} + + +/* Print all available information for specific card with SERIALNO. + Print all available information for current card when SERIALNO is NULL. + Or print for all cards when SERIALNO is "all". */ +void +card_status (ctrl_t ctrl, estream_t fp, const char *serialno) +{ + int err; + strlist_t card_list, sl; + char *serialno0, *serialno1; + int all_cards = 0; + int any_card = 0; + + if (serialno == NULL) + { + current_card_status (ctrl, fp, NULL, 0); + return; + } + + if (!strcmp (serialno, "all")) + all_cards = 1; + + err = agent_scd_serialno (&serialno0, NULL); + if (err) + { + if (gpg_err_code (err) != GPG_ERR_ENODEV && opt.verbose) + log_info (_("error getting serial number of card: %s\n"), + gpg_strerror (err)); + /* Nothing available. */ + return; + } + + err = agent_scd_cardlist (&card_list); + + for (sl = card_list; sl; sl = sl->next) + { + if (!all_cards && strcmp (serialno, sl->d)) + continue; + + if (any_card && !opt.with_colons) + tty_fprintf (fp, "\n"); + any_card = 1; + + err = agent_scd_serialno (&serialno1, sl->d); + if (err) + { + if (opt.verbose) + log_info (_("error getting serial number of card: %s\n"), + gpg_strerror (err)); + continue; + } + + current_card_status (ctrl, fp, NULL, 0); + xfree (serialno1); + + if (!all_cards) + goto leave; + } + + /* Select the original card again. */ + err = agent_scd_serialno (&serialno1, serialno0); + xfree (serialno1); + + leave: + xfree (serialno0); + free_strlist (card_list); +} + + +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 = NULL; + char *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); + rc = gpg_error (GPG_ERR_CANCELED); + goto leave; + } + + 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); + rc = gpg_error (GPG_ERR_TOO_LARGE); + goto leave; + } + + rc = agent_scd_setattr ("DISP-NAME", isoname, strlen (isoname)); + if (rc) + log_error ("error setting Name: %s\n", gpg_strerror (rc)); + + leave: + xfree (isoname); + write_sc_op_status (rc); + 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 (); + + rc = agent_scd_setattr ("PUBKEY-URL", url, strlen (url)); + if (rc) + log_error ("error setting URL: %s\n", gpg_strerror (rc)); + xfree (url); + write_sc_op_status (rc); + 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 (ctrl_t ctrl) +{ + 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 + { + 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) + { + strlist_t sl = NULL; + + add_to_strlist (&sl, info.pubkey_url); + rc = keyserver_fetch (ctrl, sl, KEYORG_URL); + free_strlist (sl); + } + else if (info.fpr1valid) + { + rc = keyserver_import_fprint (ctrl, info.fpr1, 20, opt.keyserver, 0); + } + } + + return rc; +} + + +#define MAX_GET_DATA_FROM_FILE 16384 + +/* Read data from file FNAME up to MAX_GET_DATA_FROM_FILE characters. + On error return -1 and store NULL at R_BUFFER; on success return + the number of bytes read and store the address of a newly allocated + buffer at R_BUFFER. */ +static int +get_data_from_file (const char *fname, char **r_buffer) +{ + estream_t fp; + char *data; + int n; + + *r_buffer = NULL; + + fp = es_fopen (fname, "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"), fname, strerror (errno)); + return -1; + } + + data = xtrymalloc (MAX_GET_DATA_FROM_FILE); + if (!data) + { + tty_printf (_("error allocating enough memory: %s\n"), strerror (errno)); + es_fclose (fp); + return -1; + } + + n = es_fread (data, 1, MAX_GET_DATA_FROM_FILE, fp); + es_fclose (fp); + if (n < 0) + { + tty_printf (_("error reading '%s': %s\n"), fname, strerror (errno)); + xfree (data); + return -1; + } + *r_buffer = data; + return n; +} + + +/* Write LENGTH bytes from BUFFER to file FNAME. Return 0 on + success. */ +static int +put_data_to_file (const char *fname, const void *buffer, size_t length) +{ + estream_t fp; + + fp = es_fopen (fname, "wb"); +#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 create '%s': %s\n"), fname, strerror (errno)); + return -1; + } + + if (length && es_fwrite (buffer, length, 1, fp) != 1) + { + tty_printf (_("error writing '%s': %s\n"), fname, strerror (errno)); + es_fclose (fp); + return -1; + } + es_fclose (fp); + return 0; +} + + +static int +change_login (const char *args) +{ + char *data; + int n; + int rc; + + if (args && *args == '<') /* Read it from a file */ + { + for (args++; spacep (args); args++) + ; + n = get_data_from_file (args, &data); + if (n < 0) + 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); + } + + rc = agent_scd_setattr ("LOGIN-DATA", data, n); + if (rc) + log_error ("error setting login data: %s\n", gpg_strerror (rc)); + xfree (data); + write_sc_op_status (rc); + return rc; +} + +static int +change_private_do (const char *args, int nr) +{ + char do_name[] = "PRIVATE-DO-X"; + char *data; + int n; + int rc; + + log_assert (nr >= 1 && nr <= 4); + do_name[11] = '0' + nr; + + if (args && (args = strchr (args, '<'))) /* Read it from a file */ + { + for (args++; spacep (args); args++) + ; + n = get_data_from_file (args, &data); + if (n < 0) + 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); + } + + rc = agent_scd_setattr (do_name, data, n); + if (rc) + log_error ("error setting private DO: %s\n", gpg_strerror (rc)); + xfree (data); + write_sc_op_status (rc); + return rc; +} + + +static int +change_cert (const char *args) +{ + char *data; + int n; + int rc; + + if (args && *args == '<') /* Read it from a file */ + { + for (args++; spacep (args); args++) + ; + n = get_data_from_file (args, &data); + if (n < 0) + return -1; + } + else + { + tty_printf ("usage error: redirection to file required\n"); + return -1; + } + + rc = agent_scd_writecert ("OPENPGP.3", data, n); + if (rc) + log_error ("error writing certificate to card: %s\n", gpg_strerror (rc)); + xfree (data); + write_sc_op_status (rc); + return rc; +} + + +static int +read_cert (const char *args) +{ + const char *fname; + void *buffer; + size_t length; + int rc; + + if (args && *args == '>') /* Write it to a file */ + { + for (args++; spacep (args); args++) + ; + fname = args; + } + else + { + tty_printf ("usage error: redirection to file required\n"); + return -1; + } + + rc = agent_scd_readcert ("OPENPGP.3", &buffer, &length); + if (rc) + log_error ("error reading certificate from card: %s\n", gpg_strerror (rc)); + else + rc = put_data_to_file (fname, buffer, length); + xfree (buffer); + write_sc_op_status (rc); + 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)); + if (rc) + log_error ("error setting lang: %s\n", gpg_strerror (rc)); + xfree (data); + write_sc_op_status (rc); + return rc; +} + + +static int +change_sex (void) +{ + char *data; + const char *str; + int rc; + + data = cpr_get ("cardedit.change_sex", + _("Salutation (M = Mr., F = Ms., 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); + if (rc) + log_error ("error setting salutation: %s\n", gpg_strerror (rc)); + xfree (data); + write_sc_op_status (rc); + 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); + if (rc) + log_error ("error setting cafpr: %s\n", gpg_strerror (rc)); + write_sc_op_status (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); + if (rc) + log_error ("error toggling signature PIN flag: %s\n", gpg_strerror (rc)); + write_sc_op_status (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) + rc = agent_scd_getattr ("APPTYPE", info); + if (rc || !info->apptype || ascii_strcasecmp (info->apptype, "openpgp")) + { + 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) + rc = agent_scd_getattr ("EXTCAP", info); + if (!rc) + rc = agent_scd_getattr ("KEY-ATTR", 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; + + *forced_chv1 = !info->chv1_cached; + if (*forced_chv1) + { /* Switch off 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); + 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)); + write_sc_op_status (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); + if (rc) + { + log_error ("error setting forced signature PIN flag: %s\n", + gpg_strerror (rc)); + } + } +} + + +/* 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"); +} + + +/* Helper for the key generation/edit functions. */ +static int +replace_existing_key_p (struct agent_card_info_s *info, int keyno) +{ + log_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 1; + } + return 0; +} + + +static void +show_keysize_warning (void) +{ + static int shown; + + if (shown) + return; + shown = 1; + tty_printf + (_("Note: There is no guarantee that the card " + "supports the requested size.\n" + " If the key generation does not succeed, " + "please check the\n" + " documentation of your card to see what " + "sizes are allowed.\n")); +} + + +/* Ask for the size of a card key. NBITS is the current size + configured for the card. Returns 0 to use the default size + (i.e. NBITS) or the selected size. */ +static unsigned int +ask_card_rsa_keysize (unsigned int nbits) +{ + unsigned int min_nbits = 1024; + unsigned int max_nbits = 4096; + char *prompt, *answer; + unsigned int req_nbits; + + for (;;) + { + prompt = xasprintf (_("What keysize do you want? (%u) "), nbits); + answer = cpr_get ("cardedit.genkeys.size", prompt); + cpr_kill_prompt (); + req_nbits = *answer? atoi (answer): nbits; + xfree (prompt); + xfree (answer); + + if (req_nbits != nbits && (req_nbits % 32) ) + { + req_nbits = ((req_nbits + 31) / 32) * 32; + tty_printf (_("rounded up to %u bits\n"), req_nbits); + } + + if (req_nbits == nbits) + return 0; /* Use default. */ + + if (req_nbits < min_nbits || req_nbits > max_nbits) + { + tty_printf (_("%s keysizes must be in the range %u-%u\n"), + "RSA", min_nbits, max_nbits); + } + else + return req_nbits; + } +} + +/* Ask for the key attribute of a card key. CURRENT is the current + attribute configured for the card. KEYNO is the number of the key + used to select the prompt. Returns NULL to use the default + attribute or the selected attribute structure. */ +static struct key_attr * +ask_card_keyattr (int keyno, const struct key_attr *current) +{ + struct key_attr *key_attr = NULL; + char *answer = NULL; + int algo; + + tty_printf (_("Changing card key attribute for: ")); + if (keyno == 0) + tty_printf (_("Signature key\n")); + else if (keyno == 1) + tty_printf (_("Encryption key\n")); + else + tty_printf (_("Authentication key\n")); + + tty_printf (_("Please select what kind of key you want:\n")); + tty_printf (_(" (%d) RSA\n"), 1 ); + tty_printf (_(" (%d) ECC\n"), 2 ); + + for (;;) + { + xfree (answer); + answer = cpr_get ("cardedit.genkeys.algo", _("Your selection? ")); + cpr_kill_prompt (); + algo = *answer? atoi (answer) : 0; + + if (!*answer || algo == 1 || algo == 2) + break; + else + tty_printf (_("Invalid selection.\n")); + } + + if (algo == 0) + goto leave; + + key_attr = xmalloc (sizeof (struct key_attr)); + + if (algo == 1) + { + unsigned int nbits, result_nbits; + + if (current->algo == PUBKEY_ALGO_RSA) + nbits = current->nbits; + else + nbits = 2048; + + result_nbits = ask_card_rsa_keysize (nbits); + if (result_nbits == 0) + { + if (current->algo == PUBKEY_ALGO_RSA) + { + xfree (key_attr); + key_attr = NULL; + } + else + result_nbits = nbits; + } + + if (key_attr) + { + key_attr->algo = PUBKEY_ALGO_RSA; + key_attr->nbits = result_nbits; + } + } + else + { + const char *curve; + const char *oid_str; + + if (current->algo == PUBKEY_ALGO_RSA) + { + if (keyno == 1) + /* Encryption key */ + algo = PUBKEY_ALGO_ECDH; + else /* Signature key or Authentication key */ + algo = PUBKEY_ALGO_ECDSA; + curve = NULL; + } + else + { + algo = current->algo; + curve = current->curve; + } + + curve = ask_curve (&algo, NULL, curve); + if (curve) + { + key_attr->algo = algo; + oid_str = openpgp_curve_to_oid (curve, NULL, NULL); + key_attr->curve = openpgp_oid_to_curve (oid_str, 0); + } + else + { + xfree (key_attr); + key_attr = NULL; + } + } + + leave: + if (key_attr) + { + if (key_attr->algo == PUBKEY_ALGO_RSA) + tty_printf (_("The card will now be re-configured" + " to generate a key of %u bits\n"), key_attr->nbits); + else if (key_attr->algo == PUBKEY_ALGO_ECDH + || key_attr->algo == PUBKEY_ALGO_ECDSA + || key_attr->algo == PUBKEY_ALGO_EDDSA) + tty_printf (_("The card will now be re-configured" + " to generate a key of type: %s\n"), key_attr->curve), + + show_keysize_warning (); + } + + return key_attr; +} + + + +/* Change the key attribute of key KEYNO (0..2) and show an error + * message if that fails. */ +static gpg_error_t +do_change_keyattr (int keyno, const struct key_attr *key_attr) +{ + gpg_error_t err = 0; + char args[100]; + + if (key_attr->algo == PUBKEY_ALGO_RSA) + snprintf (args, sizeof args, "--force %d 1 rsa%u", keyno+1, + key_attr->nbits); + else if (key_attr->algo == PUBKEY_ALGO_ECDH + || key_attr->algo == PUBKEY_ALGO_ECDSA + || key_attr->algo == PUBKEY_ALGO_EDDSA) + snprintf (args, sizeof args, "--force %d %d %s", + keyno+1, key_attr->algo, key_attr->curve); + else + { + log_error (_("public key algorithm %d (%s) is not supported\n"), + key_attr->algo, gcry_pk_algo_name (key_attr->algo)); + return gpg_error (GPG_ERR_PUBKEY_ALGO); + } + + err = agent_scd_setattr ("KEY-ATTR", args, strlen (args)); + if (err) + log_error (_("error changing key attribute for key %d: %s\n"), + keyno+1, gpg_strerror (err)); + return err; +} + + +static void +key_attr (void) +{ + struct agent_card_info_s info; + gpg_error_t err; + int keyno; + + err = get_info_for_key_operation (&info); + if (err) + { + log_error (_("error getting card info: %s\n"), gpg_strerror (err)); + return; + } + + if (!(info.is_v2 && info.extcap.aac)) + { + log_error (_("This command is not supported by this card\n")); + goto leave; + } + + for (keyno = 0; keyno < DIM (info.key_attr); keyno++) + { + struct key_attr *key_attr; + + if ((key_attr = ask_card_keyattr (keyno, &info.key_attr[keyno]))) + { + err = do_change_keyattr (keyno, key_attr); + xfree (key_attr); + if (err) + { + /* Error: Better read the default key attribute again. */ + agent_release_card_info (&info); + if (get_info_for_key_operation (&info)) + goto leave; + /* Ask again for this key. */ + keyno--; + } + } + } + + leave: + agent_release_card_info (&info); +} + + +static void +generate_card_keys (ctrl_t ctrl) +{ + struct agent_card_info_s info; + int forced_chv1; + int want_backup; + + if (get_info_for_key_operation (&info)) + return; + + if (info.extcap.ki) + { + char *answer; + + /* FIXME: Should be something like cpr_get_bool so that a status + GET_BOOL will be emitted. */ + 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/*(default to Yes)*/); + cpr_kill_prompt (); + xfree (answer); + } + else + want_backup = 0; + + 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; + } + } + + /* If no displayed name has been set, we assume that this is a fresh + card and print a hint about the default PINs. */ + 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; + + generate_keypair (ctrl, 1, NULL, info.serialno, want_backup); + + 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. */ +gpg_error_t +card_generate_subkey (ctrl_t ctrl, kbnode_t pub_keyblock) +{ + gpg_error_t err; + struct agent_card_info_s info; + int forced_chv1 = 0; + int keyno; + + err = get_info_for_key_operation (&info); + if (err) + return err; + + 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); + err = gpg_error (GPG_ERR_CANCELED); + 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) < 0) + { + err = gpg_error (GPG_ERR_CANCELED); + goto leave; + } + + err = check_pin_for_key_operation (&info, &forced_chv1); + if (err) + goto leave; + + err = generate_card_subkeypair (ctrl, pub_keyblock, keyno, info.serialno); + + leave: + agent_release_card_info (&info); + restore_forced_chv1 (&forced_chv1); + return err; +} + + +/* 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) +{ + struct agent_card_info_s info; + int okay = 0; + unsigned int nbits; + int allow_keyno[3]; + int keyno; + PKT_public_key *pk; + gpg_error_t err; + char *hexgrip; + int rc; + gnupg_isotime_t timebuf; + + log_assert (node->pkt->pkttype == PKT_PUBLIC_KEY + || node->pkt->pkttype == PKT_PUBLIC_SUBKEY); + + pk = node->pkt->pkt.public_key; + + if (get_info_for_key_operation (&info)) + return 0; + + if (!info.extcap.ki) + { + tty_printf ("The card does not support the import of keys\n"); + tty_printf ("\n"); + goto leave; + } + + nbits = nbits_from_pk (pk); + + if (!info.is_v2 && nbits != 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|PUBKEY_USAGE_CERT))); + 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]) + { + if (info.is_v2 && !info.extcap.aac + && info.key_attr[keyno-1].nbits != nbits) + { + tty_printf ("Key does not match the card's capability.\n"); + } + else + break; /* Okay. */ + } + else + tty_printf(_("Invalid selection.\n")); + } + + if ((rc = replace_existing_key_p (&info, keyno)) < 0) + goto leave; + + err = hexkeygrip_from_pk (pk, &hexgrip); + if (err) + goto leave; + + epoch2isotime (timebuf, (time_t)pk->timestamp); + rc = agent_keytocard (hexgrip, keyno, rc, info.serialno, timebuf); + + if (rc) + log_error (_("KEYTOCARD failed: %s\n"), gpg_strerror (rc)); + else + okay = 1; + xfree (hexgrip); + + leave: + agent_release_card_info (&info); + return okay; +} + + + +/* Direct sending of an hex encoded APDU with error printing. */ +static gpg_error_t +send_apdu (const char *hexapdu, const char *desc, unsigned int ignore) +{ + gpg_error_t err; + unsigned int sw; + + err = agent_scd_apdu (hexapdu, &sw); + if (err) + tty_printf ("sending card command %s failed: %s\n", desc, + gpg_strerror (err)); + else if (!hexapdu + || !strcmp (hexapdu, "undefined") + || !strcmp (hexapdu, "reset-keep-lock") + || !strcmp (hexapdu, "lock") + || !strcmp (hexapdu, "trylock") + || !strcmp (hexapdu, "unlock")) + ; /* Ignore pseudo APDUs. */ + else if (ignore == 0xffff) + ; /* Ignore all status words. */ + else if (sw != 0x9000) + { + switch (sw) + { + case 0x6285: err = gpg_error (GPG_ERR_OBJ_TERM_STATE); break; + case 0x6982: err = gpg_error (GPG_ERR_BAD_PIN); break; + case 0x6985: err = gpg_error (GPG_ERR_USE_CONDITIONS); break; + default: err = gpg_error (GPG_ERR_CARD); + } + if (!(ignore && ignore == sw)) + tty_printf ("card command %s failed: %s (0x%04x)\n", desc, + gpg_strerror (err), sw); + } + return err; +} + + +/* Do a factory reset after confirmation. */ +static void +factory_reset (void) +{ + struct agent_card_info_s info; + gpg_error_t err; + char *answer = NULL; + int termstate = 0; + int i; + int locked = 0; + + /* The code below basically does the same what this + gpg-connect-agent script does: + + scd reset + scd serialno undefined + scd apdu 00 A4 04 00 06 D2 76 00 01 24 01 + scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40 + scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40 + scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40 + scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40 + scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40 + scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40 + scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40 + scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40 + scd apdu 00 e6 00 00 + scd apdu 00 44 00 00 + scd reset + /echo Card has been reset to factory defaults + + but tries to find out something about the card first. + */ + + err = agent_scd_learn (&info, 0); + if (gpg_err_code (err) == GPG_ERR_OBJ_TERM_STATE + && gpg_err_source (err) == GPG_ERR_SOURCE_SCD) + termstate = 1; + else if (err) + { + log_error (_("OpenPGP card not available: %s\n"), gpg_strerror (err)); + goto leave; + } + + if (!termstate) + { + log_info (_("OpenPGP card no. %s detected\n"), + info.serialno? info.serialno : "[none]"); + if (!(info.status_indicator == 3 || info.status_indicator == 5)) + { + /* Note: We won't see status-indicator 3 here because it is not + possible to select a card application in termination state. */ + log_error (_("This command is not supported by this card\n")); + goto leave; + } + + tty_printf ("\n"); + log_info (_("Note: This command destroys all keys stored on the card!\n")); + tty_printf ("\n"); + if (!cpr_get_answer_is_yes ("cardedit.factory-reset.proceed", + _("Continue? (y/N) "))) + goto leave; + + + answer = cpr_get ("cardedit.factory-reset.really", + _("Really do a factory reset? (enter \"yes\") ")); + cpr_kill_prompt (); + trim_spaces (answer); + if (strcmp (answer, "yes")) + goto leave; + + /* We need to select a card application before we can send APDUs + to the card without scdaemon doing anything on its own. We + then lock the connection so that other tools (e.g. Kleopatra) + don't try a new select. */ + err = send_apdu ("lock", "locking connection ", 0); + if (err) + goto leave; + locked = 1; + err = send_apdu ("reset-keep-lock", "reset", 0); + if (err) + goto leave; + err = send_apdu ("undefined", "dummy select ", 0); + if (err) + goto leave; + + /* Select the OpenPGP application. */ + err = send_apdu ("00A4040006D27600012401", "SELECT AID", 0); + if (err) + goto leave; + + /* Do some dummy verifies with wrong PINs to set the retry + counter to zero. We can't easily use the card version 2.1 + feature of presenting the admin PIN to allow the terminate + command because there is no machinery in scdaemon to catch + the verify command and ask for the PIN when the "APDU" + command is used. */ + /* Here, the length of dummy wrong PIN is 32-byte, also + supporting authentication with KDF DO. */ + for (i=0; i < 4; i++) + send_apdu ("0020008120" + "40404040404040404040404040404040" + "40404040404040404040404040404040", "VERIFY", 0xffff); + for (i=0; i < 4; i++) + send_apdu ("0020008320" + "40404040404040404040404040404040" + "40404040404040404040404040404040", "VERIFY", 0xffff); + + /* Send terminate datafile command. */ + err = send_apdu ("00e60000", "TERMINATE DF", 0x6985); + if (err) + goto leave; + } + + /* Send activate datafile command. This is used without + confirmation if the card is already in termination state. */ + err = send_apdu ("00440000", "ACTIVATE DF", 0); + if (err) + goto leave; + + /* Finally we reset the card reader once more. */ + err = send_apdu ("reset-keep-lock", "reset", 0); + + /* Then, connect the card again. */ + if (!err) + { + char *serialno0; + + err = agent_scd_serialno (&serialno0, NULL); + if (!err) + xfree (serialno0); + } + + leave: + if (locked) + send_apdu ("unlock", "unlocking connection ", 0); + xfree (answer); + agent_release_card_info (&info); +} + + +#define USER_PIN_DEFAULT "123456" +#define ADMIN_PIN_DEFAULT "12345678" +#define KDF_DATA_LENGTH_MIN 90 +#define KDF_DATA_LENGTH_MAX 110 + +/* Generate KDF data. */ +static gpg_error_t +gen_kdf_data (unsigned char *data, int single_salt) +{ + const unsigned char h0[] = { 0x81, 0x01, 0x03, + 0x82, 0x01, 0x08, + 0x83, 0x04 }; + const unsigned char h1[] = { 0x84, 0x08 }; + const unsigned char h2[] = { 0x85, 0x08 }; + const unsigned char h3[] = { 0x86, 0x08 }; + const unsigned char h4[] = { 0x87, 0x20 }; + const unsigned char h5[] = { 0x88, 0x20 }; + unsigned char *p, *salt_user, *salt_admin; + unsigned char s2k_char; + unsigned int iterations; + unsigned char count_4byte[4]; + gpg_error_t err = 0; + + p = data; + + s2k_char = encode_s2k_iterations (0); + iterations = S2K_DECODE_COUNT (s2k_char); + count_4byte[0] = (iterations >> 24) & 0xff; + count_4byte[1] = (iterations >> 16) & 0xff; + count_4byte[2] = (iterations >> 8) & 0xff; + count_4byte[3] = (iterations & 0xff); + + memcpy (p, h0, sizeof h0); + p += sizeof h0; + memcpy (p, count_4byte, sizeof count_4byte); + p += sizeof count_4byte; + memcpy (p, h1, sizeof h1); + salt_user = (p += sizeof h1); + gcry_randomize (p, 8, GCRY_STRONG_RANDOM); + p += 8; + + if (single_salt) + salt_admin = salt_user; + else + { + memcpy (p, h2, sizeof h2); + p += sizeof h2; + gcry_randomize (p, 8, GCRY_STRONG_RANDOM); + p += 8; + memcpy (p, h3, sizeof h3); + salt_admin = (p += sizeof h3); + gcry_randomize (p, 8, GCRY_STRONG_RANDOM); + p += 8; + } + + memcpy (p, h4, sizeof h4); + p += sizeof h4; + err = gcry_kdf_derive (USER_PIN_DEFAULT, strlen (USER_PIN_DEFAULT), + GCRY_KDF_ITERSALTED_S2K, DIGEST_ALGO_SHA256, + salt_user, 8, iterations, 32, p); + p += 32; + if (!err) + { + memcpy (p, h5, sizeof h5); + p += sizeof h5; + err = gcry_kdf_derive (ADMIN_PIN_DEFAULT, strlen (ADMIN_PIN_DEFAULT), + GCRY_KDF_ITERSALTED_S2K, DIGEST_ALGO_SHA256, + salt_admin, 8, iterations, 32, p); + } + + return err; +} + +/* Setup KDF data object which is used for PIN authentication. */ +static void +kdf_setup (const char *args) +{ + struct agent_card_info_s info; + gpg_error_t err; + unsigned char kdf_data[KDF_DATA_LENGTH_MAX]; + int single = (*args != 0); + + memset (&info, 0, sizeof info); + + err = agent_scd_getattr ("EXTCAP", &info); + if (err) + { + log_error (_("error getting card info: %s\n"), gpg_strerror (err)); + return; + } + + if (!info.extcap.kdf) + { + log_error (_("This command is not supported by this card\n")); + goto leave; + } + + err = gen_kdf_data (kdf_data, single); + if (err) + goto leave_error; + + err = agent_scd_setattr ("KDF", kdf_data, + single ? KDF_DATA_LENGTH_MIN : KDF_DATA_LENGTH_MAX); + if (err) + goto leave_error; + + err = agent_scd_getattr ("KDF", &info); + + leave_error: + if (err) + log_error (_("error for setup KDF: %s\n"), gpg_strerror (err)); + + leave: + agent_release_card_info (&info); +} + + + +/* 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, cmdWRITECERT, + cmdREADCERT, cmdUNBLOCK, cmdFACTORYRESET, cmdKDFSETUP, + cmdKEYATTR, + 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")}, + { "salutation",cmdSEX , 1, N_("change card holder's salutation")}, + { "sex" ,cmdSEX , 1, NULL }, /* Backward compatibility. */ + { "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")}, + { "unblock" , cmdUNBLOCK,0, N_("unblock the PIN using a Reset Code") }, + { "factory-reset", cmdFACTORYRESET, 1, N_("destroy all keys and data")}, + { "kdf-setup", cmdKDFSETUP, 1, N_("setup KDF for PIN authentication")}, + { "key-attr", cmdKEYATTR, 1, N_("change the key attribute")}, + /* Note, that we do not announce these command yet. */ + { "privatedo", cmdPRIVATEDO, 0, NULL }, + { "readcert", cmdREADCERT, 0, NULL }, + { "writecert", cmdWRITECERT, 1, NULL }, + { NULL, cmdINVCMD, 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 ** +card_edit_completion(const char *text, int start, int end) +{ + (void)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*/ + +/* Menu to edit all user changeable values on an OpenPGP card. Only + Key creation is not handled here. */ +void +card_edit (ctrl_t ctrl, strlist_t commands) +{ + enum cmdids cmd = cmdNOP; + int have_commands = !!commands; + int redisplay = 1; + char *answer = NULL; + int 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 = ""; + const char *arg_rest = ""; + char *p; + int i; + int cmd_admin_only; + + tty_printf("\n"); + if (redisplay) + { + if (opt.with_colons) + { + current_card_status (ctrl, es_stdout, + serialnobuf, DIM (serialnobuf)); + fflush (stdout); + } + else + { + current_card_status (ctrl, 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) + { + tty_enable_completion (card_edit_completion); + answer = cpr_get_no_help("cardedit.prompt", _("gpg/card> ")); + cpr_kill_prompt(); + tty_disable_completion (); + } + 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; + arg_rest = p; + while (digitp (arg_rest)) + arg_rest++; + while (spacep (arg_rest)) + arg_rest++; + } + + 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("%-14s %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 (ctrl); + 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 cmdWRITECERT: + if ( arg_number != 3 ) + tty_printf ("usage: writecert 3 < FILE\n"); + else + change_cert (arg_rest); + break; + + case cmdREADCERT: + if ( arg_number != 3 ) + tty_printf ("usage: readcert 3 > FILE\n"); + else + read_cert (arg_rest); + break; + + case cmdFORCESIG: + toggle_forcesig (); + break; + + case cmdGENERATE: + generate_card_keys (ctrl); + break; + + case cmdPASSWD: + change_pin (0, allow_admin); + break; + + case cmdUNBLOCK: + change_pin (1, allow_admin); + break; + + case cmdFACTORYRESET: + factory_reset (); + break; + + case cmdKDFSETUP: + kdf_setup (arg_string); + break; + + case cmdKEYATTR: + key_attr (); + 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/cipher.c b/g10/cipher.c new file mode 100644 index 0000000..f577c97 --- /dev/null +++ b/g10/cipher.c @@ -0,0 +1,187 @@ +/* cipher.c - En-/De-ciphering filter + * Copyright (C) 1998-2003, 2006, 2009 Free Software Foundation, Inc. + * Copyright (C) 1998-2003, 2006, 2009, 2017 Werner koch + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * SPDX-License-Identifier: GPL-3.0+ + */ + +#include +#include +#include +#include +#include + +#include "gpg.h" +#include "../common/status.h" +#include "../common/iobuf.h" +#include "../common/util.h" +#include "filter.h" +#include "packet.h" +#include "options.h" +#include "main.h" +#include "../common/i18n.h" +#include "../common/status.h" + + +#define MIN_PARTIAL_SIZE 512 + + +static void +write_header (cipher_filter_context_t *cfx, iobuf_t a) +{ + gcry_error_t err; + PACKET pkt; + PKT_encrypted ed; + byte temp[18]; + unsigned int blocksize; + unsigned int nprefix; + + blocksize = openpgp_cipher_get_algo_blklen (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; + if (cfx->dek->use_mdc) + { + ed.mdc_method = DIGEST_ALGO_SHA1; + gcry_md_open (&cfx->mdc_hash, DIGEST_ALGO_SHA1, 0); + if (DBG_HASHING) + gcry_md_debug (cfx->mdc_hash, "creatmdc"); + } + else + { + log_info (_("WARNING: " + "encrypting without integrity protection is dangerous\n")); + log_info (_("Hint: Do not use option %s\n"), "--rfc2440"); + } + + write_status_printf (STATUS_BEGIN_ENCRYPTION, "%d %d", + ed.mdc_method, cfx->dek->algo); + + 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; + gcry_randomize (temp, nprefix, GCRY_STRONG_RANDOM ); + temp[nprefix] = temp[nprefix-2]; + temp[nprefix+1] = temp[nprefix-1]; + print_cipher_algo_note (cfx->dek->algo); + err = openpgp_cipher_open (&cfx->cipher_hd, + cfx->dek->algo, + GCRY_CIPHER_MODE_CFB, + (GCRY_CIPHER_SECURE + | ((cfx->dek->use_mdc || cfx->dek->algo >= 100)? + 0 : GCRY_CIPHER_ENABLE_SYNC))); + if (err) + { + /* We should never get an error here cause we already checked, + * that the algorithm is available. */ + BUG(); + } + + /* log_hexdump ("thekey", cfx->dek->key, cfx->dek->keylen); */ + gcry_cipher_setkey (cfx->cipher_hd, cfx->dek->key, cfx->dek->keylen); + gcry_cipher_setiv (cfx->cipher_hd, NULL, 0); + /* log_hexdump ("prefix", temp, nprefix+2); */ + if (cfx->mdc_hash) /* Hash the "IV". */ + gcry_md_write (cfx->mdc_hash, temp, nprefix+2 ); + gcry_cipher_encrypt (cfx->cipher_hd, temp, nprefix+2, NULL, 0); + gcry_cipher_sync (cfx->cipher_hd); + iobuf_write (a, temp, nprefix+2); + + cfx->short_blklen_warn = (blocksize < 16); + cfx->short_blklen_count = nprefix+2; + + cfx->wrote_header = 1; +} + + +/* + * This filter is used to en/de-cipher data with a symmetric algorithm + */ +int +cipher_filter_cfb (void *opaque, int control, + iobuf_t a, byte *buf, size_t *ret_len) +{ + cipher_filter_context_t *cfx = opaque; + size_t size = *ret_len; + int rc = 0; + + if (control == IOBUFCTRL_UNDERFLOW) /* decrypt */ + { + rc = -1; /* not yet used */ + } + else if (control == IOBUFCTRL_FLUSH) /* encrypt */ + { + log_assert (a); + if (!cfx->wrote_header) + write_header (cfx, a); + if (cfx->mdc_hash) + gcry_md_write (cfx->mdc_hash, buf, size); + gcry_cipher_encrypt (cfx->cipher_hd, buf, size, NULL, 0); + if (cfx->short_blklen_warn) + { + cfx->short_blklen_count += size; + if (cfx->short_blklen_count > (150 * 1024 * 1024)) + { + log_info ("WARNING: encrypting more than %d MiB with algorithm " + "%s should be avoided\n", 150, + openpgp_cipher_algo_name (cfx->dek->algo)); + cfx->short_blklen_warn = 0; /* Don't show again. */ + } + } + + rc = iobuf_write (a, buf, size); + } + else if (control == IOBUFCTRL_FREE) + { + if (cfx->mdc_hash) + { + byte *hash; + int hashlen = gcry_md_get_algo_dlen (gcry_md_get_algo(cfx->mdc_hash)); + byte temp[22]; + + log_assert (hashlen == 20); + /* We must hash the prefix of the MDC packet here. */ + temp[0] = 0xd3; + temp[1] = 0x14; + gcry_md_putc (cfx->mdc_hash, temp[0]); + gcry_md_putc (cfx->mdc_hash, temp[1]); + + gcry_md_final (cfx->mdc_hash); + hash = gcry_md_read (cfx->mdc_hash, 0); + memcpy(temp+2, hash, 20); + gcry_cipher_encrypt (cfx->cipher_hd, temp, 22, NULL, 0); + gcry_md_close (cfx->mdc_hash); cfx->mdc_hash = NULL; + if (iobuf_write( a, temp, 22)) + log_error ("writing MDC packet failed\n"); + } + + gcry_cipher_close (cfx->cipher_hd); + } + else if (control == IOBUFCTRL_DESC) + { + mem2str (buf, "cipher_filter_cfb", *ret_len); + } + + return rc; +} diff --git a/g10/compress-bz2.c b/g10/compress-bz2.c new file mode 100644 index 0000000..45aa40d --- /dev/null +++ b/g10/compress-bz2.c @@ -0,0 +1,254 @@ +/* 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 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include +#include +#include /* Early versions of bzlib (1.0) require stdio.h */ + +#include "gpg.h" +#include "../common/util.h" +#include + +#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 rc; + 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( (rc=iobuf_write( a, zfx->outbuf, n )) ) + { + log_debug("bzCompress: iobuf_write failed\n"); + return rc; + } + } + 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; + int eofseen = 0; + + 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 ) + { + eofseen = 1; + nread = 0; + } + n += nread; + bzs->avail_in = n; + } + if (!eofseen) + 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 ); + else if (zrc == BZ_OK && eofseen + && !bzs->avail_in && bzs->avail_out > 0) + { + log_error ("unexpected EOF in bz2lib\n"); + rc = GPG_ERR_BAD_DATA; + break; + } + } + 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 ) + mem2str (buf, "compress_filter", *ret_len); + return rc; +} diff --git a/g10/compress.c b/g10/compress.c new file mode 100644 index 0000000..e7a6f2b --- /dev/null +++ b/g10/compress.c @@ -0,0 +1,377 @@ +/* compress.c - compress filter + * Copyright (C) 1998, 1999, 2000, 2001, 2002, + * 2003, 2006, 2010 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +/* 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 +#include +#include +#include +#include +#include +#ifdef HAVE_ZIP +# include +# if defined(__riscos__) && defined(USE_ZLIBRISCOS) +# include "zlib-riscos.h" +# endif +#endif + +#include "gpg.h" +#include "../common/util.h" +#include "packet.h" +#include "filter.h" +#include "main.h" +#include "options.h" + + +#ifdef __riscos__ +#define BYTEF_CAST(a) ((Bytef *)(a)) +#else +#define BYTEF_CAST(a) (a) +#endif + + + +int compress_filter_bz2( void *opaque, int control, + IOBUF a, byte *buf, size_t *ret_len); + +#ifdef HAVE_ZIP +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 rc; + int zrc; + unsigned n; + + do { + zs->next_out = BYTEF_CAST (zfx->outbuf); + 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( (rc=iobuf_write( a, zfx->outbuf, n )) ) { + log_debug("deflate: iobuf_write failed\n"); + return rc; + } + } 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; + int leave = 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 ) + zs->next_in = BYTEF_CAST (zfx->inbuf); + count = zfx->inbufsize - n; + nread = iobuf_read( a, zfx->inbuf + n, count ); + if( nread == -1 ) nread = 0; + n += nread; + /* Algo 1 has no zlib header which requires us to give + * inflate an extra dummy byte to read. To be on the safe + * side we allow for up to 4 ff bytes. */ + if( nread < count && zfx->algo == 1 && zfx->algo1hack < 4) { + *(zfx->inbuf + n) = 0xFF; + zfx->algo1hack++; + n++; + leave = 1; + } + 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); + zrc = inflate ( zs, Z_SYNC_FLUSH ); + 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 + && !leave); + + *ret_len = zfx->outbufsize - zs->avail_out; + if( DBG_FILTER ) + log_debug("do_uncompress: returning %u bytes (%u ignored)\n", + (unsigned int)*ret_len, (unsigned int)zs->avail_in ); + 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; + } + + zs->next_out = BYTEF_CAST (buf); + 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; + /* Fixme: We should force a new CTB here: + cd.new_ctb = zfx->new_ctb; + */ + 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; + } + + zs->next_in = BYTEF_CAST (buf); + 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 ) { + zs->next_in = BYTEF_CAST (buf); + 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 ) + mem2str (buf, "compress_filter", *ret_len); + return rc; +} +#endif /*HAVE_ZIP*/ + +static void +release_context (compress_filter_context_t *ctx) +{ + xfree(ctx->inbuf); + ctx->inbuf = NULL; + xfree(ctx->outbuf); + ctx->outbuf = NULL; + xfree (ctx); +} + +/**************** + * Handle a compressed packet + */ +int +handle_compressed (ctrl_t ctrl, void *procctx, PKT_compressed *cd, + int (*callback)(IOBUF, void *), void *passthru ) +{ + int rc; + + if(check_compress_algo(cd->algorithm)) + return GPG_ERR_COMPR_ALGO; + if(cd->algorithm) { + compress_filter_context_t *cfx; + + cfx = xmalloc_clear (sizeof *cfx); + cfx->release = release_context; + cfx->algo = cd->algorithm; + if (push_compress_filter(cd->buf, cfx, cd->algorithm)) + xfree (cfx); + } + if( callback ) + rc = callback(cd->buf, passthru ); + else + rc = proc_packets (ctrl,procctx, cd->buf); + cd->buf = NULL; + return rc; +} + +gpg_error_t +push_compress_filter(IOBUF out,compress_filter_context_t *zfx,int algo) +{ + return push_compress_filter2(out,zfx,algo,0); +} + + +/* Push a compress filter and return 0 if that succeeded. */ +gpg_error_t +push_compress_filter2(IOBUF out,compress_filter_context_t *zfx, + int algo,int rel) +{ + gpg_error_t err = gpg_error (GPG_ERR_FALSE); + + if(algo>=0) + zfx->algo=algo; + else + zfx->algo=DEFAULT_COMPRESS_ALGO; + + switch(zfx->algo) + { + case COMPRESS_ALGO_NONE: + break; + +#ifdef HAVE_ZIP + case COMPRESS_ALGO_ZIP: + case COMPRESS_ALGO_ZLIB: + iobuf_push_filter2(out,compress_filter,zfx,rel); + err = 0; + break; +#endif + +#ifdef HAVE_BZIP2 + case COMPRESS_ALGO_BZIP2: + iobuf_push_filter2(out,compress_filter_bz2,zfx,rel); + err = 0; + break; +#endif + + default: + BUG(); + } + + return err; +} diff --git a/g10/cpr.c b/g10/cpr.c new file mode 100644 index 0000000..bc4b715 --- /dev/null +++ b/g10/cpr.c @@ -0,0 +1,659 @@ +/* status.c - Status message and command-fd interface + * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, + * 2004, 2005, 2006, 2010 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include +#include +#include +#include +#include +#include +#ifdef HAVE_SIGNAL_H +# include +#endif + +#include "gpg.h" +#include "../common/util.h" +#include "../common/status.h" +#include "../common/ttyio.h" +#include "options.h" +#include "main.h" +#include "../common/i18n.h" + +#define CONTROL_D ('D' - 'A' + 1) + + +/* The stream to output the status information. Output is disabled if + this is NULL. */ +static estream_t statusfp; + + +static void +progress_cb (void *ctx, const char *what, int printchar, + int current, int total) +{ + char buf[50]; + + (void)ctx; + + if ( printchar == '\n' && !strcmp (what, "primegen") ) + snprintf (buf, sizeof buf, "%.20s X 100 100", what ); + else + snprintf (buf, sizeof buf, "%.20s %c %d %d", + what, printchar=='\n'?'X':printchar, current, total ); + write_status_text (STATUS_PROGRESS, buf); +} + + +/* Return true if the status message NO may currently be issued. We + need this to avoid synchronization 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 retrieval 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 != es_stdout && statusfp != es_stderr ) + es_fclose (statusfp); + statusfp = NULL; + if (fd == -1) + return; + + if (! gnupg_fd_valid (fd)) + log_fatal ("status-fd is invalid: %s\n", strerror (errno)); + + if (fd == 1) + statusfp = es_stdout; + else if (fd == 2) + statusfp = es_stderr; + else + statusfp = es_fdopen (fd, "w"); + if (!statusfp) + { + log_fatal ("can't open fd %d for status output: %s\n", + fd, strerror (errno)); + } + last_fd = fd; + + gcry_set_progress_handler (progress_cb, NULL); +} + + +int +is_status_enabled () +{ + return !!statusfp; +} + + +void +write_status ( int no ) +{ + write_status_text( no, NULL ); +} + + +/* Write a status line with code NO followed by the string TEXT and + * directly followed by the remaining strings up to a NULL. Embedded + * CR and LFs in the strings (but not in TEXT) are C-style escaped.*/ +void +write_status_strings (int no, const char *text, ...) +{ + va_list arg_ptr; + const char *s; + + if (!statusfp || !status_currently_allowed (no) ) + return; /* Not enabled or allowed. */ + + es_fputs ("[GNUPG:] ", statusfp); + es_fputs (get_status_string (no), statusfp); + if ( text ) + { + es_putc ( ' ', statusfp); + va_start (arg_ptr, text); + s = text; + do + { + for (; *s; s++) + { + if (*s == '\n') + es_fputs ("\\n", statusfp); + else if (*s == '\r') + es_fputs ("\\r", statusfp); + else + es_fputc (*(const byte *)s, statusfp); + } + } + while ((s = va_arg (arg_ptr, const char*))); + va_end (arg_ptr); + } + es_putc ('\n', statusfp); + if (es_fflush (statusfp) && opt.exit_on_status_write_error) + g10_exit (0); +} + + +void +write_status_text (int no, const char *text) +{ + write_status_strings (no, text, NULL); +} + + +/* Write a status line with code NO followed by the output of the + * printf style FORMAT. Embedded CR and LFs are C-style escaped. */ +void +write_status_printf (int no, const char *format, ...) +{ + va_list arg_ptr; + char *buf; + + if (!statusfp || !status_currently_allowed (no) ) + return; /* Not enabled or allowed. */ + + es_fputs ("[GNUPG:] ", statusfp); + es_fputs (get_status_string (no), statusfp); + if (format) + { + es_putc ( ' ', statusfp); + va_start (arg_ptr, format); + buf = gpgrt_vbsprintf (format, arg_ptr); + if (!buf) + log_error ("error printing status line: %s\n", + gpg_strerror (gpg_err_code_from_syserror ())); + else + { + if (strpbrk (buf, "\r\n")) + { + const byte *s; + for (s=buf; *s; s++) + { + if (*s == '\n') + es_fputs ("\\n", statusfp); + else if (*s == '\r') + es_fputs ("\\r", statusfp); + else + es_fputc (*s, statusfp); + } + } + else + es_fputs (buf, statusfp); + gpgrt_free (buf); + } + + va_end (arg_ptr); + } + es_putc ('\n', statusfp); + if (es_fflush (statusfp) && opt.exit_on_status_write_error) + g10_exit (0); +} + + +/* Write an ERROR status line using a full gpg-error error value. */ +void +write_status_error (const char *where, gpg_error_t err) +{ + if (!statusfp || !status_currently_allowed (STATUS_ERROR)) + return; /* Not enabled or allowed. */ + + es_fprintf (statusfp, "[GNUPG:] %s %s %u\n", + get_status_string (STATUS_ERROR), where, err); + if (es_fflush (statusfp) && opt.exit_on_status_write_error) + g10_exit (0); +} + + +/* Same as above but outputs the error code only. */ +void +write_status_errcode (const char *where, int errcode) +{ + if (!statusfp || !status_currently_allowed (STATUS_ERROR)) + return; /* Not enabled or allowed. */ + + es_fprintf (statusfp, "[GNUPG:] %s %s %u\n", + get_status_string (STATUS_ERROR), where, gpg_err_code (errcode)); + if (es_fflush (statusfp) && opt.exit_on_status_write_error) + g10_exit (0); +} + + +/* Write a FAILURE status line. */ +void +write_status_failure (const char *where, gpg_error_t err) +{ + static int any_failure_printed; + + if (!statusfp || !status_currently_allowed (STATUS_FAILURE)) + return; /* Not enabled or allowed. */ + if (any_failure_printed) + return; + any_failure_printed = 1; + es_fprintf (statusfp, "[GNUPG:] %s %s %u\n", + get_status_string (STATUS_FAILURE), where, err); + if (es_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) + { + es_fprintf (statusfp, "[GNUPG:] %s ", text); + count = dowrap = 0; + if (first && string) + { + es_fputs (string, statusfp); + count += strlen (string); + /* Make sure that there is a space after the string. */ + if (*string && string[strlen (string)-1] != ' ') + { + es_putc (' ', statusfp); + count++; + } + } + first = 0; + } + for (esc=0, s=buffer, n=len; n; s++, n--) + { + if (*s == '%' || *(const byte*)s <= lower_limit + || *(const byte*)s == 127 ) + esc = 1; + if (wrap && ++count > wrap) + dowrap=1; + if (esc || dowrap) + break; + } + if (s != buffer) + es_fwrite (buffer, s-buffer, 1, statusfp); + if ( esc ) + { + es_fprintf (statusfp, "%%%02X", *(const byte*)s ); + s++; n--; + } + buffer = s; + len = n; + if (dowrap && len) + es_putc ('\n', statusfp); + } + while (len); + + es_putc ('\n',statusfp); + if (es_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); +} + + +/* Print the BEGIN_SIGNING status message. If MD is not NULL it is + used to retrieve the hash algorithms used for the message. */ +void +write_status_begin_signing (gcry_md_hd_t md) +{ + if (md) + { + char buf[100]; + size_t buflen; + int i, ga; + + buflen = 0; + for (i=1; i <= 110; i++) + { + ga = map_md_openpgp_to_gcry (i); + if (ga && gcry_md_is_enabled (md, ga) && buflen+10 < DIM(buf)) + { + snprintf (buf+buflen, DIM(buf) - buflen, + "%sH%d", buflen? " ":"",i); + buflen += strlen (buf+buflen); + } + } + write_status_text (STATUS_BEGIN_SIGNING, buf); + } + else + write_status ( STATUS_BEGIN_SIGNING ); +} + + +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 +#ifndef HAVE_W32CE_SYSTEM + raise (SIGINT); /* Nothing to hangup under DOS. */ +#endif +#else + raise (SIGHUP); /* No more input data. */ +#endif + } + } + return rc; +} + + + +/* Request a string from the client over the command-fd. If GETBOOL + is set the function returns a static string (do not free) if the + entered value was true or NULL if the entered value was false. */ +static char * +do_get_from_fd ( const char *keyword, int hidden, int getbool ) +{ + int i, len; + char *string; + + if (statusfp != es_stdout) + es_fflush (es_stdout); + + write_status_text (getbool? STATUS_GET_BOOL : + hidden? STATUS_GET_HIDDEN : STATUS_GET_LINE, keyword); + + for (string = NULL, i = len = 200; ; i++ ) + { + if (i >= len-1 ) + { + /* On the first iteration allocate a new buffer. If that + * buffer is too short at further iterations do a poor man's + * realloc. */ + char *save = string; + len += 100; + string = hidden? xmalloc_secure ( len ) : xmalloc ( len ); + if (save) + { + memcpy (string, save, i); + xfree (save); + } + else + i = 0; + } + /* Fixme: 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 (getbool) /* Fixme: is this correct??? */ + return (string[0] == 'Y' || string[0] == 'y') ? "" : NULL; + + return string; +} + + + +int +cpr_enabled() +{ + if( opt.command_fd != -1 ) + return 1; + 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 ); + 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 ); + 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 ); + 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; + tty_kill_prompt(); + return; +} + +int +cpr_get_answer_is_yes_def (const char *keyword, const char *prompt, int def_yes) +{ + int yes; + char *p; + + if( opt.command_fd != -1 ) + return !!do_get_from_fd ( keyword, 0, 1 ); + 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_default (p, def_yes); + xfree(p); + return yes; + } + } +} + +int +cpr_get_answer_is_yes (const char *keyword, const char *prompt) +{ + return cpr_get_answer_is_yes_def (keyword, prompt, 0); +} + +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 ); + 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 ); + + 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/dearmor.c b/g10/dearmor.c new file mode 100644 index 0000000..92239cc --- /dev/null +++ b/g10/dearmor.c @@ -0,0 +1,131 @@ +/* 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 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include +#include +#include +#include +#include + +#include "gpg.h" +#include "../common/status.h" +#include "../common/iobuf.h" +#include "../common/util.h" +#include "filter.h" +#include "packet.h" +#include "options.h" +#include "main.h" +#include "../common/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; + + afx = new_armor_context (); + + /* prepare iobufs */ + inp = iobuf_open(fname); + if (inp && is_secured_file (iobuf_get_fd (inp))) + { + iobuf_close (inp); + inp = NULL; + gpg_err_set_errno (EPERM); + } + if (!inp) { + rc = gpg_error_from_syserror (); + log_error(_("can't open '%s': %s\n"), fname? fname: "[stdin]", + strerror(errno) ); + goto leave; + } + + push_armor_filter ( afx, inp ); + + if( (rc = open_outfile (-1, fname, 0, 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); + release_armor_context (afx); + 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; + + afx = new_armor_context (); + + /* prepare iobufs */ + inp = iobuf_open(fname); + if (inp && is_secured_file (iobuf_get_fd (inp))) + { + iobuf_close (inp); + inp = NULL; + gpg_err_set_errno (EPERM); + } + if (!inp) { + rc = gpg_error_from_syserror (); + log_error(_("can't open '%s': %s\n"), fname? fname: "[stdin]", + strerror(errno) ); + goto leave; + } + + + if( (rc = open_outfile (-1, fname, 1, 0, &out )) ) + goto leave; + + afx->what = 4; + afx->hdrlines = "Comment: Use \"gpg --dearmor\" for unpacking\n"; + push_armor_filter ( afx, out ); + + while( (c = iobuf_get(inp)) != -1 ) + iobuf_put( out, c ); + + + leave: + if( rc ) + iobuf_cancel(out); + else + iobuf_close(out); + iobuf_close(inp); + release_armor_context (afx); + return rc; +} diff --git a/g10/decrypt-data.c b/g10/decrypt-data.c new file mode 100644 index 0000000..0046c35 --- /dev/null +++ b/g10/decrypt-data.c @@ -0,0 +1,1030 @@ +/* decrypt-data.c - Decrypt an encrypted data packet + * Copyright (C) 1998-2001, 2005-2006, 2009 Free Software Foundation, Inc. + * Copyright (C) 1998-2001, 2005-2006, 2009, 2018 Werner Koch + * Copyright (C) 2020 g10 Code GmbH + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include +#include +#include +#include + +#include "gpg.h" +#include "../common/util.h" +#include "packet.h" +#include "options.h" +#include "../common/i18n.h" +#include "../common/status.h" +#include "../common/compliance.h" + + +static int aead_decode_filter (void *opaque, int control, iobuf_t a, + byte *buf, size_t *ret_len); +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); + +/* Our context object. */ +struct decode_filter_context_s +{ + /* Recounter (max value is 2). We need it because we do not know + * whether the iobuf or the outer control code frees this object + * first. */ + int refcount; + + /* The cipher handle. */ + gcry_cipher_hd_t cipher_hd; + + /* The hash handle for use in MDC mode. */ + gcry_md_hd_t mdc_hash; + + /* The start IV for AEAD encryption. */ + byte startiv[16]; + + /* The holdback buffer and its used length. For AEAD we need 32+1 + * bytes but we use 48 byte. For MDC we need 22 bytes; here + * holdbacklen will either 0 or 22. */ + char holdback[48]; + unsigned int holdbacklen; + + /* Working on a partial length packet. */ + unsigned int partial : 1; + + /* EOF indicator with these true values: + * 1 = normal EOF + * 2 = premature EOF (tag or hash incomplete) + * 3 = premature EOF (general) */ + unsigned int eof_seen : 2; + + /* The actually used cipher algo for AEAD. */ + byte cipher_algo; + + /* The AEAD algo. */ + byte aead_algo; + + /* The encoded chunk byte for AEAD. */ + byte chunkbyte; + + /* The decoded CHUNKBYTE. */ + uint64_t chunksize; + + /* The chunk index for AEAD. */ + uint64_t chunkindex; + + /* The number of bytes in the current chunk. */ + uint64_t chunklen; + + /* The total count of decrypted plaintext octets. */ + uint64_t total; + + /* Remaining bytes in the packet according to the packet header. + * Not used if PARTIAL is true. */ + size_t length; +}; +typedef struct decode_filter_context_s *decode_filter_ctx_t; + + +/* Helper to release the decode context. */ +static void +release_dfx_context (decode_filter_ctx_t dfx) +{ + if (!dfx) + return; + + log_assert (dfx->refcount); + if ( !--dfx->refcount ) + { + gcry_cipher_close (dfx->cipher_hd); + dfx->cipher_hd = NULL; + gcry_md_close (dfx->mdc_hash); + dfx->mdc_hash = NULL; + xfree (dfx); + } +} + + +/* Set the nonce and the additional data for the current chunk. This + * also reset the decryption machinery so that the handle can be + * used for a new chunk. If FINAL is set the final AEAD chunk is + * processed. */ +static gpg_error_t +aead_set_nonce_and_ad (decode_filter_ctx_t dfx, int final) +{ + gpg_error_t err; + unsigned char ad[21]; + unsigned char nonce[16]; + int i; + + switch (dfx->aead_algo) + { + case AEAD_ALGO_OCB: + memcpy (nonce, dfx->startiv, 15); + i = 7; + break; + + case AEAD_ALGO_EAX: + memcpy (nonce, dfx->startiv, 16); + i = 8; + break; + + default: + BUG (); + } + nonce[i++] ^= dfx->chunkindex >> 56; + nonce[i++] ^= dfx->chunkindex >> 48; + nonce[i++] ^= dfx->chunkindex >> 40; + nonce[i++] ^= dfx->chunkindex >> 32; + nonce[i++] ^= dfx->chunkindex >> 24; + nonce[i++] ^= dfx->chunkindex >> 16; + nonce[i++] ^= dfx->chunkindex >> 8; + nonce[i++] ^= dfx->chunkindex; + + if (DBG_CRYPTO) + log_printhex (nonce, i, "nonce:"); + err = gcry_cipher_setiv (dfx->cipher_hd, nonce, i); + if (err) + return err; + + ad[0] = (0xc0 | PKT_ENCRYPTED_AEAD); + ad[1] = 1; + ad[2] = dfx->cipher_algo; + ad[3] = dfx->aead_algo; + ad[4] = dfx->chunkbyte; + ad[5] = dfx->chunkindex >> 56; + ad[6] = dfx->chunkindex >> 48; + ad[7] = dfx->chunkindex >> 40; + ad[8] = dfx->chunkindex >> 32; + ad[9] = dfx->chunkindex >> 24; + ad[10]= dfx->chunkindex >> 16; + ad[11]= dfx->chunkindex >> 8; + ad[12]= dfx->chunkindex; + if (final) + { + ad[13] = dfx->total >> 56; + ad[14] = dfx->total >> 48; + ad[15] = dfx->total >> 40; + ad[16] = dfx->total >> 32; + ad[17] = dfx->total >> 24; + ad[18] = dfx->total >> 16; + ad[19] = dfx->total >> 8; + ad[20] = dfx->total; + } + if (DBG_CRYPTO) + log_printhex (ad, final? 21 : 13, "authdata:"); + return gcry_cipher_authenticate (dfx->cipher_hd, ad, final? 21 : 13); +} + + +/* Helper to check the 16 byte tag in TAGBUF. The FINAL flag is only + * for debug messages. */ +static gpg_error_t +aead_checktag (decode_filter_ctx_t dfx, int final, const void *tagbuf) +{ + gpg_error_t err; + + if (DBG_FILTER) + log_printhex (tagbuf, 16, "tag:"); + err = gcry_cipher_checktag (dfx->cipher_hd, tagbuf, 16); + if (err) + { + log_error ("gcry_cipher_checktag%s failed: %s\n", + final? " (final)":"", gpg_strerror (err)); + return err; + } + if (DBG_FILTER) + log_debug ("%stag is valid\n", final?"final ":""); + return 0; +} + + +/**************** + * Decrypt the data, specified by ED with the key DEK. On return + * COMPLIANCE_ERROR is set to true iff the decryption can claim that + * it was compliant in the current mode; otherwise this flag is set to + * false. + */ +int +decrypt_data (ctrl_t ctrl, void *procctx, PKT_encrypted *ed, DEK *dek, + int *compliance_error) +{ + decode_filter_ctx_t dfx; + enum gcry_cipher_modes ciphermode; + unsigned int startivlen; + byte *p; + int rc=0, c, i; + byte temp[32]; + unsigned blocksize; + unsigned nprefix; + + *compliance_error = 0; + + dfx = xtrycalloc (1, sizeof *dfx); + if (!dfx) + return gpg_error_from_syserror (); + dfx->refcount = 1; + + if ( opt.verbose && !dek->algo_info_printed ) + { + if (!openpgp_cipher_test_algo (dek->algo)) + log_info (_("%s encrypted data\n"), + openpgp_cipher_algo_mode_name (dek->algo, ed->aead_algo)); + else + log_info (_("encrypted with unknown algorithm %d\n"), dek->algo ); + dek->algo_info_printed = 1; + } + + if (ed->aead_algo) + { + rc = openpgp_aead_algo_info (ed->aead_algo, &ciphermode, &startivlen); + if (rc) + goto leave; + log_assert (startivlen <= sizeof dfx->startiv); + } + else + ciphermode = GCRY_CIPHER_MODE_CFB; + + /* Check compliance. */ + if (!gnupg_cipher_is_allowed (opt.compliance, 0, dek->algo, ciphermode)) + { + log_error (_("cipher algorithm '%s' may not be used in %s mode\n"), + openpgp_cipher_algo_mode_name (dek->algo,ed->aead_algo), + gnupg_compliance_option_string (opt.compliance)); + *compliance_error = 1; + if (opt.flags.require_compliance) + { + /* We fail early in this case because it does not make sense + * to first decrypt everything. */ + rc = gpg_error (GPG_ERR_CIPHER_ALGO); + goto leave; + } + } + + write_status_printf (STATUS_DECRYPTION_INFO, "%d %d %d", + ed->mdc_method, dek->algo, 0); + + if (opt.show_session_key) + { + char numbuf[25]; + char *hexbuf; + + if (ed->aead_algo) + snprintf (numbuf, sizeof numbuf, "%d.%u:", dek->algo, ed->aead_algo); + else + snprintf (numbuf, sizeof numbuf, "%d:", dek->algo); + hexbuf = bin2hex (dek->key, dek->keylen, NULL); + if (!hexbuf) + { + rc = gpg_error_from_syserror (); + goto leave; + } + log_info ("session key: '%s%s'\n", numbuf, hexbuf); + write_status_strings (STATUS_SESSION_KEY, numbuf, hexbuf, NULL); + xfree (hexbuf); + } + + rc = openpgp_cipher_test_algo (dek->algo); + if (rc) + goto leave; + blocksize = openpgp_cipher_get_algo_blklen (dek->algo); + if ( !blocksize || blocksize > 16 ) + log_fatal ("unsupported blocksize %u\n", blocksize ); + + if (ed->aead_algo) + { + if (blocksize != 16) + { + rc = gpg_error (GPG_ERR_CIPHER_ALGO); + goto leave; + } + + if (ed->chunkbyte > 56) + { + log_error ("invalid AEAD chunkbyte %u\n", ed->chunkbyte); + rc = gpg_error (GPG_ERR_INV_PACKET); + goto leave; + } + + /* Read the Start-IV. */ + if (ed->len) + { + for (i=0; i < startivlen && ed->len; i++, ed->len--) + { + if ((c=iobuf_get (ed->buf)) == -1) + break; + dfx->startiv[i] = c; + } + } + else + { + for (i=0; i < startivlen; i++ ) + if ( (c=iobuf_get (ed->buf)) == -1 ) + break; + else + dfx->startiv[i] = c; + } + if (i != startivlen) + { + log_error ("Start-IV in AEAD packet too short (%d/%u)\n", + i, startivlen); + rc = gpg_error (GPG_ERR_TOO_SHORT); + goto leave; + } + + dfx->cipher_algo = ed->cipher_algo; + dfx->aead_algo = ed->aead_algo; + dfx->chunkbyte = ed->chunkbyte; + dfx->chunksize = (uint64_t)1 << (dfx->chunkbyte + 6); + + if (dek->algo != dfx->cipher_algo) + log_info ("Note: different cipher algorithms used (%s/%s)\n", + openpgp_cipher_algo_name (dek->algo), + openpgp_cipher_algo_name (dfx->cipher_algo)); + + rc = openpgp_cipher_open (&dfx->cipher_hd, + dfx->cipher_algo, + ciphermode, + GCRY_CIPHER_SECURE); + if (rc) + goto leave; /* Should never happen. */ + + if (DBG_CRYPTO) + log_printhex (dek->key, dek->keylen, "thekey:"); + rc = gcry_cipher_setkey (dfx->cipher_hd, dek->key, dek->keylen); + if (gpg_err_code (rc) == GPG_ERR_WEAK_KEY) + { + log_info (_("WARNING: message was encrypted with" + " a weak key in the symmetric cipher.\n")); + rc = 0; + } + else if (rc) + { + log_error("key setup failed: %s\n", gpg_strerror (rc)); + goto leave; + } + + if (!ed->buf) + { + log_error(_("problem handling encrypted packet\n")); + goto leave; + } + + } + else /* CFB encryption. */ + { + nprefix = blocksize; + if ( ed->len && ed->len < (nprefix+2) ) + { + /* An invalid message. We can't check that during parsing + * because we may not know the used cipher then. */ + rc = gpg_error (GPG_ERR_INV_PACKET); + goto leave; + } + + if ( ed->mdc_method ) + { + if (gcry_md_open (&dfx->mdc_hash, ed->mdc_method, 0 )) + BUG (); + if ( DBG_HASHING ) + gcry_md_debug (dfx->mdc_hash, "checkmdc"); + } + + rc = openpgp_cipher_open (&dfx->cipher_hd, dek->algo, + GCRY_CIPHER_MODE_CFB, + (GCRY_CIPHER_SECURE + | ((ed->mdc_method || dek->algo >= 100)? + 0 : GCRY_CIPHER_ENABLE_SYNC))); + if (rc) + { + /* We should never get an error here cause we already checked + * that the algorithm is available. */ + BUG(); + } + + + /* log_hexdump( "thekey", dek->key, dek->keylen );*/ + rc = gcry_cipher_setkey (dfx->cipher_hd, dek->key, dek->keylen); + if ( gpg_err_code (rc) == GPG_ERR_WEAK_KEY ) + { + log_info (_("WARNING: message was encrypted with" + " a weak key in the symmetric cipher.\n")); + rc = 0; + } + else if (rc) + { + log_error("key setup failed: %s\n", gpg_strerror (rc) ); + goto leave; + } + + if (!ed->buf) + { + log_error (_("problem handling encrypted packet\n")); + rc = gpg_error (GPG_ERR_INV_PACKET); + goto leave; + } + + gcry_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; + } + + gcry_cipher_decrypt (dfx->cipher_hd, temp, nprefix+2, NULL, 0); + gcry_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 = gpg_error (GPG_ERR_BAD_KEY); + goto leave; + } + + if ( dfx->mdc_hash ) + gcry_md_write (dfx->mdc_hash, temp, nprefix+2); + } + + dfx->refcount++; + dfx->partial = !!ed->is_partial; + dfx->length = ed->len; + if (ed->aead_algo) + iobuf_push_filter ( ed->buf, aead_decode_filter, dfx ); + else if (ed->mdc_method) + iobuf_push_filter ( ed->buf, mdc_decode_filter, dfx ); + else + iobuf_push_filter ( ed->buf, decode_filter, dfx ); + + if (opt.unwrap_encryption) + { + char *filename = NULL; + estream_t fp; + + rc = get_output_file ("", 0, ed->buf, &filename, &fp); + if (! rc) + { + iobuf_t output = iobuf_esopen (fp, "w", 0); + armor_filter_context_t *afx = NULL; + + es_setbuf (fp, NULL); + + if (opt.armor) + { + afx = new_armor_context (); + push_armor_filter (afx, output); + } + + iobuf_copy (output, ed->buf); + if ((rc = iobuf_error (ed->buf))) + log_error (_("error reading '%s': %s\n"), + filename, gpg_strerror (rc)); + else if ((rc = iobuf_error (output))) + log_error (_("error writing '%s': %s\n"), + filename, gpg_strerror (rc)); + + iobuf_close (output); + release_armor_context (afx); + } + xfree (filename); + } + else + proc_packets (ctrl, procctx, ed->buf ); + + ed->buf = NULL; + if (dfx->eof_seen > 1 ) + rc = gpg_error (GPG_ERR_INV_PACKET); + else if ( ed->mdc_method ) + { + /* 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 = gcry_md_get_algo_dlen (ed->mdc_method); + + log_assert (dfx->cipher_hd); + log_assert (dfx->mdc_hash); + gcry_cipher_decrypt (dfx->cipher_hd, dfx->holdback, 22, NULL, 0); + gcry_md_write (dfx->mdc_hash, dfx->holdback, 2); + gcry_md_final (dfx->mdc_hash); + + if ( dfx->holdback[0] != '\xd3' + || dfx->holdback[1] != '\x14' + || datalen != 20 + || memcmp (gcry_md_read (dfx->mdc_hash, 0), dfx->holdback+2, datalen)) + rc = gpg_error (GPG_ERR_BAD_SIGNATURE); + /* log_printhex(dfx->holdback, 22, "MDC message:"); */ + /* log_printhex(gcry_md_read (dfx->mdc_hash,0), datalen, "MDC calc:"); */ + } + + leave: + release_dfx_context (dfx); + return rc; +} + + +/* Fill BUFFER with up to NBYTES-OFFSET from STREAM utilizing + * information from the context DFX. Returns the new offset which is + * the number of bytes read plus the original offset. On EOF the + * respective flag in DFX is set. */ +static size_t +fill_buffer (decode_filter_ctx_t dfx, iobuf_t stream, + byte *buffer, size_t nbytes, size_t offset) +{ + size_t nread = offset; + size_t curr; + int ret; + + if (dfx->partial) + { + while (nread < nbytes) + { + curr = nbytes - nread; + + ret = iobuf_read (stream, &buffer[nread], curr); + if (ret == -1) + { + dfx->eof_seen = 1; /* Normal EOF. */ + break; + } + + nread += ret; + } + } + else + { + while (nread < nbytes && dfx->length) + { + curr = nbytes - nread; + if (curr > dfx->length) + curr = dfx->length; + + ret = iobuf_read (stream, &buffer[nread], curr); + if (ret == -1) + { + dfx->eof_seen = 3; /* Premature EOF. */ + break; + } + + nread += ret; + dfx->length -= ret; + } + if (!dfx->length) + dfx->eof_seen = 1; /* Normal EOF. */ + } + + return nread; +} + + +/* The core of the AEAD decryption. This is the underflow function of + * the aead_decode_filter. */ +static gpg_error_t +aead_underflow (decode_filter_ctx_t dfx, iobuf_t a, byte *buf, size_t *ret_len) +{ + const size_t size = *ret_len; /* The allocated size of BUF. */ + gpg_error_t err; + size_t totallen = 0; /* The number of bytes to return on success or EOF. */ + size_t off = 0; /* The offset into the buffer. */ + size_t len; /* The current number of bytes in BUF+OFF. */ + + log_assert (size > 48); /* Our code requires at least this size. */ + + /* Copy the rest from the last call of this function into BUF. */ + len = dfx->holdbacklen; + dfx->holdbacklen = 0; + memcpy (buf, dfx->holdback, len); + + if (DBG_FILTER) + log_debug ("aead_underflow: size=%zu len=%zu%s%s\n", size, len, + dfx->partial? " partial":"", dfx->eof_seen? " eof":""); + + /* Read and fill up BUF. We need to watch out for an EOF so that we + * can detect the last chunk which is commonly shorter than the + * chunksize. After the last data byte from the last chunk 32 more + * bytes are expected for the last chunk's tag and the following + * final chunk's tag. To detect the EOF we need to try reading at least + * one further byte; however we try to read 16 extra bytes to avoid + * single byte reads in some lower layers. The outcome is that we + * have up to 48 extra extra octets which we will later put into the + * holdback buffer for the next invocation (which handles the EOF + * case). */ + len = fill_buffer (dfx, a, buf, size, len); + if (len < 32) + { + /* Not enough data for the last two tags. */ + err = gpg_error (GPG_ERR_TRUNCATED); + goto leave; + } + if (dfx->eof_seen) + { + /* If have seen an EOF we copy only the last two auth tags into + * the holdback buffer. */ + dfx->holdbacklen = 32; + memcpy (dfx->holdback, buf+len-32, 32); + len -= 32; + } + else + { + /* If have not seen an EOF we copy the entire extra 48 bytes + * into the holdback buffer for processing at the next call of + * this function. */ + dfx->holdbacklen = len > 48? 48 : len; + memcpy (dfx->holdback, buf+len-dfx->holdbacklen, dfx->holdbacklen); + len -= dfx->holdbacklen; + } + /* log_printhex (dfx->holdback, dfx->holdbacklen, "holdback:"); */ + + /* Decrypt the buffer. This first requires a loop to handle the + * case when a chunk ends within the buffer. */ + if (DBG_FILTER) + log_debug ("decrypt: chunklen=%ju total=%ju size=%zu len=%zu%s\n", + dfx->chunklen, dfx->total, size, len, + dfx->eof_seen? " eof":""); + + while (len && dfx->chunklen + len >= dfx->chunksize) + { + size_t n = dfx->chunksize - dfx->chunklen; + byte tagbuf[16]; + + if (DBG_FILTER) + log_debug ("chunksize will be reached: n=%zu\n", n); + + if (!dfx->chunklen) + { + /* First data for this chunk - prepare. */ + err = aead_set_nonce_and_ad (dfx, 0); + if (err) + goto leave; + } + + /* log_printhex (buf, n, "ciph:"); */ + gcry_cipher_final (dfx->cipher_hd); + err = gcry_cipher_decrypt (dfx->cipher_hd, buf+off, n, NULL, 0); + if (err) + { + log_error ("gcry_cipher_decrypt failed (1): %s\n", + gpg_strerror (err)); + goto leave; + } + /* log_printhex (buf, n, "plai:"); */ + totallen += n; + dfx->chunklen += n; + dfx->total += n; + off += n; + len -= n; + + if (DBG_FILTER) + log_debug ("ndecrypted: %zu (nchunk=%ju) bytes left: %zu at off=%zu\n", + totallen, dfx->chunklen, len, off); + + /* Check the tag. */ + if (len < 16) + { + /* The tag is not entirely in the buffer. Read the rest of + * the tag from the holdback buffer. Then shift the holdback + * buffer and fill it up again. */ + memcpy (tagbuf, buf+off, len); + memcpy (tagbuf + len, dfx->holdback, 16 - len); + dfx->holdbacklen -= 16-len; + memmove (dfx->holdback, dfx->holdback + (16-len), dfx->holdbacklen); + + if (dfx->eof_seen) + { + /* We should have the last chunk's tag in TAGBUF and the + * final tag in HOLDBACKBUF. */ + if (len || dfx->holdbacklen != 16) + { + /* Not enough data for the last two tags. */ + err = gpg_error (GPG_ERR_TRUNCATED); + goto leave; + } + } + else + { + len = 0; + dfx->holdbacklen = fill_buffer (dfx, a, dfx->holdback, 48, + dfx->holdbacklen); + if (dfx->holdbacklen < 32) + { + /* Not enough data for the last two tags. */ + err = gpg_error (GPG_ERR_TRUNCATED); + goto leave; + } + } + } + else /* We already have the full tag. */ + { + memcpy (tagbuf, buf+off, 16); + /* Remove that tag from the output. */ + memmove (buf + off, buf + off + 16, len - 16); + len -= 16; + } + err = aead_checktag (dfx, 0, tagbuf); + if (err) + goto leave; + dfx->chunklen = 0; + dfx->chunkindex++; + + continue; + } + + /* The bulk decryption of our buffer. */ + if (len) + { + if (!dfx->chunklen) + { + /* First data for this chunk - prepare. */ + err = aead_set_nonce_and_ad (dfx, 0); + if (err) + goto leave; + } + + if (dfx->eof_seen) + { + /* This is the last block of the last chunk. Its length may + * not be a multiple of the block length. */ + gcry_cipher_final (dfx->cipher_hd); + } + err = gcry_cipher_decrypt (dfx->cipher_hd, buf + off, len, NULL, 0); + if (err) + { + log_error ("gcry_cipher_decrypt failed (2): %s\n", + gpg_strerror (err)); + goto leave; + } + totallen += len; + dfx->chunklen += len; + dfx->total += len; + if (DBG_FILTER) + log_debug ("ndecrypted: %zu (nchunk=%ju)\n", totallen, dfx->chunklen); + } + + if (dfx->eof_seen) + { + + if (dfx->chunklen) + { + if (DBG_FILTER) + log_debug ("eof seen: holdback has the last and final tag\n"); + log_assert (dfx->holdbacklen >= 32); + err = aead_checktag (dfx, 0, dfx->holdback); + if (err) + goto leave; + dfx->chunklen = 0; + dfx->chunkindex++; + off = 16; + } + else + { + if (DBG_FILTER) + log_debug ("eof seen: holdback has the final tag\n"); + log_assert (dfx->holdbacklen >= 16); + off = 0; + } + + /* Check the final chunk. */ + err = aead_set_nonce_and_ad (dfx, 1); + if (err) + goto leave; + gcry_cipher_final (dfx->cipher_hd); + /* Decrypt an empty string (using HOLDBACK as a dummy). */ + err = gcry_cipher_decrypt (dfx->cipher_hd, dfx->holdback, 0, NULL, 0); + if (err) + { + log_error ("gcry_cipher_decrypt failed (final): %s\n", + gpg_strerror (err)); + goto leave; + } + err = aead_checktag (dfx, 1, dfx->holdback+off); + if (err) + goto leave; + err = gpg_error (GPG_ERR_EOF); + } + + leave: + if (DBG_FILTER) + log_debug ("aead_underflow: returning %zu (%s)\n", + totallen, gpg_strerror (err)); + + /* In case of an auth error we map the error code to the same as + * used by the MDC decryption. */ + if (gpg_err_code (err) == GPG_ERR_CHECKSUM) + err = gpg_error (GPG_ERR_BAD_SIGNATURE); + + /* In case of an error we better wipe out the buffer than to convey + * partly decrypted data. */ + if (err && gpg_err_code (err) != GPG_ERR_EOF) + memset (buf, 0, size); + + *ret_len = totallen; + + return err; +} + + +/* The IOBUF filter used to decrypt AEAD encrypted data. */ +static int +aead_decode_filter (void *opaque, int control, IOBUF a, + byte *buf, size_t *ret_len) +{ + decode_filter_ctx_t dfx = opaque; + int rc = 0; + + if ( control == IOBUFCTRL_UNDERFLOW && dfx->eof_seen ) + { + *ret_len = 0; + rc = -1; + } + else if ( control == IOBUFCTRL_UNDERFLOW ) + { + log_assert (a); + + rc = aead_underflow (dfx, a, buf, ret_len); + if (gpg_err_code (rc) == GPG_ERR_EOF) + rc = -1; /* We need to use the old convention in the filter. */ + + } + else if ( control == IOBUFCTRL_FREE ) + { + release_dfx_context (dfx); + } + else if ( control == IOBUFCTRL_DESC ) + { + mem2str (buf, "aead_decode_filter", *ret_len); + } + + return rc; +} + + +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; + + /* Note: We need to distinguish between a partial and a fixed length + packet. The first is the usual case as created by GPG. However + for short messages the format degrades to a fixed length packet + and other implementations might use fixed length as well. Only + looking for the EOF on fixed data works only if the encrypted + packet is not followed by other data. This used to be a long + standing bug which was fixed on 2009-10-02. */ + + if ( control == IOBUFCTRL_UNDERFLOW && dfx->eof_seen ) + { + *ret_len = 0; + rc = -1; + } + else if( control == IOBUFCTRL_UNDERFLOW ) + { + log_assert (a); + log_assert (size > 44); /* Our code requires at least this size. */ + + /* Get at least 22 bytes and put it ahead in the buffer. */ + n = fill_buffer (dfx, a, buf, 44, 22); + if (n == 44) + { + /* We have enough stuff - flush the deferred stuff. */ + if ( !dfx->holdbacklen ) /* First time. */ + { + memcpy (buf, buf+22, 22); + n = 22; + } + else + { + memcpy (buf, dfx->holdback, 22); + } + /* Fill up the buffer. */ + n = fill_buffer (dfx, a, buf, size, n); + + /* Move the trailing 22 bytes back to the holdback buffer. We + have at least 44 bytes thus a memmove is not needed. */ + n -= 22; + memcpy (dfx->holdback, buf+n, 22 ); + dfx->holdbacklen = 22; + } + else if ( !dfx->holdbacklen ) /* EOF seen but empty holdback buffer. */ + { + /* This is bad because it means an incomplete hash. */ + n -= 22; + memcpy (buf, buf+22, n ); + dfx->eof_seen = 2; /* EOF with incomplete hash. */ + } + else /* EOF seen (i.e. read less than 22 bytes). */ + { + memcpy (buf, dfx->holdback, 22 ); + n -= 22; + memcpy (dfx->holdback, buf+n, 22 ); + dfx->eof_seen = 1; /* Normal EOF. */ + } + + if ( n ) + { + if ( dfx->cipher_hd ) + gcry_cipher_decrypt (dfx->cipher_hd, buf, n, NULL, 0); + if ( dfx->mdc_hash ) + gcry_md_write (dfx->mdc_hash, buf, n); + } + else + { + log_assert ( dfx->eof_seen ); + rc = -1; /* Return EOF. */ + } + *ret_len = n; + } + else if ( control == IOBUFCTRL_FREE ) + { + release_dfx_context (dfx); + } + else if ( control == IOBUFCTRL_DESC ) + { + mem2str (buf, "mdc_decode_filter", *ret_len); + } + 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 size = *ret_len; + size_t n; + int rc = 0; + + + if ( control == IOBUFCTRL_UNDERFLOW && fc->eof_seen ) + { + *ret_len = 0; + rc = -1; + } + else if ( control == IOBUFCTRL_UNDERFLOW ) + { + log_assert (a); + + n = fill_buffer (fc, a, buf, size, 0); + if (n) + { + if (fc->cipher_hd) + gcry_cipher_decrypt (fc->cipher_hd, buf, n, NULL, 0); + } + else + { + if (!fc->eof_seen) + fc->eof_seen = 1; + rc = -1; /* Return EOF. */ + } + *ret_len = n; + } + else if ( control == IOBUFCTRL_FREE ) + { + release_dfx_context (fc); + } + else if ( control == IOBUFCTRL_DESC ) + { + mem2str (buf, "decode_filter", *ret_len); + } + return rc; +} diff --git a/g10/decrypt.c b/g10/decrypt.c new file mode 100644 index 0000000..9589aff --- /dev/null +++ b/g10/decrypt.c @@ -0,0 +1,282 @@ +/* decrypt.c - decrypt and verify data + * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, + * 2007, 2009 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include +#include +#include +#include +#include + +#include "gpg.h" +#include "options.h" +#include "packet.h" +#include "../common/status.h" +#include "../common/iobuf.h" +#include "keydb.h" +#include "../common/util.h" +#include "main.h" +#include "../common/status.h" +#include "../common/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 (ctrl_t ctrl, const char *filename) +{ + IOBUF fp; + armor_filter_context_t *afx = NULL; + progress_filter_context_t *pfx; + int rc; + + pfx = new_progress_context (); + + /* Open the message file. */ + fp = iobuf_open (filename); + if (fp && is_secured_file (iobuf_get_fd (fp))) + { + iobuf_close (fp); + fp = NULL; + gpg_err_set_errno (EPERM); + } + if ( !fp ) + { + rc = gpg_error_from_syserror (); + log_error (_("can't open '%s': %s\n"), print_fname_stdin(filename), + gpg_strerror (rc)); + release_progress_context (pfx); + return rc; + } + + handle_progress (pfx, fp, filename); + + if ( !opt.no_armor ) + { + if ( use_armor_filter( fp ) ) + { + afx = new_armor_context (); + push_armor_filter ( afx, fp ); + } + } + + if (!opt.outfile) + { + opt.outfile = "-"; + opt.flags.dummy_outfile = 1; + } + else + opt.flags.dummy_outfile = 0; + rc = proc_encryption_packets (ctrl, NULL, fp ); + if (opt.flags.dummy_outfile) + opt.outfile = NULL; + + iobuf_close (fp); + release_armor_context (afx); + release_progress_context (pfx); + return rc; +} + + +/* Same as decrypt_message but takes a file descriptor for input and + output. */ +gpg_error_t +decrypt_message_fd (ctrl_t ctrl, int input_fd, int output_fd) +{ +#ifdef HAVE_W32_SYSTEM + /* No server mode yet. */ + (void)ctrl; + (void)input_fd; + (void)output_fd; + return gpg_error (GPG_ERR_NOT_IMPLEMENTED); +#else + gpg_error_t err; + IOBUF fp; + armor_filter_context_t *afx = NULL; + progress_filter_context_t *pfx; + + if (opt.outfp) + return gpg_error (GPG_ERR_BUG); + + pfx = new_progress_context (); + + /* Open the message file. */ + fp = iobuf_fdopen_nc (FD2INT(input_fd), "rb"); + if (fp && is_secured_file (iobuf_get_fd (fp))) + { + iobuf_close (fp); + fp = NULL; + gpg_err_set_errno (EPERM); + } + if (!fp) + { + char xname[64]; + + err = gpg_error_from_syserror (); + snprintf (xname, sizeof xname, "[fd %d]", input_fd); + log_error (_("can't open '%s': %s\n"), xname, gpg_strerror (err)); + release_progress_context (pfx); + return err; + } + +#ifdef HAVE_W32CE_SYSTEM +#warning Need to fix this if we want to use g13 + opt.outfp = NULL; +#else + opt.outfp = es_fdopen_nc (output_fd, "wb"); +#endif + if (!opt.outfp) + { + char xname[64]; + + err = gpg_error_from_syserror (); + snprintf (xname, sizeof xname, "[fd %d]", output_fd); + log_error (_("can't open '%s': %s\n"), xname, gpg_strerror (err)); + iobuf_close (fp); + release_progress_context (pfx); + return err; + } + + if (!opt.no_armor) + { + if (use_armor_filter (fp)) + { + afx = new_armor_context (); + push_armor_filter ( afx, fp ); + } + } + + err = proc_encryption_packets (ctrl, NULL, fp ); + + iobuf_close (fp); + es_fclose (opt.outfp); + opt.outfp = NULL; + release_armor_context (afx); + release_progress_context (pfx); + return err; +#endif +} + + +void +decrypt_messages (ctrl_t ctrl, int nfiles, char *files[]) +{ + IOBUF fp; + 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; + } + + pfx = new_progress_context (); + + 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, IOBUF_IOCTL_NO_CACHE, 1, NULL); + if (fp && is_secured_file (iobuf_get_fd (fp))) + { + iobuf_close (fp); + fp = NULL; + gpg_err_set_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)) + { + armor_filter_context_t *afx = new_armor_context (); + rc = push_armor_filter (afx, fp); + if (rc) + log_error("failed to push armor filter"); + release_armor_context (afx); + } + } + rc = proc_packets (ctrl,NULL, fp); + iobuf_close(fp); + if (rc) + log_error("%s: decryption failed: %s\n", print_fname_stdin(filename), + gpg_strerror (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 ); + xfree(output); + reset_literals_seen(); + } + + set_next_passphrase(NULL); + release_progress_context (pfx); +} diff --git a/g10/dek.h b/g10/dek.h new file mode 100644 index 0000000..3654491 --- /dev/null +++ b/g10/dek.h @@ -0,0 +1,51 @@ +/* dek.h - The data encryption key structure. + * Copyright (C) 2014 Werner Koch + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ +#ifndef G10_DEK_H +#define G10_DEK_H + +typedef struct +{ + /* The algorithm (e.g., CIPHER_ALGO_AES). */ + int algo; + /* The length of the key (in bytes). */ + int keylen; + + /* Whether we've already printed information about this key. This + * is currently only used in decrypt_data() and only if we are in + * verbose mode. */ + unsigned int algo_info_printed : 1; + + /* AEAD shall be used. The value is the AEAD algo. */ + int use_aead : 4; + + /* MDC shall be used. */ + unsigned int use_mdc : 1; + + /* This key was read from a SK-ESK packet (see proc_symkey_enc). */ + unsigned int symmetric : 1; + + /* This is the largest used keylen (256 bit). */ + byte key[32]; + + /* The cacheid for the S2K. */ + char s2k_cacheid[1+16+1]; +} DEK; + + +#endif /*G10_DEK_H*/ diff --git a/g10/delkey.c b/g10/delkey.c new file mode 100644 index 0000000..13dbcf0 --- /dev/null +++ b/g10/delkey.c @@ -0,0 +1,382 @@ +/* delkey.c - delete keys + * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2004, + * 2005, 2006 Free Software Foundation, Inc. + * Copyright (C) 2014, 2019 Werner Koch + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include +#include +#include +#include +#include +#include + +#include "gpg.h" +#include "options.h" +#include "packet.h" +#include "../common/status.h" +#include "../common/iobuf.h" +#include "keydb.h" +#include "../common/util.h" +#include "main.h" +#include "trustdb.h" +#include "filter.h" +#include "../common/ttyio.h" +#include "../common/i18n.h" +#include "../common/shareddefs.h" +#include "call-agent.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 gpg_error_t +do_delete_key (ctrl_t ctrl, const char *username, int secret, int force, + int *r_sec_avail) +{ + gpg_error_t err; + kbnode_t keyblock = NULL; + kbnode_t node, kbctx; + kbnode_t targetnode; + KEYDB_HANDLE hd; + PKT_public_key *pk = NULL; + u32 keyid[2]; + int okay=0; + int yes; + KEYDB_SEARCH_DESC desc; + int exactmatch; /* True if key was found by fingerprint. */ + int thiskeyonly; /* 0 = false, 1 = is primary key, 2 = is a subkey. */ + + *r_sec_avail = 0; + + hd = keydb_new (); + if (!hd) + return gpg_error_from_syserror (); + + /* Search the userid. */ + err = classify_user_id (username, &desc, 1); + exactmatch = (desc.mode == KEYDB_SEARCH_MODE_FPR + || desc.mode == KEYDB_SEARCH_MODE_FPR16 + || desc.mode == KEYDB_SEARCH_MODE_FPR20); + thiskeyonly = desc.exact; + if (!err) + err = keydb_search (hd, &desc, 1, NULL); + if (err) + { + log_error (_("key \"%s\" not found: %s\n"), username, gpg_strerror (err)); + write_status_text (STATUS_DELETE_PROBLEM, "1"); + goto leave; + } + + /* Read the keyblock. */ + err = keydb_get_keyblock (hd, &keyblock); + if (err) + { + log_error (_("error reading keyblock: %s\n"), gpg_strerror (err) ); + goto leave; + } + + /* Get the keyid from the keyblock. */ + node = find_kbnode( keyblock, PKT_PUBLIC_KEY ); + if (!node) + { + log_error ("Oops; key not found anymore!\n"); + err = gpg_error (GPG_ERR_GENERAL); + goto leave; + } + + /* If an operation only on a subkey is requested, find that subkey + * now. */ + if (thiskeyonly) + { + kbnode_t tmpnode; + + for (kbctx=NULL; (tmpnode = walk_kbnode (keyblock, &kbctx, 0)); ) + { + if (!(tmpnode->pkt->pkttype == PKT_PUBLIC_KEY + || tmpnode->pkt->pkttype == PKT_PUBLIC_SUBKEY)) + continue; + if (exact_subkey_match_p (&desc, tmpnode)) + break; + } + if (!tmpnode) + { + log_error ("Oops; requested subkey not found anymore!\n"); + err = gpg_error (GPG_ERR_GENERAL); + goto leave; + } + /* Set NODE to this specific subkey or primary key. */ + thiskeyonly = node == tmpnode? 1 : 2; + targetnode = tmpnode; + } + else + targetnode = node; + + pk = targetnode->pkt->pkt.public_key; + keyid_from_pk (pk, keyid); + + if (!secret && !force) + { + if (have_secret_key_with_kid (keyid)) + { + *r_sec_avail = 1; + err = gpg_error (GPG_ERR_EOF); + goto leave; + } + else + err = 0; + } + + if (secret && !have_secret_key_with_kid (keyid)) + { + err = gpg_error (GPG_ERR_NOT_FOUND); + log_error (_("key \"%s\" not found\n"), username); + write_status_text (STATUS_DELETE_PROBLEM, "1"); + goto leave; + } + + + 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 (ctrl, pk); + else + print_pubkey_info (ctrl, NULL, pk ); + tty_printf( "\n" ); + + if (thiskeyonly == 1 && !secret) + { + /* We need to delete the entire public key despite the use + * of the thiskeyonly request. */ + tty_printf (_("Note: The public primary key and all its subkeys" + " will be deleted.\n")); + } + else if (thiskeyonly == 2 && !secret) + { + tty_printf (_("Note: Only the shown public subkey" + " will be deleted.\n")); + } + if (thiskeyonly == 1 && secret) + { + tty_printf (_("Note: Only the secret part of the shown primary" + " key will be deleted.\n")); + } + else if (thiskeyonly == 2 && secret) + { + tty_printf (_("Note: Only the secret part of the shown subkey" + " will be deleted.\n")); + } + + if (thiskeyonly) + 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) + { + if (secret) + { + char *prompt; + gpg_error_t firsterr = 0; + char *hexgrip; + + setup_main_keyids (keyblock); + for (kbctx=NULL; (node = walk_kbnode (keyblock, &kbctx, 0)); ) + { + if (!(node->pkt->pkttype == PKT_PUBLIC_KEY + || node->pkt->pkttype == PKT_PUBLIC_SUBKEY)) + continue; + + if (thiskeyonly && targetnode != node) + continue; + + if (agent_probe_secret_key (NULL, node->pkt->pkt.public_key)) + continue; /* No secret key for that public (sub)key. */ + + prompt = gpg_format_keydesc (ctrl, + node->pkt->pkt.public_key, + FORMAT_KEYDESC_DELKEY, 1); + err = hexkeygrip_from_pk (node->pkt->pkt.public_key, &hexgrip); + /* NB: We require --yes to advise the agent not to + * request a confirmation. The rationale for this extra + * pre-caution is that since 2.1 the secret key may also + * be used for other protocols and thus deleting it from + * the gpg would also delete the key for other tools. */ + if (!err && !opt.dry_run) + err = agent_delete_key (NULL, hexgrip, prompt, + opt.answer_yes); + xfree (prompt); + xfree (hexgrip); + if (err) + { + if (gpg_err_code (err) == GPG_ERR_KEY_ON_CARD) + write_status_text (STATUS_DELETE_PROBLEM, "1"); + log_error (_("deleting secret %s failed: %s\n"), + (node->pkt->pkttype == PKT_PUBLIC_KEY + ? _("key"):_("subkey")), + gpg_strerror (err)); + if (!firsterr) + firsterr = err; + if (gpg_err_code (err) == GPG_ERR_CANCELED + || gpg_err_code (err) == GPG_ERR_FULLY_CANCELED) + { + write_status_error ("delete_key.secret", err); + break; + } + } + + } + + err = firsterr; + if (firsterr) + goto leave; + } + else if (thiskeyonly == 2) + { + /* Delete the specified public subkey. */ + for (kbctx=NULL; (node = walk_kbnode (keyblock, &kbctx, 0)); ) + if (targetnode == node) + break; + log_assert (node); + delete_kbnode (node); + while ((node = walk_kbnode (keyblock, &kbctx, 0)) + && node->pkt->pkttype == PKT_SIGNATURE) + delete_kbnode (node); + + commit_kbnode (&keyblock); + err = keydb_update_keyblock (ctrl, hd, keyblock); + if (err) + { + log_error (_("update failed: %s\n"), gpg_strerror (err)); + goto leave; + } + } + else + { + err = keydb_delete_keyblock (hd); + if (err) + { + log_error (_("deleting keyblock failed: %s\n"), + gpg_strerror (err)); + 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 && !opt.dry_run && thiskeyonly != 2 + && clear_ownertrusts (ctrl, pk)) + { + if (opt.verbose) + log_info (_("ownertrust information cleared\n")); + } + } + + leave: + keydb_release (hd); + release_kbnode (keyblock); + return err; +} + + +/* + * Delete a public or secret key from a keyring. + */ +gpg_error_t +delete_keys (ctrl_t ctrl, strlist_t names, int secret, int allow_both) +{ + gpg_error_t err; + int avail; + int 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 ) + { + err = do_delete_key (ctrl, names->d, secret, force, &avail); + if (err && avail) + { + if (allow_both) + { + err = do_delete_key (ctrl, names->d, 1, 0, &avail); + if (!err) + err = do_delete_key (ctrl, 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 err; + } + } + + if (err) + { + log_error ("%s: delete key failed: %s\n", + names->d, gpg_strerror (err)); + if (gpg_err_code (err) == GPG_ERR_NO_PIN_ENTRY + && opt.batch && secret + && opt.pinentry_mode == PINENTRY_MODE_LOOPBACK) + log_info ("(try option \"--yes\" to delete anyway)\n"); + + return err; + } + } + + return 0; +} diff --git a/g10/distsigkey.gpg b/g10/distsigkey.gpg new file mode 100644 index 0000000..4c4db51 Binary files /dev/null and b/g10/distsigkey.gpg differ diff --git a/g10/ecdh.c b/g10/ecdh.c new file mode 100644 index 0000000..97d4838 --- /dev/null +++ b/g10/ecdh.c @@ -0,0 +1,516 @@ +/* ecdh.c - ECDH public key operations used in public key glue code + * Copyright (C) 2010, 2011 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include +#include +#include +#include +#include + +#include "gpg.h" +#include "../common/util.h" +#include "pkglue.h" +#include "main.h" +#include "options.h" + +/* A table with the default KEK parameters used by GnuPG. */ +static const struct +{ + unsigned int qbits; + int openpgp_hash_id; /* KEK digest algorithm. */ + int openpgp_cipher_id; /* KEK cipher algorithm. */ +} kek_params_table[] = + /* Note: Must be sorted by ascending values for QBITS. */ + { + { 256, DIGEST_ALGO_SHA256, CIPHER_ALGO_AES }, + { 384, DIGEST_ALGO_SHA384, CIPHER_ALGO_AES192 }, + + /* Note: 528 is 521 rounded to the 8 bit boundary */ + { 528, DIGEST_ALGO_SHA512, CIPHER_ALGO_AES256 } + }; + + + +/* Return KEK parameters as an opaque MPI The caller must free the + returned value. Returns NULL and sets ERRNO on error. */ +gcry_mpi_t +pk_ecdh_default_params (unsigned int qbits) +{ + byte *kek_params; + int i; + + kek_params = xtrymalloc (4); + if (!kek_params) + return NULL; + kek_params[0] = 3; /* Number of bytes to follow. */ + kek_params[1] = 1; /* Version for KDF+AESWRAP. */ + + /* Search for matching KEK parameter. Defaults to the strongest + possible choices. Performance is not an issue here, only + interoperability. */ + for (i=0; i < DIM (kek_params_table); i++) + { + if (kek_params_table[i].qbits >= qbits + || i+1 == DIM (kek_params_table)) + { + kek_params[2] = kek_params_table[i].openpgp_hash_id; + kek_params[3] = kek_params_table[i].openpgp_cipher_id; + break; + } + } + log_assert (i < DIM (kek_params_table)); + if (DBG_CRYPTO) + log_printhex (kek_params, sizeof(kek_params), "ECDH KEK params are"); + + return gcry_mpi_set_opaque (NULL, kek_params, 4 * 8); +} + + +/* Extract x-component from the point (SHARED,NSHARED) and strore it + * in a new buffer at R_SECRET_X. POINT_NBYTES is the size to + * represent an EC point which is determined by the public key. + * SECRET_X_SIZE is the size of x component to represent an integer + * which is determined by the curve. */ +static gpg_error_t +extract_secret_x (byte **r_secret_x, + const char *shared, size_t nshared, + size_t point_nbytes, size_t secret_x_size) +{ + byte *secret_x; + + *r_secret_x = NULL; + + /* Extract X from the result. It must be in the format of: + 04 || X || Y + 40 || X + 41 || X + + Since it may come with the prefix, the size of point is larger + than or equals to the size of an integer X. We also better check + that the provided shared point is not larger than the size needed + to represent the point. */ + if (point_nbytes < secret_x_size) + return gpg_error (GPG_ERR_BAD_DATA); + if (point_nbytes < nshared) + return gpg_error (GPG_ERR_BAD_DATA); + + /* Extract x component of the shared point: this is the actual + shared secret. */ + secret_x = xtrymalloc_secure (point_nbytes); + if (!secret_x) + return gpg_error_from_syserror (); + + memcpy (secret_x, shared, nshared); + + /* Wrangle the provided point unless only the x-component w/o any + * prefix was provided. */ + if (nshared != secret_x_size) + { + /* Remove the prefix. */ + if ((point_nbytes & 1)) + memmove (secret_x, secret_x+1, secret_x_size); + + /* Clear the rest of data. */ + if (point_nbytes - secret_x_size) + memset (secret_x+secret_x_size, 0, point_nbytes-secret_x_size); + } + + if (DBG_CRYPTO) + log_printhex (secret_x, secret_x_size, "ECDH shared secret X is:"); + + *r_secret_x = secret_x; + return 0; +} + + +/* Encrypts/decrypts DATA using a key derived from the ECC shared + point (SHARED,NSHARED) using the FIPS SP 800-56A compliant method + key_derivation+key_wrapping. If IS_ENCRYPT is true the function + encrypts; if false, it decrypts. PKEY is the public key and PK_FP + the fingerprint of this public key. On success the result is + stored at R_RESULT; on failure NULL is stored at R_RESULT and an + error code returned. */ +gpg_error_t +pk_ecdh_encrypt_with_shared_point (int is_encrypt, + const char *shared, size_t nshared, + const byte pk_fp[MAX_FINGERPRINT_LEN], + gcry_mpi_t data, gcry_mpi_t *pkey, + gcry_mpi_t *r_result) +{ + gpg_error_t err; + byte *secret_x; + int secret_x_size; + unsigned int nbits; + const unsigned char *kek_params; + size_t kek_params_size; + int kdf_hash_algo; + int kdf_encr_algo; + size_t kek_size; + unsigned char message[256]; + size_t message_size; + + *r_result = NULL; + + if (!gcry_mpi_get_flag (pkey[2], GCRYMPI_FLAG_OPAQUE)) + return gpg_error (GPG_ERR_BUG); + + kek_params = gcry_mpi_get_opaque (pkey[2], &nbits); + kek_params_size = (nbits+7)/8; + + if (DBG_CRYPTO) + log_printhex (kek_params, kek_params_size, "ecdh KDF params:"); + + /* Expect 4 bytes 03 01 hash_alg symm_alg. */ + if (kek_params_size != 4 || kek_params[0] != 3 || kek_params[1] != 1) + return gpg_error (GPG_ERR_BAD_PUBKEY); + + kdf_hash_algo = kek_params[2]; + kdf_encr_algo = kek_params[3]; + + if (DBG_CRYPTO) + log_debug ("ecdh KDF algorithms %s+%s with aeswrap\n", + openpgp_md_algo_name (kdf_hash_algo), + openpgp_cipher_algo_name (kdf_encr_algo)); + + if (kdf_hash_algo != GCRY_MD_SHA256 + && kdf_hash_algo != GCRY_MD_SHA384 + && kdf_hash_algo != GCRY_MD_SHA512) + return gpg_error (GPG_ERR_BAD_PUBKEY); + + if (kdf_encr_algo != CIPHER_ALGO_AES + && kdf_encr_algo != CIPHER_ALGO_AES192 + && kdf_encr_algo != CIPHER_ALGO_AES256) + return gpg_error (GPG_ERR_BAD_PUBKEY); + + kek_size = gcry_cipher_get_algo_keylen (kdf_encr_algo); + if (kek_size > gcry_md_get_algo_dlen (kdf_hash_algo)) + return gpg_error (GPG_ERR_BAD_PUBKEY); + + + nbits = pubkey_nbits (PUBKEY_ALGO_ECDH, pkey); + if (!nbits) + return gpg_error (GPG_ERR_TOO_SHORT); + + /* Expected size of the x component */ + secret_x_size = (nbits+7)/8; + + if (kek_size > secret_x_size) + return gpg_error (GPG_ERR_BAD_PUBKEY); + + err = extract_secret_x (&secret_x, shared, nshared, + (mpi_get_nbits (pkey[1] /* public point */)+7)/8, + secret_x_size); + if (err) + return err; + + /*** We have now the shared secret bytes in secret_x. ***/ + + /* At this point we are done with PK encryption and the rest of the + * function uses symmetric key encryption techniques to protect the + * input DATA. The following two sections will simply replace + * current secret_x with a value derived from it. This will become + * a KEK. + */ + + + /* Build kdf_params. */ + { + IOBUF obuf; + + obuf = iobuf_temp(); + /* variable-length field 1, curve name OID */ + err = gpg_mpi_write_nohdr (obuf, pkey[0]); + /* fixed-length field 2 */ + iobuf_put (obuf, PUBKEY_ALGO_ECDH); + /* variable-length field 3, KDF params */ + err = (err ? err : gpg_mpi_write_nohdr (obuf, pkey[2])); + /* fixed-length field 4 */ + iobuf_write (obuf, "Anonymous Sender ", 20); + /* fixed-length field 5, recipient fp */ + iobuf_write (obuf, pk_fp, 20); + + message_size = iobuf_temp_to_buffer (obuf, message, sizeof message); + iobuf_close (obuf); + if (err) + { + xfree (secret_x); + return err; + } + + if(DBG_CRYPTO) + log_printhex (message, message_size, "ecdh KDF message params are:"); + } + + /* Derive a KEK (key wrapping key) using MESSAGE and SECRET_X. */ + { + gcry_md_hd_t h; + int old_size; + + err = gcry_md_open (&h, kdf_hash_algo, 0); + if (err) + { + log_error ("gcry_md_open failed for kdf_hash_algo %d: %s", + kdf_hash_algo, gpg_strerror (err)); + xfree (secret_x); + return err; + } + gcry_md_write(h, "\x00\x00\x00\x01", 4); /* counter = 1 */ + gcry_md_write(h, secret_x, secret_x_size); /* x of the point X */ + gcry_md_write(h, message, message_size); /* KDF parameters */ + + gcry_md_final (h); + + log_assert( gcry_md_get_algo_dlen (kdf_hash_algo) >= 32 ); + + memcpy (secret_x, gcry_md_read (h, kdf_hash_algo), + gcry_md_get_algo_dlen (kdf_hash_algo)); + gcry_md_close (h); + + old_size = secret_x_size; + log_assert( old_size >= gcry_cipher_get_algo_keylen( kdf_encr_algo ) ); + secret_x_size = gcry_cipher_get_algo_keylen( kdf_encr_algo ); + log_assert( secret_x_size <= gcry_md_get_algo_dlen (kdf_hash_algo) ); + + /* We could have allocated more, so clean the tail before returning. */ + memset (secret_x+secret_x_size, 0, old_size - secret_x_size); + if (DBG_CRYPTO) + log_printhex (secret_x, secret_x_size, "ecdh KEK is:"); + } + + /* And, finally, aeswrap with key secret_x. */ + { + gcry_cipher_hd_t hd; + size_t nbytes; + + byte *data_buf; + int data_buf_size; + + gcry_mpi_t result; + + err = gcry_cipher_open (&hd, kdf_encr_algo, GCRY_CIPHER_MODE_AESWRAP, 0); + if (err) + { + log_error ("ecdh failed to initialize AESWRAP: %s\n", + gpg_strerror (err)); + xfree (secret_x); + return err; + } + + err = gcry_cipher_setkey (hd, secret_x, secret_x_size); + xfree (secret_x); + secret_x = NULL; + if (err) + { + gcry_cipher_close (hd); + log_error ("ecdh failed in gcry_cipher_setkey: %s\n", + gpg_strerror (err)); + return err; + } + + data_buf_size = (gcry_mpi_get_nbits(data)+7)/8; + if ((data_buf_size & 7) != (is_encrypt ? 0 : 1)) + { + log_error ("can't use a shared secret of %d bytes for ecdh\n", + data_buf_size); + return gpg_error (GPG_ERR_BAD_DATA); + } + + data_buf = xtrymalloc_secure( 1 + 2*data_buf_size + 8); + if (!data_buf) + { + err = gpg_error_from_syserror (); + gcry_cipher_close (hd); + return err; + } + + if (is_encrypt) + { + byte *in = data_buf+1+data_buf_size+8; + + /* Write data MPI into the end of data_buf. data_buf is size + aeswrap data. */ + err = gcry_mpi_print (GCRYMPI_FMT_USG, in, + data_buf_size, &nbytes, data/*in*/); + if (err) + { + log_error ("ecdh failed to export DEK: %s\n", gpg_strerror (err)); + gcry_cipher_close (hd); + xfree (data_buf); + return err; + } + + if (DBG_CRYPTO) + log_printhex (in, data_buf_size, "ecdh encrypting :"); + + err = gcry_cipher_encrypt (hd, data_buf+1, data_buf_size+8, + in, data_buf_size); + memset (in, 0, data_buf_size); + gcry_cipher_close (hd); + if (err) + { + log_error ("ecdh failed in gcry_cipher_encrypt: %s\n", + gpg_strerror (err)); + xfree (data_buf); + return err; + } + data_buf[0] = data_buf_size+8; + + if (DBG_CRYPTO) + log_printhex (data_buf+1, data_buf[0], "ecdh encrypted to:"); + + result = gcry_mpi_set_opaque (NULL, data_buf, 8 * (1+data_buf[0])); + if (!result) + { + err = gpg_error_from_syserror (); + xfree (data_buf); + log_error ("ecdh failed to create an MPI: %s\n", + gpg_strerror (err)); + return err; + } + + *r_result = result; + } + else + { + byte *in; + const void *p; + + p = gcry_mpi_get_opaque (data, &nbits); + nbytes = (nbits+7)/8; + if (!p || nbytes > data_buf_size || !nbytes) + { + xfree (data_buf); + return gpg_error (GPG_ERR_BAD_MPI); + } + memcpy (data_buf, p, nbytes); + if (data_buf[0] != nbytes-1) + { + log_error ("ecdh inconsistent size\n"); + xfree (data_buf); + return gpg_error (GPG_ERR_BAD_MPI); + } + in = data_buf+data_buf_size; + data_buf_size = data_buf[0]; + + if (DBG_CRYPTO) + log_printhex (data_buf+1, data_buf_size, "ecdh decrypting :"); + + err = gcry_cipher_decrypt (hd, in, data_buf_size, data_buf+1, + data_buf_size); + gcry_cipher_close (hd); + if (err) + { + log_error ("ecdh failed in gcry_cipher_decrypt: %s\n", + gpg_strerror (err)); + xfree (data_buf); + return err; + } + + data_buf_size -= 8; + + if (DBG_CRYPTO) + log_printhex (in, data_buf_size, "ecdh decrypted to :"); + + /* Padding is removed later. */ + /* if (in[data_buf_size-1] > 8 ) */ + /* { */ + /* log_error ("ecdh failed at decryption: invalid padding." */ + /* " 0x%02x > 8\n", in[data_buf_size-1] ); */ + /* return gpg_error (GPG_ERR_BAD_KEY); */ + /* } */ + + err = gcry_mpi_scan (&result, GCRYMPI_FMT_USG, in, data_buf_size, NULL); + xfree (data_buf); + if (err) + { + log_error ("ecdh failed to create a plain text MPI: %s\n", + gpg_strerror (err)); + return err; + } + + *r_result = result; + } + } + + return err; +} + + +static gcry_mpi_t +gen_k (unsigned nbits) +{ + gcry_mpi_t k; + + k = gcry_mpi_snew (nbits); + if (DBG_CRYPTO) + log_debug ("choosing a random k of %u bits\n", nbits); + + gcry_mpi_randomize (k, nbits-1, GCRY_STRONG_RANDOM); + + if (DBG_CRYPTO) + { + unsigned char *buffer; + if (gcry_mpi_aprint (GCRYMPI_FMT_HEX, &buffer, NULL, k)) + BUG (); + log_debug ("ephemeral scalar MPI #0: %s\n", buffer); + gcry_free (buffer); + } + + return k; +} + + +/* Generate an ephemeral key for the public ECDH key in PKEY. On + success the generated key is stored at R_K; on failure NULL is + stored at R_K and an error code returned. */ +gpg_error_t +pk_ecdh_generate_ephemeral_key (gcry_mpi_t *pkey, gcry_mpi_t *r_k) +{ + unsigned int nbits; + gcry_mpi_t k; + + *r_k = NULL; + + nbits = pubkey_nbits (PUBKEY_ALGO_ECDH, pkey); + if (!nbits) + return gpg_error (GPG_ERR_TOO_SHORT); + k = gen_k (nbits); + if (!k) + BUG (); + + *r_k = k; + return 0; +} + + + +/* Perform ECDH decryption. */ +int +pk_ecdh_decrypt (gcry_mpi_t *result, const byte sk_fp[MAX_FINGERPRINT_LEN], + gcry_mpi_t data, + const char *shared, size_t nshared, + gcry_mpi_t *skey) +{ + if (!data) + return gpg_error (GPG_ERR_BAD_MPI); + return pk_ecdh_encrypt_with_shared_point (0 /*=decryption*/, + shared, nshared, + sk_fp, data/*encr data as an MPI*/, + skey, result); +} diff --git a/g10/encrypt.c b/g10/encrypt.c new file mode 100644 index 0000000..5f9480f --- /dev/null +++ b/g10/encrypt.c @@ -0,0 +1,1060 @@ +/* encrypt.c - Main encryption driver + * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, + * 2006, 2009 Free Software Foundation, Inc. + * Copyright (C) 2016, 2022 g10 Code GmbH + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include +#include +#include +#include +#include + +#include "gpg.h" +#include "options.h" +#include "packet.h" +#include "../common/status.h" +#include "../common/iobuf.h" +#include "keydb.h" +#include "../common/util.h" +#include "main.h" +#include "filter.h" +#include "trustdb.h" +#include "../common/i18n.h" +#include "../common/status.h" +#include "pkglue.h" +#include "../common/compliance.h" + + +static int encrypt_simple( const char *filename, int mode, int use_seskey ); +static int write_pubkey_enc_from_list (ctrl_t ctrl, + PK_LIST pk_list, DEK *dek, iobuf_t out); + +/**************** + * Encrypt FILENAME with only the symmetric cipher. Take input from + * stdin if FILENAME is NULL. + */ +int +encrypt_symmetric (const char *filename) +{ + return encrypt_simple( filename, 1, 0 ); +} + + +/**************** + * Encrypt FILENAME as a literal data packet only. Take input from + * stdin if FILENAME is NULL. + */ +int +encrypt_store (const char *filename) +{ + return encrypt_simple( filename, 0, 0 ); +} + + +/* Create an setup DEK structure and print approriate warnings. The + * FALLBACK_TO_3DES flag is used to handle the two different ways we + * use this code. PK_LIST gives the list of public keys. Always + * returns a DEK. The actual session needs to be added later. */ +static DEK * +create_dek_with_warnings (int fallback_to_3des, pk_list_t pk_list) +{ + DEK *dek; + + dek = xmalloc_secure_clear (sizeof *dek); + if (!opt.def_cipher_algo) + { + /* Try to get it from the prefs. */ + dek->algo = select_algo_from_prefs (pk_list, PREFTYPE_SYM, -1, NULL); + if (dek->algo == -1 && fallback_to_3des) + { + /* 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. */ + dek->algo = CIPHER_ALGO_3DES; + } + else if (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. */ + dek->algo = DEFAULT_CIPHER_ALGO; + } + + /* In case 3DES has been selected, print a warning if any key + * does not have a preference for AES. This should help to + * indentify why encrypting to several recipients falls back to + * 3DES. */ + if (opt.verbose && dek->algo == CIPHER_ALGO_3DES) + warn_missing_aes_from_pklist (pk_list); + } + 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"), + openpgp_cipher_algo_name (opt.def_cipher_algo), + opt.def_cipher_algo); + } + + dek->algo = opt.def_cipher_algo; + } + + return dek; +} + + +/* *SESKEY contains the unencrypted session key ((*SESKEY)->KEY) and + the algorithm that will be used to encrypt the contents of the SED + packet ((*SESKEY)->ALGO). If *SESKEY is NULL, then a random + session key that is appropriate for DEK->ALGO is generated and + stored there. + + Encrypt that session key using DEK and store the result in ENCKEY, + which must be large enough to hold (*SESKEY)->KEYLEN + 1 bytes. */ +void +encrypt_seskey (DEK *dek, DEK **seskey, byte *enckey) +{ + gcry_cipher_hd_t hd; + byte buf[33]; + + log_assert ( dek->keylen <= 32 ); + if (!*seskey) + { + *seskey=xmalloc_clear(sizeof(DEK)); + (*seskey)->algo=dek->algo; + make_session_key(*seskey); + /*log_hexdump( "thekey", c->key, c->keylen );*/ + } + + /* The encrypted session key is prefixed with a one-octet algorithm id. */ + buf[0] = (*seskey)->algo; + memcpy( buf + 1, (*seskey)->key, (*seskey)->keylen ); + + /* We only pass already checked values to the following function, + thus we consider any failure as fatal. */ + if (openpgp_cipher_open (&hd, dek->algo, GCRY_CIPHER_MODE_CFB, 1)) + BUG (); + if (gcry_cipher_setkey (hd, dek->key, dek->keylen)) + BUG (); + gcry_cipher_setiv (hd, NULL, 0); + gcry_cipher_encrypt (hd, buf, (*seskey)->keylen + 1, NULL, 0); + gcry_cipher_close (hd); + + memcpy( enckey, buf, (*seskey)->keylen + 1 ); + wipememory( buf, sizeof buf ); /* burn key */ +} + + +/* Shall we use the MDC? Yes - unless rfc-2440 compatibility is + * requested. Must return 1 or 0. */ +int +use_mdc (pk_list_t pk_list,int algo) +{ + (void)pk_list; + (void)algo; + + /* RFC-2440 don't has MDC - this is the only way to create a legacy + * non-MDC encryption packet. */ + if (RFC2440) + return 0; + + return 1; /* In all other cases we use the 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 +encrypt_simple (const char *filename, int mode, int use_seskey) +{ + iobuf_t 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 = NULL; + compress_filter_context_t zfx; + text_filter_context_t tfx; + progress_filter_context_t *pfx; + int do_compress = !!default_compress_algo(); + + if (!gnupg_rng_is_compliant (opt.compliance)) + { + rc = gpg_error (GPG_ERR_FORBIDDEN); + log_error (_("%s is not compliant with %s mode\n"), + "RNG", + gnupg_compliance_option_string (opt.compliance)); + write_status_error ("random-compliance", rc); + return rc; + } + + pfx = new_progress_context (); + memset( &cfx, 0, sizeof cfx); + memset( &zfx, 0, sizeof zfx); + memset( &tfx, 0, sizeof tfx); + init_packet(&pkt); + + /* Prepare iobufs. */ + inp = iobuf_open(filename); + if (inp) + iobuf_ioctl (inp, IOBUF_IOCTL_NO_CACHE, 1, NULL); + if (inp && is_secured_file (iobuf_get_fd (inp))) + { + iobuf_close (inp); + inp = NULL; + gpg_err_set_errno (EPERM); + } + if (!inp) + { + rc = gpg_error_from_syserror (); + log_error(_("can't open '%s': %s\n"), filename? filename: "[stdin]", + strerror(errno) ); + release_progress_context (pfx); + return rc; + } + + handle_progress (pfx, inp, filename); + + if (opt.textmode) + iobuf_push_filter( inp, text_filter, &tfx ); + + cfx.dek = NULL; + if ( mode ) + { + rc = setup_symkey (&s2k, &cfx.dek); + if (rc) + { + iobuf_close (inp); + if (gpg_err_code (rc) == GPG_ERR_CIPHER_ALGO + || gpg_err_code (rc) == GPG_ERR_DIGEST_ALGO) + ; /* Error has already been printed. */ + else + log_error (_("error creating passphrase: %s\n"), gpg_strerror (rc)); + release_progress_context (pfx); + 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; /* Dummy. */ + + seskeylen = openpgp_cipher_get_algo_keylen (default_cipher_algo ()); + encrypt_seskey( cfx.dek, &dek, enckey ); + xfree( cfx.dek ); cfx.dek = dek; + } + + if (opt.verbose) + log_info(_("using cipher %s\n"), + openpgp_cipher_algo_name (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 (-1, filename, opt.armor? 1:0, 0, &out ))) + { + iobuf_cancel (inp); + xfree (cfx.dek); + xfree (s2k); + release_progress_context (pfx); + return rc; + } + + if ( opt.armor ) + { + afx = new_armor_context (); + push_armor_filter (afx, out); + } + + if ( s2k ) + { + 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", gpg_strerror (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 && opt.verbose) + 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 + 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) + { + /* Note that PT has been initialized above in !no_literal mode. */ + pt->timestamp = make_timestamp(); + pt->mode = opt.mimemode? 'm' : opt.textmode? 't' : 'b'; + pt->len = filesize; + pt->new_ctb = !pt->len; + 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_cfb, &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", gpg_strerror (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 ( (rc=iobuf_write(out, copy_buffer, bytes_copied)) ) { + log_error ("copying input to output failed: %s\n", + gpg_strerror (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, NULL); + xfree (cfx.dek); + xfree (s2k); + release_armor_context (afx); + release_progress_context (pfx); + return rc; +} + + +gpg_error_t +setup_symkey (STRING2KEY **symkey_s2k, DEK **symkey_dek) +{ + int canceled; + int defcipher; + int s2kdigest; + + defcipher = default_cipher_algo (); + if (!gnupg_cipher_is_allowed (opt.compliance, 1, defcipher, + GCRY_CIPHER_MODE_CFB)) + { + log_error (_("cipher algorithm '%s' may not be used in %s mode\n"), + openpgp_cipher_algo_name (defcipher), + gnupg_compliance_option_string (opt.compliance)); + return gpg_error (GPG_ERR_CIPHER_ALGO); + } + + s2kdigest = S2K_DIGEST_ALGO; + if (!gnupg_digest_is_allowed (opt.compliance, 1, s2kdigest)) + { + log_error (_("digest algorithm '%s' may not be used in %s mode\n"), + gcry_md_algo_name (s2kdigest), + gnupg_compliance_option_string (opt.compliance)); + return gpg_error (GPG_ERR_DIGEST_ALGO); + } + + *symkey_s2k = xmalloc_clear (sizeof **symkey_s2k); + (*symkey_s2k)->mode = opt.s2k_mode; + (*symkey_s2k)->hash_algo = s2kdigest; + + *symkey_dek = passphrase_to_dek (defcipher, + *symkey_s2k, 1, 0, NULL, 0, &canceled); + if (!*symkey_dek || !(*symkey_dek)->keylen) + { + xfree(*symkey_dek); + xfree(*symkey_s2k); + return gpg_error (canceled?GPG_ERR_CANCELED:GPG_ERR_INV_PASSPHRASE); + } + + return 0; +} + + +static int +write_symkey_enc (STRING2KEY *symkey_s2k, DEK *symkey_dek, DEK *dek, + iobuf_t out) +{ + int rc, seskeylen = openpgp_cipher_get_algo_keylen (dek->algo); + + PKT_symkey_enc *enc; + byte enckey[33]; + PACKET pkt; + + enc=xmalloc_clear(sizeof(PKT_symkey_enc)+seskeylen+1); + encrypt_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",gpg_strerror (rc)); + + xfree(enc); + return rc; +} + + +/* Check whether all encryption keys are compliant with the current + * mode and issue respective status lines. DEK has the info about the + * session key and PK_LIST the list of public keys. */ +static gpg_error_t +check_encryption_compliance (DEK *dek, pk_list_t pk_list) +{ + gpg_error_t err = 0; + pk_list_t pkr; + int compliant; + + if (! gnupg_cipher_is_allowed (opt.compliance, 1, dek->algo, + GCRY_CIPHER_MODE_CFB)) + { + log_error (_("cipher algorithm '%s' may not be used in %s mode\n"), + openpgp_cipher_algo_name (dek->algo), + gnupg_compliance_option_string (opt.compliance)); + err = gpg_error (GPG_ERR_CIPHER_ALGO); + goto leave; + } + + if (!gnupg_rng_is_compliant (opt.compliance)) + { + err = gpg_error (GPG_ERR_FORBIDDEN); + log_error (_("%s is not compliant with %s mode\n"), + "RNG", + gnupg_compliance_option_string (opt.compliance)); + write_status_error ("random-compliance", err); + goto leave; + } + + /* From here on we only test for CO_DE_VS - if we ever want to + * return other compliance mode values we need to change this to + * loop over all those values. */ + compliant = gnupg_gcrypt_is_compliant (CO_DE_VS); + + if (!gnupg_cipher_is_compliant (CO_DE_VS, dek->algo, GCRY_CIPHER_MODE_CFB)) + compliant = 0; + + for (pkr = pk_list; pkr; pkr = pkr->next) + { + PKT_public_key *pk = pkr->pk; + unsigned int nbits = nbits_from_pk (pk); + + if (!gnupg_pk_is_compliant (opt.compliance, pk->pubkey_algo, 0, + pk->pkey, nbits, NULL)) + log_info (_("WARNING: key %s is not suitable for encryption" + " in %s mode\n"), + keystr_from_pk (pk), + gnupg_compliance_option_string (opt.compliance)); + + if (compliant + && !gnupg_pk_is_compliant (CO_DE_VS, pk->pubkey_algo, 0, pk->pkey, + nbits, NULL)) + compliant = 0; /* Not compliant - reset flag. */ + } + + /* If we are compliant print the status for de-vs compliance. */ + if (compliant) + write_status_strings (STATUS_ENCRYPTION_COMPLIANCE_MODE, + gnupg_status_compliance_flag (CO_DE_VS), + NULL); + + /* Check whether we should fail the operation. */ + if (opt.flags.require_compliance + && opt.compliance == CO_DE_VS + && !compliant) + { + compliance_failure (); + err = gpg_error (GPG_ERR_FORBIDDEN); + goto leave; + } + + leave: + return err; +} + + +/* + * Encrypt the file with the given userids (or ask if none is + * supplied). Either FILENAME or FILEFD must be given, but not both. + * The caller may provide a checked list of public keys in + * PROVIDED_PKS; if not the function builds a list of keys on its own. + * + * Note that FILEFD is currently only used by cmd_encrypt in the + * not yet finished server.c. + */ +int +encrypt_crypt (ctrl_t ctrl, int filefd, const char *filename, + strlist_t remusr, int use_symkey, pk_list_t provided_keys, + int outputfd) +{ + iobuf_t inp = NULL; + iobuf_t 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 = NULL; + compress_filter_context_t zfx; + text_filter_context_t tfx; + progress_filter_context_t *pfx; + PK_LIST pk_list; + int do_compress; + + if (filefd != -1 && filename) + return gpg_error (GPG_ERR_INV_ARG); /* Both given. */ + + do_compress = !!opt.compress_algo; + + pfx = new_progress_context (); + memset( &cfx, 0, sizeof cfx); + memset( &zfx, 0, sizeof zfx); + memset( &tfx, 0, sizeof tfx); + init_packet(&pkt); + + if (use_symkey + && (rc=setup_symkey(&symkey_s2k,&symkey_dek))) + { + release_progress_context (pfx); + return rc; + } + + if (provided_keys) + pk_list = provided_keys; + else + { + if ((rc = build_pk_list (ctrl, remusr, &pk_list))) + { + release_progress_context (pfx); + return rc; + } + } + + /* Prepare iobufs. */ +#ifdef HAVE_W32_SYSTEM + if (filefd == -1) + inp = iobuf_open (filename); + else + { + inp = NULL; + gpg_err_set_errno (ENOSYS); + } +#else + if (filefd == GNUPG_INVALID_FD) + inp = iobuf_open (filename); + else + inp = iobuf_fdopen_nc (FD2INT(filefd), "rb"); +#endif + if (inp) + iobuf_ioctl (inp, IOBUF_IOCTL_NO_CACHE, 1, NULL); + if (inp && is_secured_file (iobuf_get_fd (inp))) + { + iobuf_close (inp); + inp = NULL; + gpg_err_set_errno (EPERM); + } + if (!inp) + { + char xname[64]; + + rc = gpg_error_from_syserror (); + if (filefd != -1) + snprintf (xname, sizeof xname, "[fd %d]", filefd); + else if (!filename) + strcpy (xname, "[stdin]"); + else + *xname = 0; + log_error (_("can't open '%s': %s\n"), + *xname? xname : filename, gpg_strerror (rc) ); + goto leave; + } + + if (opt.verbose) + log_info (_("reading from '%s'\n"), iobuf_get_fname_nonnull (inp)); + + handle_progress (pfx, inp, filename); + + if (opt.textmode) + iobuf_push_filter (inp, text_filter, &tfx); + + rc = open_outfile (outputfd, filename, opt.armor? 1:0, 0, &out); + if (rc) + goto leave; + + if (opt.armor) + { + afx = new_armor_context (); + push_armor_filter (afx, out); + } + + /* Create a session key (a DEK). */ + cfx.dek = create_dek_with_warnings (1, pk_list); + + /* Check compliance etc. */ + rc = check_encryption_compliance (cfx.dek, pk_list); + if (rc) + goto leave; + + 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_CRYPTO) + log_printhex (cfx.dek->key, cfx.dek->keylen, "DEK is: "); + + rc = write_pubkey_enc_from_list (ctrl, 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) + pt = setup_plaintext_name (filename, inp); + + /* Get the size of the file if possible, i.e., if it is a real file. */ + if (filename && *filename + && !iobuf_is_pipe_filename (filename) && !opt.textmode ) + { + off_t tmpsize; + int overflow; + + if ( !(tmpsize = iobuf_get_filelength(inp, &overflow)) + && !overflow && opt.verbose) + 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 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.mimemode? 'm' : opt.textmode ? 't' : 'b'; + pt->len = filesize; + pt->new_ctb = !pt->len; + 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_cfb, &cfx); + + /* Register the compress filter. */ + if (do_compress) + { + int compr_algo = opt.compress_algo; + + if (compr_algo == -1) + { + compr_algo = select_algo_from_prefs (pk_list, PREFTYPE_ZIP, -1, NULL); + if (compr_algo == -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", gpg_strerror (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) + { + rc = iobuf_write (out, copy_buffer, bytes_copied); + if (rc) + { + log_error ("copying input to output failed: %s\n", + gpg_strerror (rc)); + break; + } + } + wipememory (copy_buffer, 4096); /* Burn the 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, NULL); + xfree (cfx.dek); + xfree (symkey_dek); + xfree (symkey_s2k); + if (!provided_keys) + release_pk_list (pk_list); + release_armor_context (afx); + release_progress_context (pfx); + return rc; +} + + +/* + * Filter to do a complete public key encryption. + */ +int +encrypt_filter (void *opaque, int control, + iobuf_t 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->header_okay = 1; + + efx->cfx.dek = create_dek_with_warnings (0, efx->pk_list); + + rc = check_encryption_compliance (efx->cfx.dek, efx->pk_list); + if (rc) + return rc; + + efx->cfx.dek->use_mdc = use_mdc (efx->pk_list,efx->cfx.dek->algo); + + make_session_key ( efx->cfx.dek ); + if (DBG_CRYPTO) + log_printhex (efx->cfx.dek->key, efx->cfx.dek->keylen, "DEK is: "); + + rc = write_pubkey_enc_from_list (efx->ctrl, + 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_cfb, &efx->cfx); + } + rc = iobuf_write (a, buf, size); + + } + else if (control == IOBUFCTRL_FREE) + { + xfree (efx->symkey_dek); + xfree (efx->symkey_s2k); + } + else if ( control == IOBUFCTRL_DESC ) + { + mem2str (buf, "encrypt_filter", *ret_len); + } + return rc; +} + + +/* + * Write a pubkey-enc packet for the public key PK to OUT. + */ +int +write_pubkey_enc (ctrl_t ctrl, + PKT_public_key *pk, int throw_keyid, DEK *dek, iobuf_t out) +{ + PACKET pkt; + PKT_pubkey_enc *enc; + int rc; + gcry_mpi_t frame; + + 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 = throw_keyid; + + /* 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 (pk->pubkey_algo, dek, + pubkey_nbits (pk->pubkey_algo, pk->pkey)); + rc = pk_encrypt (pk->pubkey_algo, enc->data, frame, pk, pk->pkey); + gcry_mpi_release (frame); + if (rc) + log_error ("pubkey_encrypt failed: %s\n", gpg_strerror (rc) ); + else + { + if ( opt.verbose ) + { + char *ustr = get_user_id_string_native (ctrl, enc->keyid); + log_info (_("%s/%s encrypted for: \"%s\"\n"), + openpgp_pk_algo_name (enc->pubkey_algo), + openpgp_cipher_algo_name (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", + gpg_strerror (rc)); + } + free_pubkey_enc(enc); + return rc; +} + + +/* + * Write pubkey-enc packets from the list of PKs to OUT. + */ +static int +write_pubkey_enc_from_list (ctrl_t ctrl, PK_LIST pk_list, DEK *dek, iobuf_t out) +{ + if (opt.throw_keyids && (PGP6 || PGP7 || PGP8)) + { + log_info(_("option '%s' may not be used in %s mode\n"), + "--throw-keyids", + gnupg_compliance_option_string (opt.compliance)); + compliance_failure(); + } + + for ( ; pk_list; pk_list = pk_list->next ) + { + PKT_public_key *pk = pk_list->pk; + int throw_keyid = (opt.throw_keyids || (pk_list->flags&1)); + int rc = write_pubkey_enc (ctrl, pk, throw_keyid, dek, out); + if (rc) + return rc; + } + + return 0; +} + +void +encrypt_crypt_files (ctrl_t ctrl, int nfiles, char **files, strlist_t 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); + rc = encrypt_crypt (ctrl, -1, line, remusr, 0, NULL, -1); + if (rc) + log_error ("encryption of '%s' failed: %s\n", + print_fname_stdin(line), gpg_strerror (rc) ); + write_status( STATUS_FILE_DONE ); + } + } + else + { + while (nfiles--) + { + print_file_status(STATUS_FILE_START, *files, 2); + if ( (rc = encrypt_crypt (ctrl, -1, *files, remusr, 0, NULL, -1)) ) + log_error("encryption of '%s' failed: %s\n", + print_fname_stdin(*files), gpg_strerror (rc) ); + write_status( STATUS_FILE_DONE ); + files++; + } + } +} diff --git a/g10/exec.c b/g10/exec.c new file mode 100644 index 0000000..bf0d8f2 --- /dev/null +++ b/g10/exec.c @@ -0,0 +1,705 @@ +/* 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 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +/* + FIXME: We should replace most code in this module by our + spawn implementation from common/exechelp.c. + */ + + +#include +#include +#include +#include +#include +#include +#ifndef EXEC_TEMPFILE_ONLY +#include +#endif +#ifdef HAVE_DOSISH_SYSTEM +# ifdef HAVE_WINSOCK2_H +# include +# endif +# include +#endif +#include +#include +#include +#include + +#include "gpg.h" +#include "options.h" +#include "../common/i18n.h" +#include "../common/iobuf.h" +#include "../common/util.h" +#include "../common/membuf.h" +#include "../common/sysutils.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 GPG_ERR_GENERAL; +} + +int +exec_read(struct exec_info *info) { return GPG_ERR_GENERAL; } +int +exec_finish(struct exec_info *info) { return GPG_ERR_GENERAL; } +int +set_exec_path(const char *path) { return GPG_ERR_GENERAL; } + +#else /* ! NO_EXEC */ + +#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 +w32_system(const char *command) +{ + if (!strncmp (command, "!ShellExecute ", 14)) + { + SHELLEXECUTEINFOW see; + wchar_t *wname; + int waitms; + + command = command + 14; + while (spacep (command)) + command++; + waitms = atoi (command); + if (waitms < 0) + waitms = 0; + else if (waitms > 60*1000) + waitms = 60000; + while (*command && !spacep (command)) + command++; + while (spacep (command)) + command++; + + wname = utf8_to_wchar (command); + if (!wname) + return -1; + + memset (&see, 0, sizeof see); + see.cbSize = sizeof see; + see.fMask = (SEE_MASK_NOCLOSEPROCESS + | SEE_MASK_NOASYNC + | SEE_MASK_FLAG_NO_UI + | SEE_MASK_NO_CONSOLE); + see.lpVerb = L"open"; + see.lpFile = (LPCWSTR)wname; + see.nShow = SW_SHOW; + + if (DBG_EXTPROG) + log_debug ("running ShellExecuteEx(open,'%s')\n", command); + if (!ShellExecuteExW (&see)) + { + if (DBG_EXTPROG) + log_debug ("ShellExecuteEx failed: rc=%d\n", (int)GetLastError ()); + xfree (wname); + return -1; + } + if (DBG_EXTPROG) + log_debug ("ShellExecuteEx succeeded (hProcess=%p,hInstApp=%d)\n", + see.hProcess, (int)see.hInstApp); + + if (!see.hProcess) + { + gnupg_usleep (waitms*1000); + if (DBG_EXTPROG) + log_debug ("ShellExecuteEx ready (wait=%dms)\n", waitms); + } + else + { + WaitForSingleObject (see.hProcess, INFINITE); + if (DBG_EXTPROG) + log_debug ("ShellExecuteEx ready\n"); + } + CloseHandle (see.hProcess); + + xfree (wname); + } + else + { + char *string; + wchar_t *wstring; + PROCESS_INFORMATION pi; + STARTUPINFOW si; + + /* We must use a copy of the command as CreateProcess modifies + * this argument. */ + string = xstrdup (command); + wstring = utf8_to_wchar (string); + xfree (string); + if (!wstring) + return -1; + + memset (&pi, 0, sizeof(pi)); + memset (&si, 0, sizeof(si)); + si.cb = sizeof (si); + + if (!CreateProcessW (NULL, wstring, NULL, NULL, FALSE, + DETACHED_PROCESS, + NULL, NULL, &si, &pi)) + { + xfree (wstring); + return -1; + } + + /* Wait for the child to exit */ + WaitForSingleObject (pi.hProcess, INFINITE); + + CloseHandle (pi.hProcess); + CloseHandle (pi.hThread); + xfree (wstring); + } + + return 0; +} +#endif /*_W32*/ + + +/* Replaces current $PATH */ +int +set_exec_path(const char *path) +{ +#ifdef HAVE_W32CE_SYSTEM +#warning Change this code to use common/exechelp.c +#else + 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 GPG_ERR_GENERAL; + else + return 0; +#endif +} + +/* 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+2); + err=GetTempPath(MAX_PATH+1,tmp); + if(err==0 || err>MAX_PATH+1) + 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=".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 (!gnupg_mkdtemp(info->tempdir)) + 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 : GPG_ERR_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; + membuf_t command; + + info->flags.use_temp_files=0; + info->flags.keep_temp_files=0; + + if(DBG_EXTPROG) + log_debug("expanding string \"%s\"\n",args_in); + + init_membuf (&command, 100); + + 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) + put_membuf_str (&command, append); + } + else + put_membuf (&command, ch, 1); + + ch++; + } + + put_membuf (&command, "", 1); /* Terminate string. */ + + info->command = get_membuf (&command, NULL); + if (!info->command) + return gpg_error_from_syserror (); + + 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 (get_membuf (&command, NULL)); + return GPG_ERR_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 = GPG_ERR_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) + { + ret = gpg_error_from_syserror (); + close(to[1]); + goto fail; + } + + close(from[1]); + + (*info)->fromchild=iobuf_fdopen(from[0],"r"); + if((*info)->fromchild==NULL) + { + ret = gpg_error_from_syserror (); + close(from[0]); + goto fail; + } + + /* fd iobufs are cached! */ + iobuf_ioctl((*info)->fromchild, IOBUF_IOCTL_NO_CACHE, 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; + gpg_err_set_errno (EPERM); + } + else + (*info)->tochild = gnupg_fopen ((*info)->tempfile_in,binary?"wb":"w"); + if((*info)->tochild==NULL) + { + ret = gpg_error_from_syserror (); + log_error(_("can't create '%s': %s\n"), + (*info)->tempfile_in,strerror(errno)); + goto fail; + } + + ret=0; + + fail: + if (ret) + { + xfree (*info); + *info = NULL; + } + return ret; +} + +int +exec_read(struct exec_info *info) +{ + int ret = GPG_ERR_GENERAL; + + fclose(info->tochild); + info->tochild=NULL; + + if(info->flags.use_temp_files) + { + if(DBG_EXTPROG) + log_debug ("running command: %s\n",info->command); + +#if defined (_WIN32) + info->progreturn=w32_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; + gpg_err_set_errno (EPERM); + } + if(info->fromchild==NULL) + { + ret = gpg_error_from_syserror (); + log_error(_("unable to read external program response: %s\n"), + strerror(errno)); + goto fail; + } + + /* Do not cache this iobuf on close */ + iobuf_ioctl(info->fromchild, IOBUF_IOCTL_NO_CACHE, 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..1cb1c72 --- /dev/null +++ b/g10/exec.h @@ -0,0 +1,51 @@ +/* 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 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#ifndef _EXEC_H_ +#define _EXEC_H_ + +#include +#include + +#include "../common/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_t 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..e98af59 --- /dev/null +++ b/g10/export.c @@ -0,0 +1,2475 @@ +/* export.c - Export keys in the OpenPGP defined format. + * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, + * 2005, 2010 Free Software Foundation, Inc. + * Copyright (C) 1998-2016 Werner Koch + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include +#include +#include +#include +#include + +#include "gpg.h" +#include "options.h" +#include "packet.h" +#include "../common/status.h" +#include "keydb.h" +#include "../common/util.h" +#include "main.h" +#include "../common/i18n.h" +#include "../common/membuf.h" +#include "../common/host2net.h" +#include "../common/zb32.h" +#include "../common/recsel.h" +#include "../common/mbox-util.h" +#include "../common/init.h" +#include "trustdb.h" +#include "call-agent.h" +#include "key-clean.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; + + +/* An object to track statistics for export operations. */ +struct export_stats_s +{ + ulong count; /* Number of processed keys. */ + ulong secret_count; /* Number of secret keys seen. */ + ulong exported; /* Number of actual exported keys. */ +}; + + +/* A global variable to store the selector created from + * --export-filter keep-uid=EXPR. + * --export-filter drop-subkey=EXPR. + * + * FIXME: We should put this into the CTRL object but that requires a + * lot more changes right now. + */ +static recsel_expr_t export_keep_uid; +static recsel_expr_t export_drop_subkey; + + +/* An object used for a linked list to implement the + * push_export_filter/pop_export_filters functions. */ +struct export_filter_attic_s +{ + struct export_filter_attic_s *next; + recsel_expr_t export_keep_uid; + recsel_expr_t export_drop_subkey; +}; +static struct export_filter_attic_s *export_filter_attic; + + + +/* Local prototypes. */ +static int do_export (ctrl_t ctrl, strlist_t users, int secret, + unsigned int options, export_stats_t stats); +static int do_export_stream (ctrl_t ctrl, iobuf_t out, + strlist_t users, int secret, + kbnode_t *keyblock_out, unsigned int options, + export_stats_t stats, int *any); +static gpg_error_t print_pka_or_dane_records +/**/ (iobuf_t out, kbnode_t keyblock, PKT_public_key *pk, + const void *data, size_t datalen, + int print_pka, int print_dane); + + +static void +cleanup_export_globals (void) +{ + recsel_release (export_keep_uid); + export_keep_uid = NULL; + recsel_release (export_drop_subkey); + export_drop_subkey = NULL; +} + + +/* Option parser for export options. See parse_options fro + details. */ +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-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")}, + + {"export-pka", EXPORT_PKA_FORMAT, NULL, NULL }, + {"export-dane", EXPORT_DANE_FORMAT, NULL, NULL }, + + {"backup", EXPORT_BACKUP, NULL, + N_("use the GnuPG key backup format")}, + {"export-backup", EXPORT_BACKUP, NULL, NULL }, + + /* 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? */ + }; + int rc; + + rc = parse_options (str, options, export_opts, noisy); + if (rc && (*options & EXPORT_BACKUP)) + { + /* Alter other options we want or don't want for restore. */ + *options |= (EXPORT_LOCAL_SIGS | EXPORT_ATTRIBUTES + | EXPORT_SENSITIVE_REVKEYS); + *options &= ~(EXPORT_CLEAN | EXPORT_MINIMAL + | EXPORT_PKA_FORMAT | EXPORT_DANE_FORMAT); + } + return rc; +} + + +/* Parse and set an export filter from string. STRING has the format + * "NAME=EXPR" with NAME being the name of the filter. Spaces before + * and after NAME are not allowed. If this function is called several + * times all expressions for the same NAME are concatenated. + * Supported filter names are: + * + * - keep-uid :: If the expression evaluates to true for a certain + * user ID packet, that packet and all it dependencies + * will be exported. The expression may use these + * variables: + * + * - uid :: The entire user ID. + * - mbox :: The mail box part of the user ID. + * - primary :: Evaluate to true for the primary user ID. + * + * - drop-subkey :: If the expression evaluates to true for a subkey + * packet that subkey and all it dependencies will be + * remove from the keyblock. The expression may use these + * variables: + * + * - secret :: 1 for a secret subkey, else 0. + * - key_algo :: Public key algorithm id + */ +gpg_error_t +parse_and_set_export_filter (const char *string) +{ + gpg_error_t err; + + /* Auto register the cleanup function. */ + register_mem_cleanup_func (cleanup_export_globals); + + if (!strncmp (string, "keep-uid=", 9)) + err = recsel_parse_expr (&export_keep_uid, string+9); + else if (!strncmp (string, "drop-subkey=", 12)) + err = recsel_parse_expr (&export_drop_subkey, string+12); + else + err = gpg_error (GPG_ERR_INV_NAME); + + return err; +} + + +/* Push the current export filters onto a stack so that new export + * filters can be defined which will be active until the next + * pop_export_filters or another push_export_filters. */ +void +push_export_filters (void) +{ + struct export_filter_attic_s *item; + + item = xcalloc (1, sizeof *item); + item->export_keep_uid = export_keep_uid; + export_keep_uid = NULL; + item->export_drop_subkey = export_drop_subkey; + export_drop_subkey = NULL; + item->next = export_filter_attic; + export_filter_attic = item; +} + + +/* Revert the last push_export_filters. */ +void +pop_export_filters (void) +{ + struct export_filter_attic_s *item; + + item = export_filter_attic; + if (!item) + BUG (); /* No corresponding push. */ + export_filter_attic = item->next; + cleanup_export_globals (); + export_keep_uid = item->export_keep_uid; + export_drop_subkey = item->export_drop_subkey; +} + + + +/* Create a new export stats object initialized to zero. On error + returns NULL and sets ERRNO. */ +export_stats_t +export_new_stats (void) +{ + export_stats_t stats; + + return xtrycalloc (1, sizeof *stats); +} + + +/* Release an export stats object. */ +void +export_release_stats (export_stats_t stats) +{ + xfree (stats); +} + + +/* Print export statistics using the status interface. */ +void +export_print_stats (export_stats_t stats) +{ + if (!stats) + return; + + if (is_status_enabled ()) + { + char buf[15*20]; + + snprintf (buf, sizeof buf, "%lu %lu %lu", + stats->count, + stats->secret_count, + stats->exported ); + write_status_text (STATUS_EXPORT_RES, buf); + } +} + + +/* + * Export public keys (to stdout or to --output FILE). + * + * Depending on opt.armor the output is armored. OPTIONS are defined + * in main.h. If USERS is NULL, all keys will be exported. STATS is + * either an export stats object for update or NULL. + * + * This function is the core of "gpg --export". + */ +int +export_pubkeys (ctrl_t ctrl, strlist_t users, unsigned int options, + export_stats_t stats) +{ + return do_export (ctrl, users, 0, options, stats); +} + + +/* + * Export secret keys (to stdout or to --output FILE). + * + * Depending on opt.armor the output is armored. OPTIONS are defined + * in main.h. If USERS is NULL, all secret keys will be exported. + * STATS is either an export stats object for update or NULL. + * + * This function is the core of "gpg --export-secret-keys". + */ +int +export_seckeys (ctrl_t ctrl, strlist_t users, unsigned int options, + export_stats_t stats) +{ + return do_export (ctrl, users, 1, options, stats); +} + + +/* + * Export secret sub keys (to stdout or to --output FILE). + * + * This is the same as export_seckeys but replaces the primary key by + * a stub key. Depending on opt.armor the output is armored. OPTIONS + * are defined in main.h. If USERS is NULL, all secret subkeys will + * be exported. STATS is either an export stats object for update or + * NULL. + * + * This function is the core of "gpg --export-secret-subkeys". + */ +int +export_secsubkeys (ctrl_t ctrl, strlist_t users, unsigned int options, + export_stats_t stats) +{ + return do_export (ctrl, users, 2, options, stats); +} + + +/* + * Export a single key into a memory buffer. STATS is either an + * export stats object for update or NULL. If PREFIX is not NULL + * PREFIXLEN bytes from PREFIX are prepended to the R_DATA. + */ +gpg_error_t +export_pubkey_buffer (ctrl_t ctrl, const char *keyspec, unsigned int options, + const void *prefix, size_t prefixlen, + export_stats_t stats, + kbnode_t *r_keyblock, void **r_data, size_t *r_datalen) +{ + gpg_error_t err; + iobuf_t iobuf; + int any; + strlist_t helplist; + + *r_keyblock = NULL; + *r_data = NULL; + *r_datalen = 0; + + helplist = NULL; + if (!add_to_strlist_try (&helplist, keyspec)) + return gpg_error_from_syserror (); + + iobuf = iobuf_temp (); + if (prefix && prefixlen) + iobuf_write (iobuf, prefix, prefixlen); + err = do_export_stream (ctrl, iobuf, helplist, 0, r_keyblock, options, + stats, &any); + if (!err && !any) + err = gpg_error (GPG_ERR_NOT_FOUND); + if (!err) + { + const void *src; + size_t datalen; + + iobuf_flush_temp (iobuf); + src = iobuf_get_temp_buffer (iobuf); + datalen = iobuf_get_temp_length (iobuf); + if (!datalen) + err = gpg_error (GPG_ERR_NO_PUBKEY); + else if (!(*r_data = xtrymalloc (datalen))) + err = gpg_error_from_syserror (); + else + { + memcpy (*r_data, src, datalen); + *r_datalen = datalen; + } + } + iobuf_close (iobuf); + free_strlist (helplist); + if (err && *r_keyblock) + { + release_kbnode (*r_keyblock); + *r_keyblock = NULL; + } + return err; +} + + +/* Export the keys identified by the list of strings in USERS. If + Secret is false public keys will be exported. With secret true + secret keys will be exported; in this case 1 means the entire + secret keyblock and 2 only the subkeys. OPTIONS are the export + options to apply. */ +static int +do_export (ctrl_t ctrl, strlist_t users, int secret, unsigned int options, + export_stats_t stats) +{ + IOBUF out = NULL; + int any, rc; + armor_filter_context_t *afx = NULL; + compress_filter_context_t zfx; + + memset( &zfx, 0, sizeof zfx); + + rc = open_outfile (-1, NULL, 0, !!secret, &out ); + if (rc) + return rc; + + if ( opt.armor && !(options & (EXPORT_PKA_FORMAT|EXPORT_DANE_FORMAT)) ) + { + afx = new_armor_context (); + afx->what = secret? 5 : 1; + push_armor_filter (afx, out); + } + + rc = do_export_stream (ctrl, out, users, secret, NULL, options, stats, &any); + + if ( rc || !any ) + iobuf_cancel (out); + else + iobuf_close (out); + release_armor_context (afx); + 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]; + + keyid_from_pk (node->pkt->pkt.public_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 + || node->pkt->pkttype == PKT_SECRET_SUBKEY) + keyid_from_pk (node->pkt->pkt.public_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. */ +int +exact_subkey_match_p (KEYDB_SEARCH_DESC *desc, kbnode_t 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: + keyid_from_pk (node->pkt->pkt.public_key, kid); + break; + + case KEYDB_SEARCH_MODE_FPR16: + case KEYDB_SEARCH_MODE_FPR20: + case KEYDB_SEARCH_MODE_FPR: + fingerprint_from_pk (node->pkt->pkt.public_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; +} + + +/* Return an error if the key represented by the S-expression S_KEY + * and the OpenPGP key represented by PK do not use the same curve. */ +static gpg_error_t +match_curve_skey_pk (gcry_sexp_t s_key, PKT_public_key *pk) +{ + gcry_sexp_t curve = NULL; + gcry_sexp_t flags = NULL; + char *curve_str = NULL; + char *flag; + const char *oidstr = NULL; + gcry_mpi_t curve_as_mpi = NULL; + gpg_error_t err; + int is_eddsa = 0; + int idx = 0; + + if (!(pk->pubkey_algo==PUBKEY_ALGO_ECDH + || pk->pubkey_algo==PUBKEY_ALGO_ECDSA + || pk->pubkey_algo==PUBKEY_ALGO_EDDSA)) + return gpg_error (GPG_ERR_PUBKEY_ALGO); + + curve = gcry_sexp_find_token (s_key, "curve", 0); + if (!curve) + { + log_error ("no reported curve\n"); + return gpg_error (GPG_ERR_UNKNOWN_CURVE); + } + curve_str = gcry_sexp_nth_string (curve, 1); + gcry_sexp_release (curve); curve = NULL; + if (!curve_str) + { + log_error ("no curve name\n"); + return gpg_error (GPG_ERR_UNKNOWN_CURVE); + } + oidstr = openpgp_curve_to_oid (curve_str, NULL, NULL); + if (!oidstr) + { + log_error ("no OID known for curve '%s'\n", curve_str); + xfree (curve_str); + return gpg_error (GPG_ERR_UNKNOWN_CURVE); + } + xfree (curve_str); + err = openpgp_oid_from_str (oidstr, &curve_as_mpi); + if (err) + return err; + if (gcry_mpi_cmp (pk->pkey[0], curve_as_mpi)) + { + log_error ("curves do not match\n"); + gcry_mpi_release (curve_as_mpi); + return gpg_error (GPG_ERR_INV_CURVE); + } + gcry_mpi_release (curve_as_mpi); + flags = gcry_sexp_find_token (s_key, "flags", 0); + if (flags) + { + for (idx = 1; idx < gcry_sexp_length (flags); idx++) + { + flag = gcry_sexp_nth_string (flags, idx); + if (flag && (strcmp ("eddsa", flag) == 0)) + is_eddsa = 1; + gcry_free (flag); + } + } + if (is_eddsa != (pk->pubkey_algo == PUBKEY_ALGO_EDDSA)) + { + log_error ("disagreement about EdDSA\n"); + err = gpg_error (GPG_ERR_INV_CURVE); + } + + return err; +} + + +/* Return a canonicalized public key algoithms. This is used to + compare different flavors of algorithms (e.g. ELG and ELG_E are + considered the same). */ +static enum gcry_pk_algos +canon_pk_algo (enum gcry_pk_algos algo) +{ + switch (algo) + { + case GCRY_PK_RSA: + case GCRY_PK_RSA_E: + case GCRY_PK_RSA_S: return GCRY_PK_RSA; + case GCRY_PK_ELG: + case GCRY_PK_ELG_E: return GCRY_PK_ELG; + case GCRY_PK_ECC: + case GCRY_PK_ECDSA: + case GCRY_PK_ECDH: return GCRY_PK_ECC; + default: return algo; + } +} + + +/* Take a cleartext dump of a secret key in PK and change the + * parameter array in PK to include the secret parameters. */ +static gpg_error_t +cleartext_secret_key_to_openpgp (gcry_sexp_t s_key, PKT_public_key *pk) +{ + gpg_error_t err; + gcry_sexp_t top_list; + gcry_sexp_t key = NULL; + char *key_type = NULL; + enum gcry_pk_algos pk_algo; + struct seckey_info *ski; + int idx, sec_start; + gcry_mpi_t pub_params[10] = { NULL }; + + /* we look for a private-key, then the first element in it tells us + the type */ + top_list = gcry_sexp_find_token (s_key, "private-key", 0); + if (!top_list) + goto bad_seckey; + + /* ignore all S-expression after the first sublist -- we assume that + they are comments or otherwise irrelevant to OpenPGP */ + if (gcry_sexp_length(top_list) < 2) + goto bad_seckey; + key = gcry_sexp_nth (top_list, 1); + if (!key) + goto bad_seckey; + key_type = gcry_sexp_nth_string(key, 0); + pk_algo = gcry_pk_map_name (key_type); + + log_assert (!pk->seckey_info); + + pk->seckey_info = ski = xtrycalloc (1, sizeof *ski); + if (!ski) + { + err = gpg_error_from_syserror (); + goto leave; + } + + switch (canon_pk_algo (pk_algo)) + { + case GCRY_PK_RSA: + if (!is_RSA (pk->pubkey_algo)) + goto bad_pubkey_algo; + err = gcry_sexp_extract_param (key, NULL, "ne", + &pub_params[0], + &pub_params[1], + NULL); + for (idx=0; idx < 2 && !err; idx++) + if (gcry_mpi_cmp(pk->pkey[idx], pub_params[idx])) + err = gpg_error (GPG_ERR_BAD_PUBKEY); + if (!err) + { + for (idx = 2; idx < 6 && !err; idx++) + { + gcry_mpi_release (pk->pkey[idx]); + pk->pkey[idx] = NULL; + } + err = gcry_sexp_extract_param (key, NULL, "dpqu", + &pk->pkey[2], + &pk->pkey[3], + &pk->pkey[4], + &pk->pkey[5], + NULL); + } + if (!err) + { + for (idx = 2; idx < 6; idx++) + ski->csum += checksum_mpi (pk->pkey[idx]); + } + break; + + case GCRY_PK_DSA: + if (!is_DSA (pk->pubkey_algo)) + goto bad_pubkey_algo; + err = gcry_sexp_extract_param (key, NULL, "pqgy", + &pub_params[0], + &pub_params[1], + &pub_params[2], + &pub_params[3], + NULL); + for (idx=0; idx < 4 && !err; idx++) + if (gcry_mpi_cmp(pk->pkey[idx], pub_params[idx])) + err = gpg_error (GPG_ERR_BAD_PUBKEY); + if (!err) + { + gcry_mpi_release (pk->pkey[4]); + pk->pkey[4] = NULL; + err = gcry_sexp_extract_param (key, NULL, "x", + &pk->pkey[4], + NULL); + } + if (!err) + ski->csum += checksum_mpi (pk->pkey[4]); + break; + + case GCRY_PK_ELG: + if (!is_ELGAMAL (pk->pubkey_algo)) + goto bad_pubkey_algo; + err = gcry_sexp_extract_param (key, NULL, "pgy", + &pub_params[0], + &pub_params[1], + &pub_params[2], + NULL); + for (idx=0; idx < 3 && !err; idx++) + if (gcry_mpi_cmp(pk->pkey[idx], pub_params[idx])) + err = gpg_error (GPG_ERR_BAD_PUBKEY); + if (!err) + { + gcry_mpi_release (pk->pkey[3]); + pk->pkey[3] = NULL; + err = gcry_sexp_extract_param (key, NULL, "x", + &pk->pkey[3], + NULL); + } + if (!err) + ski->csum += checksum_mpi (pk->pkey[3]); + break; + + case GCRY_PK_ECC: + err = match_curve_skey_pk (key, pk); + if (err) + goto leave; + if (!err) + err = gcry_sexp_extract_param (key, NULL, "q", + &pub_params[0], + NULL); + if (!err && (gcry_mpi_cmp(pk->pkey[1], pub_params[0]))) + err = gpg_error (GPG_ERR_BAD_PUBKEY); + + sec_start = 2; + if (pk->pubkey_algo == PUBKEY_ALGO_ECDH) + sec_start += 1; + if (!err) + { + gcry_mpi_release (pk->pkey[sec_start]); + pk->pkey[sec_start] = NULL; + err = gcry_sexp_extract_param (key, NULL, "d", + &pk->pkey[sec_start], + NULL); + } + + if (!err) + ski->csum += checksum_mpi (pk->pkey[sec_start]); + break; + + default: + pk->seckey_info = NULL; + xfree (ski); + err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); + break; + } + + leave: + gcry_sexp_release (top_list); + gcry_sexp_release (key); + gcry_free (key_type); + + for (idx=0; idx < DIM(pub_params); idx++) + gcry_mpi_release (pub_params[idx]); + return err; + + bad_pubkey_algo: + err = gpg_error (GPG_ERR_PUBKEY_ALGO); + goto leave; + + bad_seckey: + err = gpg_error (GPG_ERR_BAD_SECKEY); + goto leave; +} + + +/* Use the key transfer format given in S_PGP to create the secinfo + structure in PK and change the parameter array in PK to include the + secret parameters. */ +static gpg_error_t +transfer_format_to_openpgp (gcry_sexp_t s_pgp, PKT_public_key *pk) +{ + gpg_error_t err; + gcry_sexp_t top_list; + gcry_sexp_t list = NULL; + char *curve = NULL; + const char *value; + size_t valuelen; + char *string; + int idx; + int is_v4, is_protected; + enum gcry_pk_algos pk_algo; + int protect_algo = 0; + char iv[16]; + int ivlen = 0; + int s2k_mode = 0; + int s2k_algo = 0; + byte s2k_salt[8]; + u32 s2k_count = 0; + int is_ecdh = 0; + size_t npkey, nskey; + gcry_mpi_t skey[10]; /* We support up to 9 parameters. */ + int skeyidx = 0; + struct seckey_info *ski; + + /* gcry_log_debugsxp ("transferkey", s_pgp); */ + top_list = gcry_sexp_find_token (s_pgp, "openpgp-private-key", 0); + if (!top_list) + goto bad_seckey; + + list = gcry_sexp_find_token (top_list, "version", 0); + if (!list) + goto bad_seckey; + value = gcry_sexp_nth_data (list, 1, &valuelen); + if (!value || valuelen != 1 || !(value[0] == '3' || value[0] == '4')) + goto bad_seckey; + is_v4 = (value[0] == '4'); + + gcry_sexp_release (list); + list = gcry_sexp_find_token (top_list, "protection", 0); + if (!list) + goto bad_seckey; + value = gcry_sexp_nth_data (list, 1, &valuelen); + if (!value) + goto bad_seckey; + if (valuelen == 4 && !memcmp (value, "sha1", 4)) + is_protected = 2; + else if (valuelen == 3 && !memcmp (value, "sum", 3)) + is_protected = 1; + else if (valuelen == 4 && !memcmp (value, "none", 4)) + is_protected = 0; + else + goto bad_seckey; + if (is_protected) + { + string = gcry_sexp_nth_string (list, 2); + if (!string) + goto bad_seckey; + protect_algo = gcry_cipher_map_name (string); + xfree (string); + + value = gcry_sexp_nth_data (list, 3, &valuelen); + if (!value || !valuelen || valuelen > sizeof iv) + goto bad_seckey; + memcpy (iv, value, valuelen); + ivlen = valuelen; + + string = gcry_sexp_nth_string (list, 4); + if (!string) + goto bad_seckey; + s2k_mode = strtol (string, NULL, 10); + xfree (string); + + string = gcry_sexp_nth_string (list, 5); + if (!string) + goto bad_seckey; + s2k_algo = gcry_md_map_name (string); + xfree (string); + + value = gcry_sexp_nth_data (list, 6, &valuelen); + if (!value || !valuelen || valuelen > sizeof s2k_salt) + goto bad_seckey; + memcpy (s2k_salt, value, valuelen); + + string = gcry_sexp_nth_string (list, 7); + if (!string) + goto bad_seckey; + s2k_count = strtoul (string, NULL, 10); + xfree (string); + } + + /* Parse the gcrypt PK algo and check that it is okay. */ + gcry_sexp_release (list); + list = gcry_sexp_find_token (top_list, "algo", 0); + if (!list) + goto bad_seckey; + string = gcry_sexp_nth_string (list, 1); + if (!string) + goto bad_seckey; + pk_algo = gcry_pk_map_name (string); + xfree (string); string = NULL; + if (gcry_pk_algo_info (pk_algo, GCRYCTL_GET_ALGO_NPKEY, NULL, &npkey) + || gcry_pk_algo_info (pk_algo, GCRYCTL_GET_ALGO_NSKEY, NULL, &nskey) + || !npkey || npkey >= nskey) + goto bad_seckey; + + /* Check that the pubkey algo matches the one from the public key. */ + switch (canon_pk_algo (pk_algo)) + { + case GCRY_PK_RSA: + if (!is_RSA (pk->pubkey_algo)) + pk_algo = 0; /* Does not match. */ + break; + case GCRY_PK_DSA: + if (!is_DSA (pk->pubkey_algo)) + pk_algo = 0; /* Does not match. */ + break; + case GCRY_PK_ELG: + if (!is_ELGAMAL (pk->pubkey_algo)) + pk_algo = 0; /* Does not match. */ + break; + case GCRY_PK_ECC: + if (pk->pubkey_algo == PUBKEY_ALGO_ECDSA) + ; + else if (pk->pubkey_algo == PUBKEY_ALGO_ECDH) + is_ecdh = 1; + else if (pk->pubkey_algo == PUBKEY_ALGO_EDDSA) + ; + else + pk_algo = 0; /* Does not match. */ + /* For ECC we do not have the domain parameters thus fix our info. */ + npkey = 1; + nskey = 2; + break; + default: + pk_algo = 0; /* Oops. */ + break; + } + if (!pk_algo) + { + err = gpg_error (GPG_ERR_PUBKEY_ALGO); + goto leave; + } + + /* This check has to go after the ecc adjustments. */ + if (nskey > PUBKEY_MAX_NSKEY) + goto bad_seckey; + + /* Parse the key parameters. */ + gcry_sexp_release (list); + list = gcry_sexp_find_token (top_list, "skey", 0); + if (!list) + goto bad_seckey; + for (idx=0;;) + { + int is_enc; + + value = gcry_sexp_nth_data (list, ++idx, &valuelen); + if (!value && skeyidx >= npkey) + break; /* Ready. */ + + /* Check for too many parameters. Note that depending on the + protection mode and version number we may see less than NSKEY + (but at least NPKEY+1) parameters. */ + if (idx >= 2*nskey) + goto bad_seckey; + if (skeyidx >= DIM (skey)-1) + goto bad_seckey; + + if (!value || valuelen != 1 || !(value[0] == '_' || value[0] == 'e')) + goto bad_seckey; + is_enc = (value[0] == 'e'); + value = gcry_sexp_nth_data (list, ++idx, &valuelen); + if (!value || !valuelen) + goto bad_seckey; + if (is_enc) + { + void *p = xtrymalloc (valuelen); + if (!p) + goto outofmem; + memcpy (p, value, valuelen); + skey[skeyidx] = gcry_mpi_set_opaque (NULL, p, valuelen*8); + if (!skey[skeyidx]) + goto outofmem; + } + else + { + if (gcry_mpi_scan (skey + skeyidx, GCRYMPI_FMT_STD, + value, valuelen, NULL)) + goto bad_seckey; + } + skeyidx++; + } + skey[skeyidx++] = NULL; + + gcry_sexp_release (list); list = NULL; + + /* We have no need for the CSUM value thus we don't parse it. */ + /* list = gcry_sexp_find_token (top_list, "csum", 0); */ + /* if (list) */ + /* { */ + /* string = gcry_sexp_nth_string (list, 1); */ + /* if (!string) */ + /* goto bad_seckey; */ + /* desired_csum = strtoul (string, NULL, 10); */ + /* xfree (string); */ + /* } */ + /* else */ + /* desired_csum = 0; */ + /* gcry_sexp_release (list); list = NULL; */ + + /* Get the curve name if any, */ + list = gcry_sexp_find_token (top_list, "curve", 0); + if (list) + { + curve = gcry_sexp_nth_string (list, 1); + gcry_sexp_release (list); list = NULL; + } + + gcry_sexp_release (top_list); top_list = NULL; + + /* log_debug ("XXX is_v4=%d\n", is_v4); */ + /* log_debug ("XXX pubkey_algo=%d\n", pubkey_algo); */ + /* log_debug ("XXX is_protected=%d\n", is_protected); */ + /* log_debug ("XXX protect_algo=%d\n", protect_algo); */ + /* log_printhex (iv, ivlen, "XXX iv"); */ + /* log_debug ("XXX ivlen=%d\n", ivlen); */ + /* log_debug ("XXX s2k_mode=%d\n", s2k_mode); */ + /* log_debug ("XXX s2k_algo=%d\n", s2k_algo); */ + /* log_printhex (s2k_salt, sizeof s2k_salt, "XXX s2k_salt"); */ + /* log_debug ("XXX s2k_count=%lu\n", (unsigned long)s2k_count); */ + /* for (idx=0; skey[idx]; idx++) */ + /* { */ + /* int is_enc = gcry_mpi_get_flag (skey[idx], GCRYMPI_FLAG_OPAQUE); */ + /* log_info ("XXX skey[%d]%s:", idx, is_enc? " (enc)":""); */ + /* if (is_enc) */ + /* { */ + /* void *p; */ + /* unsigned int nbits; */ + /* p = gcry_mpi_get_opaque (skey[idx], &nbits); */ + /* log_printhex ( p, (nbits+7)/8, NULL); */ + /* } */ + /* else */ + /* gcry_mpi_dump (skey[idx]); */ + /* log_printf ("\n"); */ + /* } */ + + if (!is_v4 || is_protected != 2 ) + { + /* We only support the v4 format and a SHA-1 checksum. */ + err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); + goto leave; + } + + /* We need to change the received parameters for ECC algorithms. + The transfer format has the curve name and the parameters + separate. We put them all into the SKEY array. */ + if (canon_pk_algo (pk_algo) == GCRY_PK_ECC) + { + const char *oidstr; + + /* Assert that all required parameters are available. We also + check that the array does not contain more parameters than + needed (this was used by some beta versions of 2.1. */ + if (!curve || !skey[0] || !skey[1] || skey[2]) + { + err = gpg_error (GPG_ERR_INTERNAL); + goto leave; + } + + oidstr = openpgp_curve_to_oid (curve, NULL, NULL); + if (!oidstr) + { + log_error ("no OID known for curve '%s'\n", curve); + err = gpg_error (GPG_ERR_UNKNOWN_CURVE); + goto leave; + } + /* Put the curve's OID into the MPI array. This requires + that we shift Q and D. For ECDH also insert the KDF parms. */ + if (is_ecdh) + { + skey[4] = NULL; + skey[3] = skey[1]; + skey[2] = gcry_mpi_copy (pk->pkey[2]); + } + else + { + skey[3] = NULL; + skey[2] = skey[1]; + } + skey[1] = skey[0]; + skey[0] = NULL; + err = openpgp_oid_from_str (oidstr, skey + 0); + if (err) + goto leave; + /* Fixup the NPKEY and NSKEY to match OpenPGP reality. */ + npkey = 2 + is_ecdh; + nskey = 3 + is_ecdh; + + /* for (idx=0; skey[idx]; idx++) */ + /* { */ + /* log_info ("YYY skey[%d]:", idx); */ + /* if (gcry_mpi_get_flag (skey[idx], GCRYMPI_FLAG_OPAQUE)) */ + /* { */ + /* void *p; */ + /* unsigned int nbits; */ + /* p = gcry_mpi_get_opaque (skey[idx], &nbits); */ + /* log_printhex (p, (nbits+7)/8, NULL); */ + /* } */ + /* else */ + /* gcry_mpi_dump (skey[idx]); */ + /* log_printf ("\n"); */ + /* } */ + } + + /* Do some sanity checks. */ + if (s2k_count > 255) + { + /* We expect an already encoded S2K count. */ + err = gpg_error (GPG_ERR_INV_DATA); + goto leave; + } + err = openpgp_cipher_test_algo (protect_algo); + if (err) + goto leave; + err = openpgp_md_test_algo (s2k_algo); + if (err) + goto leave; + + /* Check that the public key parameters match. Note that since + Libgcrypt 1.5 gcry_mpi_cmp handles opaque MPI correctly. */ + for (idx=0; idx < npkey; idx++) + if (gcry_mpi_cmp (pk->pkey[idx], skey[idx])) + { + err = gpg_error (GPG_ERR_BAD_PUBKEY); + goto leave; + } + + /* Check that the first secret key parameter in SKEY is encrypted + and that there are no more secret key parameters. The latter is + guaranteed by the v4 packet format. */ + if (!gcry_mpi_get_flag (skey[npkey], GCRYMPI_FLAG_OPAQUE)) + goto bad_seckey; + if (npkey+1 < DIM (skey) && skey[npkey+1]) + goto bad_seckey; + + /* Check that the secret key parameters in PK are all set to NULL. */ + for (idx=npkey; idx < nskey; idx++) + if (pk->pkey[idx]) + goto bad_seckey; + + /* Now build the protection info. */ + pk->seckey_info = ski = xtrycalloc (1, sizeof *ski); + if (!ski) + { + err = gpg_error_from_syserror (); + goto leave; + } + + ski->is_protected = 1; + ski->sha1chk = 1; + ski->algo = protect_algo; + ski->s2k.mode = s2k_mode; + ski->s2k.hash_algo = s2k_algo; + log_assert (sizeof ski->s2k.salt == sizeof s2k_salt); + memcpy (ski->s2k.salt, s2k_salt, sizeof s2k_salt); + ski->s2k.count = s2k_count; + log_assert (ivlen <= sizeof ski->iv); + memcpy (ski->iv, iv, ivlen); + ski->ivlen = ivlen; + + /* Store the protected secret key parameter. */ + pk->pkey[npkey] = skey[npkey]; + skey[npkey] = NULL; + + /* That's it. */ + + leave: + gcry_free (curve); + gcry_sexp_release (list); + gcry_sexp_release (top_list); + for (idx=0; idx < skeyidx; idx++) + gcry_mpi_release (skey[idx]); + return err; + + bad_seckey: + err = gpg_error (GPG_ERR_BAD_SECKEY); + goto leave; + + outofmem: + err = gpg_error (GPG_ERR_ENOMEM); + goto leave; +} + + +/* Print an "EXPORTED" status line. PK is the primary public key. */ +static void +print_status_exported (PKT_public_key *pk) +{ + char *hexfpr; + + if (!is_status_enabled ()) + return; + + hexfpr = hexfingerprint (pk, NULL, 0); + write_status_text (STATUS_EXPORTED, hexfpr? hexfpr : "[?]"); + xfree (hexfpr); +} + + +/* + * Receive a secret key from agent specified by HEXGRIP. + * + * Since the key data from the agent is encrypted, decrypt it using + * CIPHERHD context. Then, parse the decrypted key data into transfer + * format, and put secret parameters into PK. + * + * If CLEARTEXT is 0, store the secret key material + * passphrase-protected. Otherwise, store secret key material in the + * clear. + * + * CACHE_NONCE_ADDR is used to share nonce for multple key retrievals. + */ +gpg_error_t +receive_seckey_from_agent (ctrl_t ctrl, gcry_cipher_hd_t cipherhd, + int cleartext, + char **cache_nonce_addr, const char *hexgrip, + PKT_public_key *pk) +{ + gpg_error_t err = 0; + unsigned char *wrappedkey = NULL; + size_t wrappedkeylen; + unsigned char *key = NULL; + size_t keylen, realkeylen; + gcry_sexp_t s_skey; + char *prompt; + + if (opt.verbose) + log_info ("key %s: asking agent for the secret parts\n", hexgrip); + + prompt = gpg_format_keydesc (ctrl, pk, FORMAT_KEYDESC_EXPORT,1); + err = agent_export_key (ctrl, hexgrip, prompt, !cleartext, cache_nonce_addr, + &wrappedkey, &wrappedkeylen, + pk->keyid, pk->main_keyid, pk->pubkey_algo); + xfree (prompt); + + if (err) + goto unwraperror; + if (wrappedkeylen < 24) + { + err = gpg_error (GPG_ERR_INV_LENGTH); + goto unwraperror; + } + keylen = wrappedkeylen - 8; + key = xtrymalloc_secure (keylen); + if (!key) + { + err = gpg_error_from_syserror (); + goto unwraperror; + } + err = gcry_cipher_decrypt (cipherhd, key, keylen, wrappedkey, wrappedkeylen); + if (err) + goto unwraperror; + realkeylen = gcry_sexp_canon_len (key, keylen, NULL, &err); + if (!realkeylen) + goto unwraperror; /* Invalid csexp. */ + + err = gcry_sexp_sscan (&s_skey, NULL, key, realkeylen); + if (!err) + { + if (cleartext) + err = cleartext_secret_key_to_openpgp (s_skey, pk); + else + err = transfer_format_to_openpgp (s_skey, pk); + gcry_sexp_release (s_skey); + } + + unwraperror: + xfree (key); + xfree (wrappedkey); + if (err) + { + log_error ("key %s: error receiving key from agent:" + " %s%s\n", hexgrip, gpg_strerror (err), + gpg_err_code (err) == GPG_ERR_FULLY_CANCELED? + "":_(" - skipped")); + } + return err; +} + + +/* Write KEYBLOCK either to stdout or to the file set with the + * --output option. This is a simplified version of do_export_stream + * which supports only a few export options. */ +gpg_error_t +write_keyblock_to_output (kbnode_t keyblock, int with_armor, + unsigned int options) +{ + gpg_error_t err; + const char *fname; + iobuf_t out; + kbnode_t node; + armor_filter_context_t *afx = NULL; + iobuf_t out_help = NULL; + PKT_public_key *pk = NULL; + + fname = opt.outfile? opt.outfile : "-"; + if (is_secured_filename (fname) ) + return gpg_error (GPG_ERR_EPERM); + + out = iobuf_create (fname, 0); + if (!out) + { + err = gpg_error_from_syserror (); + log_error(_("can't create '%s': %s\n"), fname, gpg_strerror (err)); + return err; + } + if (opt.verbose) + log_info (_("writing to '%s'\n"), iobuf_get_fname_nonnull (out)); + + if ((options & (EXPORT_PKA_FORMAT|EXPORT_DANE_FORMAT))) + { + with_armor = 0; + out_help = iobuf_temp (); + } + + if (with_armor) + { + afx = new_armor_context (); + afx->what = 1; + push_armor_filter (afx, out); + } + + for (node = keyblock; node; node = node->next) + { + if (is_deleted_kbnode (node)) + continue; + if (node->pkt->pkttype == PKT_RING_TRUST) + continue; /* Skip - they should not be here anyway. */ + + if (!pk && (node->pkt->pkttype == PKT_PUBLIC_KEY + || node->pkt->pkttype == PKT_SECRET_KEY)) + pk = node->pkt->pkt.public_key; + + if ((options & EXPORT_BACKUP)) + err = build_packet_and_meta (out_help? out_help : out, node->pkt); + else + err = build_packet (out_help? out_help : out, node->pkt); + if (err) + { + log_error ("build_packet(%d) failed: %s\n", + node->pkt->pkttype, gpg_strerror (err) ); + goto leave; + } + } + err = 0; + + if (out_help && pk) + { + const void *data; + size_t datalen; + + iobuf_flush_temp (out_help); + data = iobuf_get_temp_buffer (out_help); + datalen = iobuf_get_temp_length (out_help); + + err = print_pka_or_dane_records (out, + keyblock, pk, data, datalen, + (options & EXPORT_PKA_FORMAT), + (options & EXPORT_DANE_FORMAT)); + } + + leave: + if (err) + iobuf_cancel (out); + else + iobuf_close (out); + iobuf_cancel (out_help); + release_armor_context (afx); + return err; +} + + +/* + * Apply the keep-uid filter to the keyblock. The deleted nodes are + * marked and thus the caller should call commit_kbnode afterwards. + * KEYBLOCK must not have any blocks marked as deleted. + */ +static void +apply_keep_uid_filter (ctrl_t ctrl, kbnode_t keyblock, recsel_expr_t selector) +{ + kbnode_t node; + struct impex_filter_parm_s parm; + + parm.ctrl = ctrl; + + for (node = keyblock->next; node; node = node->next ) + { + if (node->pkt->pkttype == PKT_USER_ID) + { + parm.node = node; + if (!recsel_select (selector, impex_filter_getval, &parm)) + { + /* log_debug ("keep-uid: deleting '%s'\n", */ + /* node->pkt->pkt.user_id->name); */ + /* The UID packet and all following packets up to the + * next UID or a subkey. */ + delete_kbnode (node); + for (; node->next + && node->next->pkt->pkttype != PKT_USER_ID + && node->next->pkt->pkttype != PKT_PUBLIC_SUBKEY + && node->next->pkt->pkttype != PKT_SECRET_SUBKEY ; + node = node->next) + delete_kbnode (node->next); + } + /* else */ + /* log_debug ("keep-uid: keeping '%s'\n", */ + /* node->pkt->pkt.user_id->name); */ + } + } +} + + +/* + * Apply the drop-subkey filter to the keyblock. The deleted nodes are + * marked and thus the caller should call commit_kbnode afterwards. + * KEYBLOCK must not have any blocks marked as deleted. + */ +static void +apply_drop_subkey_filter (ctrl_t ctrl, kbnode_t keyblock, + recsel_expr_t selector) +{ + kbnode_t node; + struct impex_filter_parm_s parm; + + parm.ctrl = ctrl; + + for (node = keyblock->next; node; node = node->next ) + { + if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY + || node->pkt->pkttype == PKT_SECRET_SUBKEY) + { + parm.node = node; + if (recsel_select (selector, impex_filter_getval, &parm)) + { + /*log_debug ("drop-subkey: deleting a key\n");*/ + /* The subkey packet and all following packets up to the + * next subkey. */ + delete_kbnode (node); + for (; node->next + && node->next->pkt->pkttype != PKT_PUBLIC_SUBKEY + && node->next->pkt->pkttype != PKT_SECRET_SUBKEY ; + node = node->next) + delete_kbnode (node->next); + } + } + } +} + + +/* Print DANE or PKA records for all user IDs in KEYBLOCK to OUT. The + * data for the record is taken from (DATA,DATELEN). PK is the public + * key packet with the primary key. */ +static gpg_error_t +print_pka_or_dane_records (iobuf_t out, kbnode_t keyblock, PKT_public_key *pk, + const void *data, size_t datalen, + int print_pka, int print_dane) +{ + gpg_error_t err = 0; + kbnode_t kbctx, node; + PKT_user_id *uid; + char *mbox = NULL; + char hashbuf[32]; + char *hash = NULL; + char *domain; + const char *s; + unsigned int len; + estream_t fp = NULL; + char *hexdata = NULL; + char *hexfpr; + + hexfpr = hexfingerprint (pk, NULL, 0); + if (!hexfpr) + { + err = gpg_error_from_syserror (); + goto leave; + } + hexdata = bin2hex (data, datalen, NULL); + if (!hexdata) + { + err = gpg_error_from_syserror (); + goto leave; + } + ascii_strlwr (hexdata); + fp = es_fopenmem (0, "rw,samethread"); + if (!fp) + { + err = gpg_error_from_syserror (); + goto leave; + } + + for (kbctx = NULL; (node = walk_kbnode (keyblock, &kbctx, 0));) + { + if (node->pkt->pkttype != PKT_USER_ID) + continue; + uid = node->pkt->pkt.user_id; + + if (uid->flags.expired || uid->flags.revoked) + continue; + + xfree (mbox); + mbox = mailbox_from_userid (uid->name); + if (!mbox) + continue; + + domain = strchr (mbox, '@'); + *domain++ = 0; + + if (print_pka) + { + es_fprintf (fp, "$ORIGIN _pka.%s.\n; %s\n; ", domain, hexfpr); + print_utf8_buffer (fp, uid->name, uid->len); + es_putc ('\n', fp); + gcry_md_hash_buffer (GCRY_MD_SHA1, hashbuf, mbox, strlen (mbox)); + xfree (hash); + hash = zb32_encode (hashbuf, 8*20); + if (!hash) + { + err = gpg_error_from_syserror (); + goto leave; + } + len = strlen (hexfpr)/2; + es_fprintf (fp, "%s TYPE37 \\# %u 0006 0000 00 %02X %s\n\n", + hash, 6 + len, len, hexfpr); + } + + if (print_dane && hexdata) + { + es_fprintf (fp, "$ORIGIN _openpgpkey.%s.\n; %s\n; ", domain, hexfpr); + print_utf8_buffer (fp, uid->name, uid->len); + es_putc ('\n', fp); + gcry_md_hash_buffer (GCRY_MD_SHA256, hashbuf, mbox, strlen (mbox)); + xfree (hash); + hash = bin2hex (hashbuf, 28, NULL); + if (!hash) + { + err = gpg_error_from_syserror (); + goto leave; + } + ascii_strlwr (hash); + len = strlen (hexdata)/2; + es_fprintf (fp, "%s TYPE61 \\# %u (\n", hash, len); + for (s = hexdata; ;) + { + es_fprintf (fp, "\t%.64s\n", s); + if (strlen (s) < 64) + break; + s += 64; + } + es_fputs ("\t)\n\n", fp); + } + } + + /* Make sure it is a string and write it. */ + es_fputc (0, fp); + { + void *vp; + + if (es_fclose_snatch (fp, &vp, NULL)) + { + err = gpg_error_from_syserror (); + goto leave; + } + fp = NULL; + iobuf_writestr (out, vp); + es_free (vp); + } + err = 0; + + leave: + xfree (hash); + xfree (mbox); + es_fclose (fp); + xfree (hexdata); + xfree (hexfpr); + return err; +} + + +/* Helper for do_export_stream which writes one keyblock to OUT. */ +static gpg_error_t +do_export_one_keyblock (ctrl_t ctrl, kbnode_t keyblock, u32 *keyid, + iobuf_t out, int secret, unsigned int options, + export_stats_t stats, int *any, + KEYDB_SEARCH_DESC *desc, size_t ndesc, + size_t descindex, gcry_cipher_hd_t cipherhd) +{ + gpg_error_t err = gpg_error (GPG_ERR_NOT_FOUND); + char *cache_nonce = NULL; + subkey_list_t subkey_list = NULL; /* Track already processed subkeys. */ + int skip_until_subkey = 0; + int cleartext = 0; + char *hexgrip = NULL; + char *serialno = NULL; + PKT_public_key *pk; + u32 subkidbuf[2], *subkid; + kbnode_t kbctx, node; + + /* NB: walk_kbnode skips packets marked as deleted. */ + for (kbctx=NULL; (node = walk_kbnode (keyblock, &kbctx, 0)); ) + { + if (skip_until_subkey) + { + if (node->pkt->pkttype == PKT_PUBLIC_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; + + /* Skip ring trust packets - they should not ne here anyway. */ + 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) + { + 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 of GnuPG < 2.1 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 && (node->pkt->pkttype == PKT_PUBLIC_KEY + || node->pkt->pkttype == PKT_PUBLIC_SUBKEY)) + { + pk = node->pkt->pkt.public_key; + if (node->pkt->pkttype == PKT_PUBLIC_KEY) + subkid = NULL; + else + { + keyid_from_pk (pk, subkidbuf); + subkid = subkidbuf; + } + + if (pk->seckey_info) + { + log_error ("key %s: oops: seckey_info already set" + " - skipped\n", keystr_with_sub (keyid, subkid)); + skip_until_subkey = 1; + continue; + } + + xfree (hexgrip); + err = hexkeygrip_from_pk (pk, &hexgrip); + if (err) + { + log_error ("key %s: error computing keygrip: %s" + " - skipped\n", keystr_with_sub (keyid, subkid), + gpg_strerror (err)); + skip_until_subkey = 1; + err = 0; + continue; + } + + xfree (serialno); + serialno = NULL; + if (secret == 2 && node->pkt->pkttype == PKT_PUBLIC_KEY) + { + /* We are asked not to export the secret parts of the + * primary key. Make up an error code to create the + * stub. */ + err = GPG_ERR_NOT_FOUND; + } + else + err = agent_get_keyinfo (ctrl, hexgrip, &serialno, &cleartext); + + if ((!err && serialno) + && secret == 2 && node->pkt->pkttype == PKT_PUBLIC_KEY) + { + /* It does not make sense to export a key with its + * primary key on card using a non-key stub. Thus we + * skip those keys when used with --export-secret-subkeys. */ + log_info (_("key %s: key material on-card - skipped\n"), + keystr_with_sub (keyid, subkid)); + skip_until_subkey = 1; + } + else if (gpg_err_code (err) == GPG_ERR_NOT_FOUND + || (!err && serialno)) + { + /* Create a key stub. */ + struct seckey_info *ski; + const char *s; + + pk->seckey_info = ski = xtrycalloc (1, sizeof *ski); + if (!ski) + { + err = gpg_error_from_syserror (); + goto leave; + } + + ski->is_protected = 1; + if (err) + ski->s2k.mode = 1001; /* GNU dummy (no secret key). */ + else + { + ski->s2k.mode = 1002; /* GNU-divert-to-card. */ + for (s=serialno; sizeof (ski->ivlen) && *s && s[1]; + ski->ivlen++, s += 2) + ski->iv[ski->ivlen] = xtoi_2 (s); + } + + if ((options & EXPORT_BACKUP)) + err = build_packet_and_meta (out, node->pkt); + else + err = build_packet (out, node->pkt); + if (!err && node->pkt->pkttype == PKT_PUBLIC_KEY) + { + stats->exported++; + print_status_exported (node->pkt->pkt.public_key); + } + } + else if (!err) + { + err = receive_seckey_from_agent (ctrl, cipherhd, + cleartext, &cache_nonce, + hexgrip, pk); + if (err) + { + if (gpg_err_code (err) == GPG_ERR_FULLY_CANCELED) + goto leave; + skip_until_subkey = 1; + err = 0; + } + else + { + if ((options & EXPORT_BACKUP)) + err = build_packet_and_meta (out, node->pkt); + else + err = build_packet (out, node->pkt); + if (node->pkt->pkttype == PKT_PUBLIC_KEY) + { + stats->exported++; + print_status_exported (node->pkt->pkt.public_key); + } + } + } + else + { + log_error ("key %s: error getting keyinfo from agent: %s" + " - skipped\n", keystr_with_sub (keyid, subkid), + gpg_strerror (err)); + skip_until_subkey = 1; + err = 0; + } + + xfree (pk->seckey_info); + pk->seckey_info = NULL; + { + int i; + for (i = pubkey_get_npkey (pk->pubkey_algo); + i < pubkey_get_nskey (pk->pubkey_algo); i++) + { + gcry_mpi_release (pk->pkey[i]); + pk->pkey[i] = NULL; + } + } + } + else /* Not secret or common packets. */ + { + if ((options & EXPORT_BACKUP)) + err = build_packet_and_meta (out, node->pkt); + else + err = build_packet (out, node->pkt); + if (!err && node->pkt->pkttype == PKT_PUBLIC_KEY) + { + stats->exported++; + print_status_exported (node->pkt->pkt.public_key); + } + } + + if (err) + { + log_error ("build_packet(%d) failed: %s\n", + node->pkt->pkttype, gpg_strerror (err)); + goto leave; + } + + if (!skip_until_subkey) + *any = 1; + } + + leave: + release_subkey_list (subkey_list); + xfree (serialno); + xfree (hexgrip); + xfree (cache_nonce); + return err; +} + + +/* Export the keys identified by the list of strings in USERS to the + stream OUT. If SECRET is false public keys will be exported. With + secret true secret keys will be exported; in this case 1 means the + entire secret keyblock and 2 only the subkeys. OPTIONS are the + export options to apply. If KEYBLOCK_OUT is not NULL, AND the exit + code is zero, a pointer to the first keyblock found and exported + will be stored at this address; no other keyblocks are exported in + this case. The caller must free the returned keyblock. If any + key has been exported true is stored at ANY. */ +static int +do_export_stream (ctrl_t ctrl, iobuf_t out, strlist_t users, int secret, + kbnode_t *keyblock_out, unsigned int options, + export_stats_t stats, int *any) +{ + gpg_error_t err = 0; + PACKET pkt; + kbnode_t keyblock = NULL; + kbnode_t node; + size_t ndesc, descindex; + KEYDB_SEARCH_DESC *desc = NULL; + KEYDB_HANDLE kdbhd; + strlist_t sl; + gcry_cipher_hd_t cipherhd = NULL; + struct export_stats_s dummystats; + iobuf_t out_help = NULL; + + if (!stats) + stats = &dummystats; + *any = 0; + init_packet (&pkt); + kdbhd = keydb_new (); + if (!kdbhd) + return gpg_error_from_syserror (); + + /* For the PKA and DANE format open a helper iobuf and for DANE + * enforce some options. */ + if ((options & (EXPORT_PKA_FORMAT | EXPORT_DANE_FORMAT))) + { + out_help = iobuf_temp (); + if ((options & EXPORT_DANE_FORMAT)) + options |= EXPORT_MINIMAL | EXPORT_CLEAN; + } + + 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 (!(err=classify_user_id (sl->d, desc+ndesc, 1))) + ndesc++; + else + log_error (_("key \"%s\" not found: %s\n"), + sl->d, gpg_strerror (err)); + } + + keydb_disable_caching (kdbhd); /* We are looping the search. */ + + /* 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. To set this flag 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. */ + } + +#ifdef ENABLE_SELINUX_HACKS + if (secret) + { + log_error (_("exporting secret keys not allowed\n")); + err = gpg_error (GPG_ERR_NOT_SUPPORTED); + goto leave; + } +#endif + + /* For secret key export we need to setup a decryption context. */ + if (secret) + { + void *kek = NULL; + size_t keklen; + + err = agent_keywrap_key (ctrl, 1, &kek, &keklen); + if (err) + { + log_error ("error getting the KEK: %s\n", gpg_strerror (err)); + goto leave; + } + + /* Prepare a cipher context. */ + err = gcry_cipher_open (&cipherhd, GCRY_CIPHER_AES128, + GCRY_CIPHER_MODE_AESWRAP, 0); + if (!err) + err = gcry_cipher_setkey (cipherhd, kek, keklen); + if (err) + { + log_error ("error setting up an encryption context: %s\n", + gpg_strerror (err)); + goto leave; + } + xfree (kek); + kek = NULL; + } + + for (;;) + { + u32 keyid[2]; + PKT_public_key *pk; + + err = keydb_search (kdbhd, desc, ndesc, &descindex); + if (!users) + desc[0].mode = KEYDB_SEARCH_MODE_NEXT; + if (err) + break; + + /* Read the keyblock. */ + release_kbnode (keyblock); + keyblock = NULL; + err = keydb_get_keyblock (kdbhd, &keyblock); + if (err) + { + log_error (_("error reading keyblock: %s\n"), gpg_strerror (err)); + goto leave; + } + + node = find_kbnode (keyblock, PKT_PUBLIC_KEY); + if (!node) + { + log_error ("public key packet not found in keyblock - skipped\n"); + continue; + } + stats->count++; + setup_main_keyids (keyblock); /* gpg_format_keydesc needs it. */ + pk = node->pkt->pkt.public_key; + keyid_from_pk (pk, keyid); + + /* If a secret key export is required we need to check whether + we have a secret key at all and if so create the seckey_info + structure. */ + if (secret) + { + if (agent_probe_any_secret_key (ctrl, keyblock)) + continue; /* No secret key (neither primary nor subkey). */ + + /* No v3 keys with GNU mode 1001. */ + if (secret == 2 && pk->version == 3) + { + log_info (_("key %s: PGP 2.x style key - skipped\n"), + keystr (keyid)); + continue; + } + + /* The agent does not yet allow export of v3 packets. It is + actually questionable whether we should allow them at + all. */ + if (pk->version == 3) + { + log_info ("key %s: PGP 2.x style key (v3) export " + "not yet supported - skipped\n", keystr (keyid)); + continue; + } + stats->secret_count++; + } + + /* Always do the cleaning on the public key part if requested. + * A designated revocation is never stripped, even with + * export-minimal set. */ + if ((options & EXPORT_CLEAN)) + { + merge_keys_and_selfsig (ctrl, keyblock); + clean_all_uids (ctrl, keyblock, opt.verbose, + (options&EXPORT_MINIMAL), NULL, NULL); + clean_all_subkeys (ctrl, keyblock, opt.verbose, + (options&EXPORT_MINIMAL)? KEY_CLEAN_ALL + /**/ : KEY_CLEAN_AUTHENCR, + NULL, NULL); + commit_kbnode (&keyblock); + } + + if (export_keep_uid) + { + commit_kbnode (&keyblock); + apply_keep_uid_filter (ctrl, keyblock, export_keep_uid); + commit_kbnode (&keyblock); + } + + if (export_drop_subkey) + { + commit_kbnode (&keyblock); + apply_drop_subkey_filter (ctrl, keyblock, export_drop_subkey); + commit_kbnode (&keyblock); + } + + /* And write it. */ + err = do_export_one_keyblock (ctrl, keyblock, keyid, + out_help? out_help : out, + secret, options, stats, any, + desc, ndesc, descindex, cipherhd); + if (err) + break; + + if (keyblock_out) + { + *keyblock_out = keyblock; + break; + } + + if (out_help) + { + /* We want to write PKA or DANE records. OUT_HELP has the + * keyblock and we print a record for each uid to OUT. */ + const void *data; + size_t datalen; + + iobuf_flush_temp (out_help); + data = iobuf_get_temp_buffer (out_help); + datalen = iobuf_get_temp_length (out_help); + + err = print_pka_or_dane_records (out, + keyblock, pk, data, datalen, + (options & EXPORT_PKA_FORMAT), + (options & EXPORT_DANE_FORMAT)); + if (err) + goto leave; + + iobuf_close (out_help); + out_help = iobuf_temp (); + } + + } + if (gpg_err_code (err) == GPG_ERR_NOT_FOUND) + err = 0; + + leave: + iobuf_cancel (out_help); + gcry_cipher_close (cipherhd); + xfree(desc); + keydb_release (kdbhd); + if (err || !keyblock_out) + release_kbnode( keyblock ); + if( !*any ) + log_info(_("WARNING: nothing exported\n")); + return err; +} + + + + +static gpg_error_t +key_to_sshblob (membuf_t *mb, const char *identifier, ...) +{ + va_list arg_ptr; + gpg_error_t err = 0; + unsigned char nbuf[4]; + unsigned char *buf; + size_t buflen; + gcry_mpi_t a; + + ulongtobuf (nbuf, (ulong)strlen (identifier)); + put_membuf (mb, nbuf, 4); + put_membuf_str (mb, identifier); + if (!strncmp (identifier, "ecdsa-sha2-", 11)) + { + ulongtobuf (nbuf, (ulong)strlen (identifier+11)); + put_membuf (mb, nbuf, 4); + put_membuf_str (mb, identifier+11); + } + va_start (arg_ptr, identifier); + while ((a = va_arg (arg_ptr, gcry_mpi_t))) + { + err = gcry_mpi_aprint (GCRYMPI_FMT_SSH, &buf, &buflen, a); + if (err) + break; + if (!strcmp (identifier, "ssh-ed25519") + && buflen > 5 && buf[4] == 0x40) + { + /* We need to strip our 0x40 prefix. */ + put_membuf (mb, "\x00\x00\x00\x20", 4); + put_membuf (mb, buf+5, buflen-5); + } + else + put_membuf (mb, buf, buflen); + gcry_free (buf); + } + va_end (arg_ptr); + return err; +} + +/* Export the key identified by USERID in the SSH public key format. + The function exports the latest subkey with Authentication + capability unless the '!' suffix is used to export a specific + key. */ +gpg_error_t +export_ssh_key (ctrl_t ctrl, const char *userid) +{ + gpg_error_t err; + kbnode_t keyblock = NULL; + KEYDB_SEARCH_DESC desc; + u32 latest_date; + u32 curtime = make_timestamp (); + kbnode_t latest_key, node; + PKT_public_key *pk; + const char *identifier = NULL; + membuf_t mb; + estream_t fp = NULL; + struct b64state b64_state; + const char *fname = "-"; + + init_membuf (&mb, 4096); + + /* We need to know whether the key has been specified using the + exact syntax ('!' suffix). Thus we need to run a + classify_user_id on our own. */ + err = classify_user_id (userid, &desc, 1); + + /* Get the public key. */ + if (!err) + { + getkey_ctx_t getkeyctx; + + err = get_pubkey_byname (ctrl, GET_PUBKEY_NO_AKL, + &getkeyctx, NULL, userid, &keyblock, + NULL, + 0 /* Only usable keys or given exact. */); + if (!err) + { + err = getkey_next (ctrl, getkeyctx, NULL, NULL); + if (!err) + err = gpg_error (GPG_ERR_AMBIGUOUS_NAME); + else if (gpg_err_code (err) == GPG_ERR_NO_PUBKEY) + err = 0; + } + getkey_end (ctrl, getkeyctx); + } + if (err) + { + log_error (_("key \"%s\" not found: %s\n"), userid, gpg_strerror (err)); + return err; + } + + /* The finish_lookup code in getkey.c does not handle auth keys, + thus we have to duplicate the code here to find the latest + subkey. However, if the key has been found using an exact match + ('!' notation) we use that key without any further checks and + even allow the use of the primary key. */ + latest_date = 0; + latest_key = NULL; + for (node = keyblock; node; node = node->next) + { + if ((node->pkt->pkttype == PKT_PUBLIC_SUBKEY + || node->pkt->pkttype == PKT_PUBLIC_KEY) + && node->pkt->pkt.public_key->flags.exact) + { + latest_key = node; + break; + } + } + if (!latest_key) + { + for (node = keyblock; node; node = node->next) + { + if (node->pkt->pkttype != PKT_PUBLIC_SUBKEY) + continue; + + pk = node->pkt->pkt.public_key; + if (DBG_LOOKUP) + log_debug ("\tchecking subkey %08lX\n", + (ulong) keyid_from_pk (pk, NULL)); + if (!(pk->pubkey_usage & PUBKEY_USAGE_AUTH)) + { + if (DBG_LOOKUP) + log_debug ("\tsubkey not usable for authentication\n"); + continue; + } + if (!pk->flags.valid) + { + if (DBG_LOOKUP) + log_debug ("\tsubkey not valid\n"); + continue; + } + if (pk->flags.revoked) + { + if (DBG_LOOKUP) + log_debug ("\tsubkey has been revoked\n"); + continue; + } + if (pk->has_expired) + { + if (DBG_LOOKUP) + log_debug ("\tsubkey has expired\n"); + continue; + } + if (pk->timestamp > curtime && !opt.ignore_valid_from) + { + if (DBG_LOOKUP) + log_debug ("\tsubkey not yet valid\n"); + continue; + } + if (DBG_LOOKUP) + log_debug ("\tsubkey might be fine\n"); + /* In case a key has a timestamp of 0 set, we make sure that it + is used. A better change would be to compare ">=" but that + might also change the selected keys and is as such a more + intrusive change. */ + if (pk->timestamp > latest_date || (!pk->timestamp && !latest_date)) + { + latest_date = pk->timestamp; + latest_key = node; + } + } + + /* If no subkey was suitable check the primary key. */ + if (!latest_key + && (node = keyblock) && node->pkt->pkttype == PKT_PUBLIC_KEY) + { + pk = node->pkt->pkt.public_key; + if (DBG_LOOKUP) + log_debug ("\tchecking primary key %08lX\n", + (ulong) keyid_from_pk (pk, NULL)); + if (!(pk->pubkey_usage & PUBKEY_USAGE_AUTH)) + { + if (DBG_LOOKUP) + log_debug ("\tprimary key not usable for authentication\n"); + } + else if (!pk->flags.valid) + { + if (DBG_LOOKUP) + log_debug ("\tprimary key not valid\n"); + } + else if (pk->flags.revoked) + { + if (DBG_LOOKUP) + log_debug ("\tprimary key has been revoked\n"); + } + else if (pk->has_expired) + { + if (DBG_LOOKUP) + log_debug ("\tprimary key has expired\n"); + } + else if (pk->timestamp > curtime && !opt.ignore_valid_from) + { + if (DBG_LOOKUP) + log_debug ("\tprimary key not yet valid\n"); + } + else + { + if (DBG_LOOKUP) + log_debug ("\tprimary key is fine\n"); + latest_date = pk->timestamp; + latest_key = node; + } + } + } + + if (!latest_key) + { + err = gpg_error (GPG_ERR_UNUSABLE_PUBKEY); + log_error (_("key \"%s\" not found: %s\n"), userid, gpg_strerror (err)); + goto leave; + } + + pk = latest_key->pkt->pkt.public_key; + if (DBG_LOOKUP) + log_debug ("\tusing key %08lX\n", (ulong) keyid_from_pk (pk, NULL)); + + switch (pk->pubkey_algo) + { + case PUBKEY_ALGO_DSA: + identifier = "ssh-dss"; + err = key_to_sshblob (&mb, identifier, + pk->pkey[0], pk->pkey[1], pk->pkey[2], pk->pkey[3], + NULL); + break; + + case PUBKEY_ALGO_RSA: + case PUBKEY_ALGO_RSA_S: + identifier = "ssh-rsa"; + err = key_to_sshblob (&mb, identifier, pk->pkey[1], pk->pkey[0], NULL); + break; + + case PUBKEY_ALGO_ECDSA: + { + char *curveoid; + const char *curve; + + curveoid = openpgp_oid_to_str (pk->pkey[0]); + if (!curveoid) + err = gpg_error_from_syserror (); + else if (!(curve = openpgp_oid_to_curve (curveoid, 0))) + err = gpg_error (GPG_ERR_UNKNOWN_CURVE); + else + { + if (!strcmp (curve, "nistp256")) + identifier = "ecdsa-sha2-nistp256"; + else if (!strcmp (curve, "nistp384")) + identifier = "ecdsa-sha2-nistp384"; + else if (!strcmp (curve, "nistp521")) + identifier = "ecdsa-sha2-nistp521"; + + if (!identifier) + err = gpg_error (GPG_ERR_UNKNOWN_CURVE); + else + err = key_to_sshblob (&mb, identifier, pk->pkey[1], NULL); + } + xfree (curveoid); + } + break; + + case PUBKEY_ALGO_EDDSA: + if (!openpgp_oid_is_ed25519 (pk->pkey[0])) + err = gpg_error (GPG_ERR_UNKNOWN_CURVE); + else + { + identifier = "ssh-ed25519"; + err = key_to_sshblob (&mb, identifier, pk->pkey[1], NULL); + } + break; + + case PUBKEY_ALGO_ELGAMAL_E: + case PUBKEY_ALGO_ELGAMAL: + err = gpg_error (GPG_ERR_UNUSABLE_PUBKEY); + break; + + default: + err = GPG_ERR_PUBKEY_ALGO; + break; + } + + if (!identifier) + goto leave; + + if (opt.outfile && *opt.outfile && strcmp (opt.outfile, "-")) + fp = es_fopen ((fname = opt.outfile), "w"); + else + fp = es_stdout; + if (!fp) + { + err = gpg_error_from_syserror (); + log_error (_("error creating '%s': %s\n"), fname, gpg_strerror (err)); + goto leave; + } + + es_fprintf (fp, "%s ", identifier); + err = b64enc_start_es (&b64_state, fp, ""); + if (!err) + { + void *blob; + size_t bloblen; + + blob = get_membuf (&mb, &bloblen); + if (blob) + { + err = b64enc_write (&b64_state, blob, bloblen); + xfree (blob); + if (err) + goto leave; + } + err = b64enc_finish (&b64_state); + } + if (err) + goto leave; + es_fprintf (fp, " openpgp:0x%08lX\n", (ulong)keyid_from_pk (pk, NULL)); + + if (es_ferror (fp)) + err = gpg_error_from_syserror (); + else + { + if (fp != es_stdout && es_fclose (fp)) + err = gpg_error_from_syserror (); + fp = NULL; + } + + if (err) + log_error (_("error writing '%s': %s\n"), fname, gpg_strerror (err)); + + leave: + if (fp != es_stdout) + es_fclose (fp); + xfree (get_membuf (&mb, NULL)); + release_kbnode (keyblock); + return err; +} diff --git a/g10/filter.h b/g10/filter.h new file mode 100644 index 0000000..d2f6c3f --- /dev/null +++ b/g10/filter.h @@ -0,0 +1,164 @@ +/* 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 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ +#ifndef G10_FILTER_H +#define G10_FILTER_H + +#include "../common/types.h" +#include "dek.h" + +typedef struct { + gcry_md_hd_t md; /* catch all */ + gcry_md_hd_t md2; /* if we want to calculate an alternate hash */ + size_t maxbuf_size; +} md_filter_context_t; + +typedef struct { + int refcount; /* Initialized to 1. */ + + /* 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 fields 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; + 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; + gcry_cipher_hd_t cipher_hd; + unsigned int wrote_header : 1; + unsigned int short_blklen_warn : 1; + unsigned long short_blklen_count; + gcry_md_hd_t mdc_hash; + byte enchash[20]; +} 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; + gcry_md_hd_t 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 */ + int refcount; +} progress_filter_context_t; + +/* encrypt_filter_context_t defined in main.h */ + +/*-- mdfilter.c --*/ +int md_filter( void *opaque, int control, iobuf_t 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_t iobuf); +int use_armor_filter( iobuf_t a ); +UnarmorPump unarmor_pump_new (void); +void unarmor_pump_release (UnarmorPump x); +int unarmor_pump (UnarmorPump x, int c); + +/*-- compress.c --*/ +gpg_error_t push_compress_filter (iobuf_t out, compress_filter_context_t *zfx, + int algo); +gpg_error_t push_compress_filter2 (iobuf_t out,compress_filter_context_t *zfx, + int algo, int rel); + +/*-- cipher.c --*/ +int cipher_filter_cfb (void *opaque, int control, + iobuf_t chain, byte *buf, size_t *ret_len); + +/*-- textfilter.c --*/ +int text_filter( void *opaque, int control, + iobuf_t chain, byte *buf, size_t *ret_len); +int copy_clearsig_text (iobuf_t out, iobuf_t inp, gcry_md_hd_t md, + int escape_dash, int escape_from); + +/*-- progress.c --*/ +progress_filter_context_t *new_progress_context (void); +void release_progress_context (progress_filter_context_t *pfx); +void handle_progress (progress_filter_context_t *pfx, + iobuf_t 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..4df8896 --- /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, 2010 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include +#include +#include +#include + +#include "gpg.h" +#include "../common/util.h" +#include "packet.h" +#include "../common/iobuf.h" +#include "options.h" + + +/* This is mpi_copy with a fix for opaque MPIs which store a NULL + pointer. This will also be fixed in Libggcrypt 1.7.0. */ +static gcry_mpi_t +my_mpi_copy (gcry_mpi_t a) +{ + if (a + && gcry_mpi_get_flag (a, GCRYMPI_FLAG_OPAQUE) + && !gcry_mpi_get_opaque (a, NULL)) + return NULL; + + return gcry_mpi_copy (a); +} + + +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_release(enc->data[0]); + for(i=0; i < n; i++ ) + mpi_release( 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_release(sig->data[0]); + for(i=0; i < n; i++ ) + mpi_release( 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->signers_uid); + + xfree(sig); +} + + +void +release_public_key_parts (PKT_public_key *pk) +{ + int n, i; + + if (pk->seckey_info) + n = pubkey_get_nskey (pk->pubkey_algo); + else + n = pubkey_get_npkey (pk->pubkey_algo); + if (!n) + mpi_release (pk->pkey[0]); + for (i=0; i < n; i++ ) + { + mpi_release (pk->pkey[i]); + pk->pkey[i] = NULL; + } + if (pk->seckey_info) + { + xfree (pk->seckey_info); + pk->seckey_info = NULL; + } + if (pk->prefs) + { + xfree (pk->prefs); + pk->prefs = NULL; + } + free_user_id (pk->user_id); + pk->user_id = NULL; + if (pk->revkey) + { + xfree(pk->revkey); + pk->revkey=NULL; + pk->numrevkeys=0; + } + if (pk->serialno) + { + xfree (pk->serialno); + pk->serialno = NULL; + } + if (pk->updateurl) + { + xfree (pk->updateurl); + pk->updateurl = NULL; + } +} + + +/* Free an allocated public key structure including all parts. + Passing NULL is allowed. */ +void +free_public_key (PKT_public_key *pk) +{ + if (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; +} + + +/* Copy the public key S to D. If D is NULL allocate a new public key + structure. If S has seckret key infos, only the public stuff is + copied. */ +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->seckey_info = NULL; + d->user_id = scopy_user_id (s->user_id); + d->prefs = copy_prefs (s->prefs); + + n = pubkey_get_npkey (s->pubkey_algo); + i = 0; + if (!n) + d->pkey[i++] = my_mpi_copy (s->pkey[0]); + else + { + for (; i < n; i++ ) + d->pkey[i] = my_mpi_copy (s->pkey[i]); + } + for (; i < PUBKEY_MAX_NSKEY; i++) + d->pkey[i] = NULL; + + 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; + + if (s->serialno) + d->serialno = xstrdup (s->serialno); + if (s->updateurl) + d->updateurl = xstrdup (s->updateurl); + + return d; +} + + + +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] = my_mpi_copy(s->data[0]); + else { + for(i=0; i < n; i++ ) + d->data[i] = my_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->signers_uid) + d->signers_uid = xstrdup (s->signers_uid); + 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 +free_comment( PKT_comment *rem ) +{ + xfree(rem); +} + +void +free_attributes(PKT_user_id *uid) +{ + if (!uid) + return; + + 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) +{ + if (!uid) + return; + + log_assert (uid->ref > 0); + if (--uid->ref) + return; + + free_attributes(uid); + xfree (uid->prefs); + xfree (uid->namehash); + xfree (uid->updateurl); + xfree (uid->mbox); + xfree (uid); +} + +void +free_compressed( PKT_compressed *zd ) +{ + if (!zd) + return; + + if (zd->buf) + { + /* We need to skip some bytes. Because 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) + return; + + if (ed->buf) + { + /* We need 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) + return; + + if (pt->buf) + { /* We need 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, parse_packet_ctx_t parsectx) +{ + if (!pkt || !pkt->pkt.generic) + { + if (parsectx && parsectx->last_pkt.pkt.generic) + { + if (parsectx->free_last_pkt) + { + free_packet (&parsectx->last_pkt, NULL); + parsectx->free_last_pkt = 0; + } + parsectx->last_pkt.pkttype = 0; + parsectx->last_pkt.pkt.generic = NULL; + } + return; + } + + if (DBG_MEMORY) + log_debug ("free_packet() type=%d\n", pkt->pkttype); + + /* If we have a parser context holding PKT then do not free the + * packet but set a flag that the packet in the parser context is + * now a deep copy. */ + if (parsectx && !parsectx->free_last_pkt + && parsectx->last_pkt.pkttype == pkt->pkttype + && parsectx->last_pkt.pkt.generic == pkt->pkt.generic) + { + parsectx->last_pkt = *pkt; + parsectx->free_last_pkt = 1; + pkt->pkt.generic = NULL; + return; + } + + 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: + case PKT_SECRET_KEY: + case PKT_SECRET_SUBKEY: + free_public_key (pkt->pkt.public_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: + case PKT_ENCRYPTED_AEAD: + 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 ) { /* unknown algorithm, rest is in opaque MPI */ + if( mpi_cmp( a->pkey[0], b->pkey[0] ) ) + return -1; /* can't compare due to unknown algorithm */ + } else { + for(i=0; i < n; i++ ) { + if( mpi_cmp( a->pkey[i], b->pkey[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..4642174 --- /dev/null +++ b/g10/getkey.c @@ -0,0 +1,4544 @@ +/* getkey.c - Get a key from the database + * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, + * 2007, 2008, 2010 Free Software Foundation, Inc. + * Copyright (C) 2015, 2016 g10 Code GmbH + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include +#include +#include +#include +#include + +#include "gpg.h" +#include "../common/util.h" +#include "packet.h" +#include "../common/iobuf.h" +#include "keydb.h" +#include "options.h" +#include "main.h" +#include "trustdb.h" +#include "../common/i18n.h" +#include "keyserver-internal.h" +#include "call-agent.h" +#include "../common/host2net.h" +#include "../common/mbox-util.h" +#include "../common/status.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 + +/* Flags values returned by the lookup code. Note that the values are + * directly used by the KEY_CONSIDERED status line. */ +#define LOOKUP_NOT_SELECTED (1<<0) +#define LOOKUP_ALL_SUBKEYS_EXPIRED (1<<1) /* or revoked */ + + +/* A context object used by the lookup functions. */ +struct getkey_ctx_s +{ + /* Part of the search criteria: whether the search is an exact + search or not. A search that is exact requires that a key or + subkey meet all of the specified criteria. A search that is not + exact allows selecting a different key or subkey from the + keyblock that matched the critera. Further, an exact search + returns the key or subkey that matched whereas a non-exact search + typically returns the primary key. See finish_lookup for + details. */ + int exact; + + /* Part of the search criteria: Whether the caller only wants keys + with an available secret key. This is used by getkey_next to get + the next result with the same initial criteria. */ + int want_secret; + + /* Part of the search criteria: The type of the requested key. A + mask of PUBKEY_USAGE_SIG, PUBKEY_USAGE_ENC and PUBKEY_USAGE_CERT. + If non-zero, then for a key to match, it must implement one of + the required uses. */ + int req_usage; + + /* The database handle. */ + KEYDB_HANDLE kr_handle; + + /* Whether we should call xfree() on the context when the context is + released using getkey_end()). */ + int not_allocated; + + /* This variable is used as backing store for strings which have + their address used in ITEMS. */ + strlist_t extra_list; + + /* Hack to return the mechanism (AKL_foo) used to find the key. */ + int found_via_akl; + + /* Part of the search criteria: The low-level search specification + as passed to keydb_search. */ + int nitems; + /* This must be the last element in the structure. When we allocate + the structure, we allocate it so that ITEMS can hold 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; + char fpr[MAX_FINGERPRINT_LEN]; + 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 (ctrl_t ctrl, kbnode_t keyblock); +static int lookup (ctrl_t ctrl, getkey_ctx_t ctx, int want_secret, + kbnode_t *ret_keyblock, kbnode_t *ret_found_key); +static kbnode_t finish_lookup (kbnode_t keyblock, + unsigned int req_usage, int want_exact, + int want_secret, unsigned int *r_flags); +static void print_status_key_considered (kbnode_t keyblock, unsigned int flags); + + +#if 0 +static void +print_stats () +{ + int i; + for (i = 0; i < DIM (lkup_stats); i++) + { + if (lkup_stats[i].any) + es_fprintf (es_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 + + +/* Cache a copy of a public key in the public key cache. PK is not + * cached if caching is disabled (via getkey_disable_caches), if + * PK->FLAGS.DONT_CACHE is set, we don't know how to derive a key id + * from the public key (e.g., unsupported algorithm), or a key with + * the key id is already in the cache. + * + * The public key packet is copied into the cache using + * copy_public_key. Thus, any secret parts are not copied, for + * instance. + * + * This cache is filled by get_pubkey and is read by get_pubkey and + * get_pubkey_fast. */ +void +cache_public_key (PKT_public_key * pk) +{ +#if MAX_PK_CACHE_ENTRIES + pk_cache_entry_t ce, ce2; + u32 keyid[2]; + + if (pk_cache_disabled) + return; + + if (pk->flags.dont_cache) + return; + + if (is_ELGAMAL (pk->pubkey_algo) + || pk->pubkey_algo == PUBKEY_ALGO_DSA + || pk->pubkey_algo == PUBKEY_ALGO_ECDSA + || pk->pubkey_algo == PUBKEY_ALGO_EDDSA + || pk->pubkey_algo == PUBKEY_ALGO_ECDH + || 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) + { + int n; + + /* Remove the last 50% of the entries. */ + for (ce = pk_cache, n = 0; ce && n < pk_cache_entries/2; n++) + ce = ce->next; + if (ce && ce != pk_cache && ce->next) + { + ce2 = ce->next; + ce->next = NULL; + ce = ce2; + for (; ce; ce = ce2) + { + ce2 = ce->next; + free_public_key (ce->pk); + xfree (ce); + pk_cache_entries--; + } + } + log_assert (pk_cache_entries < MAX_PK_CACHE_ENTRIES); + } + 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 function 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 the 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->flags.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. */ + fingerprint_from_pk (k->pkt->pkt.public_key, a->fpr, NULL); + 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; + + for (b = r->keyids; b; b = b->next) + { + if (!memcmp (b->fpr, a->fpr, MAX_FINGERPRINT_LEN)) + { + 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++; +} + + +/* Disable and drop the public key cache (which is filled by + cache_public_key and get_pubkey). Note: there is currently no way + to re-enable this cache. */ +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 ? */ +} + + +/* Free a list of pubkey_t objects. */ +void +pubkeys_free (pubkey_t keys) +{ + while (keys) + { + pubkey_t next = keys->next; + xfree (keys->pk); + release_kbnode (keys->keyblock); + xfree (keys); + keys = next; + } +} + + +static void +pk_from_block (PKT_public_key *pk, kbnode_t keyblock, kbnode_t found_key) +{ + kbnode_t a = found_key ? found_key : keyblock; + + log_assert (a->pkt->pkttype == PKT_PUBLIC_KEY + || a->pkt->pkttype == PKT_PUBLIC_SUBKEY); + + copy_public_key (pk, a->pkt->pkt.public_key); +} + + +/* Specialized version of get_pubkey which retrieves the key based on + * information in SIG. In contrast to get_pubkey PK is required. IF + * FORCED_PK is not NULL, this public key is used and copied to PK. */ +gpg_error_t +get_pubkey_for_sig (ctrl_t ctrl, PKT_public_key *pk, PKT_signature *sig, + PKT_public_key *forced_pk) +{ + const byte *fpr; + size_t fprlen; + + if (forced_pk) + { + copy_public_key (pk, forced_pk); + return 0; + } + + /* First try the new ISSUER_FPR info. */ + fpr = issuer_fpr_raw (sig, &fprlen); + if (fpr && !get_pubkey_byfprint (ctrl, pk, NULL, fpr, fprlen)) + return 0; + + /* Fallback to use the ISSUER_KEYID. */ + return get_pubkey (ctrl, pk, sig->keyid); +} + + +/* Return the public key with the key id KEYID and store it at PK. + * The resources in *PK should be released using + * release_public_key_parts(). This function also stores a copy of + * the public key in the user id cache (see cache_public_key). + * + * If PK is NULL, this function just stores the public key in the + * cache and returns the usual return code. + * + * PK->REQ_USAGE (which is a mask of PUBKEY_USAGE_SIG, + * PUBKEY_USAGE_ENC and PUBKEY_USAGE_CERT) is passed through to the + * lookup function. If this is non-zero, only keys with the specified + * usage will be returned. As such, it is essential that + * PK->REQ_USAGE be correctly initialized! + * + * Returns 0 on success, GPG_ERR_NO_PUBKEY if there is no public key + * with the specified key id, or another error code if an error + * occurs. + * + * If the data was not read from the cache, then the self-signed data + * has definitely been merged into the public key using + * merge_selfsigs. */ +int +get_pubkey (ctrl_t ctrl, 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]) + /* XXX: We don't check PK->REQ_USAGE here, but if we don't + read from the cache, we do check it! */ + { + copy_public_key (pk, ce->pk); + return 0; + } + } + } +#endif + /* More init stuff. */ + if (!pk) + { + internal++; + pk = xtrycalloc (1, sizeof *pk); + if (!pk) + { + rc = gpg_error_from_syserror (); + goto leave; + } + } + + + /* Do a lookup. */ + { + struct getkey_ctx_s ctx; + kbnode_t kb = NULL; + kbnode_t found_key = NULL; + + memset (&ctx, 0, sizeof ctx); + ctx.exact = 1; /* Use the key ID exactly as given. */ + ctx.not_allocated = 1; + + if (ctrl && ctrl->cached_getkey_kdb) + { + ctx.kr_handle = ctrl->cached_getkey_kdb; + ctrl->cached_getkey_kdb = NULL; + keydb_search_reset (ctx.kr_handle); + } + else + { + ctx.kr_handle = keydb_new (); + if (!ctx.kr_handle) + { + rc = gpg_error_from_syserror (); + goto leave; + } + } + 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_usage = pk->req_usage; + rc = lookup (ctrl, &ctx, 0, &kb, &found_key); + if (!rc) + { + pk_from_block (pk, kb, found_key); + } + getkey_end (ctrl, &ctx); + release_kbnode (kb); + } + if (!rc) + goto leave; + + rc = GPG_ERR_NO_PUBKEY; + +leave: + if (!rc) + cache_public_key (pk); + if (internal) + free_public_key (pk); + return rc; +} + + +/* Same as get_pubkey but if the key was not found the function tries + * to import it from LDAP. FIXME: We should not need this but swicth + * to a fingerprint lookup. */ +gpg_error_t +get_pubkey_with_ldap_fallback (ctrl_t ctrl, PKT_public_key *pk, u32 *keyid) +{ + gpg_error_t err; + + err = get_pubkey (ctrl, pk, keyid); + if (!err) + return 0; + + if (gpg_err_code (err) != GPG_ERR_NO_PUBKEY) + return err; + + /* Note that this code does not handle the case for two readers + * having both openpgp encryption keys. Only one will be tried. */ + if (opt.debug) + log_debug ("using LDAP to find a public key\n"); + err = keyserver_import_keyid (ctrl, keyid, + opt.keyserver, KEYSERVER_IMPORT_FLAG_LDAP); + if (gpg_err_code (err) == GPG_ERR_NO_DATA + || gpg_err_code (err) == GPG_ERR_NO_KEYSERVER) + { + /* Dirmngr returns NO DATA is the selected keyserver + * does not have the requested key. It returns NO + * KEYSERVER if no LDAP keyservers are configured. */ + err = gpg_error (GPG_ERR_NO_PUBKEY); + } + if (err) + return err; + + return get_pubkey (ctrl, pk, keyid); +} + + +/* Similar to get_pubkey, but it does not take PK->REQ_USAGE into + * account nor does it merge in the self-signed data. This function + * also only considers primary keys. It is intended to be used as a + * quick check of the key to avoid recursion. It should only be used + * in very certain cases. Like get_pubkey and unlike any of the other + * lookup functions, this function also consults the user id cache + * (see cache_public_key). + * + * Return the public key in *PK. The resources in *PK should be + * released using release_public_key_parts(). */ +int +get_pubkey_fast (PKT_public_key * pk, u32 * keyid) +{ + int rc = 0; + KEYDB_HANDLE hd; + KBNODE keyblock; + u32 pkid[2]; + + log_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] + /* Only consider primary keys. */ + && ce->pk->keyid[0] == ce->pk->main_keyid[0] + && ce->pk->keyid[1] == ce->pk->main_keyid[1]) + { + if (pk) + copy_public_key (pk, ce->pk); + return 0; + } + } + } +#endif + + hd = keydb_new (); + if (!hd) + return gpg_error_from_syserror (); + rc = keydb_search_kid (hd, keyid); + if (gpg_err_code (rc) == GPG_ERR_NOT_FOUND) + { + keydb_release (hd); + return GPG_ERR_NO_PUBKEY; + } + rc = keydb_get_keyblock (hd, &keyblock); + keydb_release (hd); + if (rc) + { + log_error ("keydb_get_keyblock failed: %s\n", gpg_strerror (rc)); + return GPG_ERR_NO_PUBKEY; + } + + log_assert (keyblock && keyblock->pkt + && keyblock->pkt->pkttype == PKT_PUBLIC_KEY); + + /* We return the primary key. If KEYID matched a subkey, then we + return an error. */ + 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 = GPG_ERR_NO_PUBKEY; + + release_kbnode (keyblock); + + /* Not caching key here since it won't have all of the fields + properly set. */ + + return rc; +} + + +/* Return the entire keyblock used to create SIG. This is a + * specialized version of get_pubkeyblock. + * + * FIXME: This is a hack because get_pubkey_for_sig was already called + * and it could have used a cache to hold the key. */ +kbnode_t +get_pubkeyblock_for_sig (ctrl_t ctrl, PKT_signature *sig) +{ + const byte *fpr; + size_t fprlen; + kbnode_t keyblock; + + /* First try the new ISSUER_FPR info. */ + fpr = issuer_fpr_raw (sig, &fprlen); + if (fpr && !get_pubkey_byfprint (ctrl, NULL, &keyblock, fpr, fprlen)) + return keyblock; + + /* Fallback to use the ISSUER_KEYID. */ + return get_pubkeyblock (ctrl, sig->keyid); +} + + +/* Return the key block for the key with key id KEYID or NULL, if an + * error occurs. Use release_kbnode() to release the key block. + * + * The self-signed data has already been merged into the public key + * using merge_selfsigs. */ +kbnode_t +get_pubkeyblock (ctrl_t ctrl, 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 (); + if (!ctx.kr_handle) + return NULL; + 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 (ctrl, &ctx, 0, &keyblock, NULL); + getkey_end (ctrl, &ctx); + + return rc ? NULL : keyblock; +} + + +/* Return the public key with the key id KEYID iff the secret key is + * available and store it at PK. The resources should be released + * using release_public_key_parts(). + * + * Unlike other lookup functions, PK may not be NULL. PK->REQ_USAGE + * is passed through to the lookup function and is a mask of + * PUBKEY_USAGE_SIG, PUBKEY_USAGE_ENC and PUBKEY_USAGE_CERT. Thus, it + * must be valid! If this is non-zero, only keys with the specified + * usage will be returned. + * + * Returns 0 on success. If a public key with the specified key id is + * not found or a secret key is not available for that public key, an + * error code is returned. Note: this function ignores legacy keys. + * An error code is also return if an error occurs. + * + * The self-signed data has already been merged into the public key + * using merge_selfsigs. */ +gpg_error_t +get_seckey (ctrl_t ctrl, PKT_public_key *pk, u32 *keyid) +{ + gpg_error_t err; + struct getkey_ctx_s ctx; + kbnode_t keyblock = NULL; + kbnode_t found_key = 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 (); + if (!ctx.kr_handle) + return gpg_error_from_syserror (); + 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_usage = pk->req_usage; + err = lookup (ctrl, &ctx, 1, &keyblock, &found_key); + if (!err) + { + pk_from_block (pk, keyblock, found_key); + } + getkey_end (ctrl, &ctx); + release_kbnode (keyblock); + + if (!err) + { + err = agent_probe_secret_key (/*ctrl*/NULL, pk); + if (err) + release_public_key_parts (pk); + } + + return err; +} + + +/* Skip unusable keys. A key is unusable if it is revoked, expired or + disabled or if the selected user id is revoked or expired. */ +static int +skip_unusable (void *opaque, u32 * keyid, int uid_no) +{ + ctrl_t ctrl = opaque; + int unusable = 0; + KBNODE keyblock; + PKT_public_key *pk; + + keyblock = get_pubkeyblock (ctrl, keyid); + if (!keyblock) + { + log_error ("error checking usability status of %s\n", keystr (keyid)); + goto leave; + } + + pk = keyblock->pkt->pkt.public_key; + + /* Is the key revoked or expired? */ + if (pk->flags.revoked || pk->has_expired) + unusable = 1; + + /* Is the user ID in question revoked or expired? */ + if (!unusable && uid_no) + { + KBNODE node; + int uids_seen = 0; + + for (node = keyblock; node; node = node->next) + { + if (node->pkt->pkttype == PKT_USER_ID) + { + PKT_user_id *user_id = node->pkt->pkt.user_id; + + uids_seen ++; + if (uids_seen != uid_no) + continue; + + if (user_id->flags.revoked || user_id->flags.expired) + unusable = 1; + + break; + } + } + + /* If UID_NO is non-zero, then the keyblock better have at least + that many UIDs. */ + log_assert (uids_seen == uid_no); + } + + if (!unusable) + unusable = pk_is_disabled (pk); + +leave: + release_kbnode (keyblock); + return unusable; +} + + +/* Search for keys matching some criteria. + + If RETCTX is not NULL, then the constructed context is returned in + *RETCTX so that getpubkey_next can be used to get subsequent + results. In this case, getkey_end() must be used to free the + search context. If RETCTX is not NULL, then RET_KDBHD must be + NULL. + + If NAMELIST is not NULL, then a search query is constructed using + classify_user_id on each of the strings in the list. (Recall: the + database does an OR of the terms, not an AND.) If NAMELIST is + NULL, then all results are returned. + + If PK is not NULL, the public key of the first result is returned + in *PK. Note: PK->REQ_USAGE must be valid!!! If PK->REQ_USAGE is + set, it is used to filter the search results. See the + documentation for finish_lookup to understand exactly how this is + used. Note: The self-signed data has already been merged into the + public key using merge_selfsigs. Free *PK by calling + release_public_key_parts (or, if PK was allocated using xfree, you + can use free_public_key, which calls release_public_key_parts(PK) + and then xfree(PK)). + + If WANT_SECRET is set, then only keys with an available secret key + (either locally or via key registered on a smartcard) are returned. + + If INCLUDE_UNUSABLE is set, then unusable keys (see the + documentation for skip_unusable for an exact definition) are + skipped unless they are looked up by key id or by fingerprint. + + If RET_KB is not NULL, the keyblock is returned in *RET_KB. This + should be freed using release_kbnode(). + + If RET_KDBHD is not NULL, then the new database handle used to + conduct the search is returned in *RET_KDBHD. This can be used to + get subsequent results using keydb_search_next. Note: in this + case, no advanced filtering is done for subsequent results (e.g., + WANT_SECRET and PK->REQ_USAGE are not respected). + + This function returns 0 on success. Otherwise, an error code is + returned. In particular, GPG_ERR_NO_PUBKEY or GPG_ERR_NO_SECKEY + (if want_secret is set) is returned if the key is not found. */ +static int +key_byname (ctrl_t ctrl, GETKEY_CTX *retctx, strlist_t namelist, + PKT_public_key *pk, + int want_secret, int include_unusable, + KBNODE * ret_kb, KEYDB_HANDLE * ret_kdbhd) +{ + int rc = 0; + int n; + strlist_t r; + GETKEY_CTX ctx; + KBNODE help_kb = NULL; + KBNODE found_key = NULL; + + if (retctx) + { + /* Reset the returned context in case of error. */ + log_assert (!ret_kdbhd); /* Not allowed because the handle is stored + in the context. */ + *retctx = NULL; + } + if (ret_kdbhd) + *ret_kdbhd = NULL; + + if (!namelist) + /* No search terms: iterate over the whole DB. */ + { + 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; + ctx->items[0].skipfncvalue = ctrl; + } + } + else + { + /* Build the search context. */ + for (n = 0, r = namelist; r; r = r->next) + n++; + + /* CTX has space for a single search term at the end. Thus, we + need to allocate sizeof *CTX plus (n - 1) sizeof + CTX->ITEMS. */ + ctx = xmalloc_clear (sizeof *ctx + (n - 1) * sizeof ctx->items); + ctx->nitems = n; + + for (n = 0, r = namelist; r; r = r->next, n++) + { + gpg_error_t err; + + err = classify_user_id (r->d, &ctx->items[n], 1); + + if (ctx->items[n].exact) + ctx->exact = 1; + if (err) + { + xfree (ctx); + return gpg_err_code (err); /* FIXME: remove gpg_err_code. */ + } + 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->items[n].skipfncvalue = ctrl; + } + } + } + + ctx->want_secret = want_secret; + ctx->kr_handle = keydb_new (); + if (!ctx->kr_handle) + { + rc = gpg_error_from_syserror (); + getkey_end (ctrl, ctx); + return rc; + } + + if (!ret_kb) + ret_kb = &help_kb; + + if (pk) + { + ctx->req_usage = pk->req_usage; + } + + rc = lookup (ctrl, ctx, want_secret, ret_kb, &found_key); + if (!rc && pk) + { + pk_from_block (pk, *ret_kb, found_key); + } + + 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; + } + getkey_end (ctrl, ctx); + } + + return rc; +} + + +/* Find a public key identified by NAME. + * + * If name appears to be a valid RFC822 mailbox (i.e., email address) + * and auto key lookup is enabled (mode != GET_PUBKEY_NO_AKL), then + * the specified auto key lookup methods (--auto-key-lookup) are used + * to import the key into the local keyring. Otherwise, just the + * local keyring is consulted. + * + * MODE can be one of: + * GET_PUBKEY_NORMAL - The standard mode + * GET_PUBKEY_NO_AKL - The auto key locate functionality is + * disabled and only the local key ring is + * considered. Note: the local key ring is + * consulted even if local is not in the + * auto-key-locate option list! + * GET_PUBKEY_NO_LOCAL - Only the auto key locate functionaly is + * used and no local search is done. + * + * If RETCTX is not NULL, then the constructed context is returned in + * *RETCTX so that getpubkey_next can be used to get subsequent + * results. In this case, getkey_end() must be used to free the + * search context. If RETCTX is not NULL, then RET_KDBHD must be + * NULL. + * + * If PK is not NULL, the public key of the first result is returned + * in *PK. Note: PK->REQ_USAGE must be valid!!! PK->REQ_USAGE is + * passed through to the lookup function and is a mask of + * PUBKEY_USAGE_SIG, PUBKEY_USAGE_ENC and PUBKEY_USAGE_CERT. If this + * is non-zero, only keys with the specified usage will be returned. + * Note: The self-signed data has already been merged into the public + * key using merge_selfsigs. Free *PK by calling + * release_public_key_parts (or, if PK was allocated using xfree, you + * can use free_public_key, which calls release_public_key_parts(PK) + * and then xfree(PK)). + * + * NAME is a string, which is turned into a search query using + * classify_user_id. + * + * If RET_KEYBLOCK is not NULL, the keyblock is returned in + * *RET_KEYBLOCK. This should be freed using release_kbnode(). + * + * If RET_KDBHD is not NULL, then the new database handle used to + * conduct the search is returned in *RET_KDBHD. This can be used to + * get subsequent results using keydb_search_next or to modify the + * returned record. Note: in this case, no advanced filtering is done + * for subsequent results (e.g., PK->REQ_USAGE is not respected). + * Unlike RETCTX, this is always returned. + * + * If INCLUDE_UNUSABLE is set, then unusable keys (see the + * documentation for skip_unusable for an exact definition) are + * skipped unless they are looked up by key id or by fingerprint. + * + * This function returns 0 on success. Otherwise, an error code is + * returned. In particular, GPG_ERR_NO_PUBKEY or GPG_ERR_NO_SECKEY + * (if want_secret is set) is returned if the key is not found. */ +int +get_pubkey_byname (ctrl_t ctrl, enum get_pubkey_modes mode, + GETKEY_CTX * retctx, PKT_public_key * pk, + const char *name, KBNODE * ret_keyblock, + KEYDB_HANDLE * ret_kdbhd, int include_unusable) +{ + int rc; + strlist_t namelist = NULL; + struct akl *akl; + int is_mbox, is_fpr; + KEYDB_SEARCH_DESC fprbuf; + int nodefault = 0; + int anylocalfirst = 0; + int mechanism_type = AKL_NODEFAULT; + size_t fprbuf_fprlen = 0; + + /* If RETCTX is not NULL, then RET_KDBHD must be NULL. */ + log_assert (retctx == NULL || ret_kdbhd == NULL); + + if (retctx) + *retctx = NULL; + + /* Does NAME appear to be a mailbox (mail address)? */ + is_mbox = is_valid_mailbox (name); + if (!is_mbox && *name == '<' && name[1] && name[strlen(name)-1]=='>' + && name[1] != '>' + && is_valid_mailbox_mem (name+1, strlen (name)-2)) + { + /* The mailbox is in the form "" which is not + * detected by is_valid_mailbox. Set the flag but keep name as + * it is because the bracketed name is actual the better + * specification for a local search and the other methods + * extract the mail address anyway. */ + is_mbox = 1; + } + + /* If we are called due to --locate-external-key Check whether NAME + * is a fingerprint and then try to lookup that key by configured + * method which support lookup by fingerprint. FPRBUF carries the + * parsed fingerpint iff IS_FPR is true. */ + is_fpr = 0; + if (!is_mbox && mode == GET_PUBKEY_NO_LOCAL) + { + if (!classify_user_id (name, &fprbuf, 1) + && (fprbuf.mode == KEYDB_SEARCH_MODE_FPR16 + || fprbuf.mode == KEYDB_SEARCH_MODE_FPR20 + || fprbuf.mode == KEYDB_SEARCH_MODE_FPR)) + { + /* Note: We should get rid of the FPR16 because we don't + * support v3 keys anymore. However, in 2.3 the fingerprint + * code has already been reworked and thus it is + * questionable whether we should really tackle this here. */ + if (fprbuf.mode == KEYDB_SEARCH_MODE_FPR16) + fprbuf_fprlen = 16; + else + fprbuf_fprlen = 20; + is_fpr = 1; + } + } + + /* The auto-key-locate feature works as follows: there are a number + * of methods to look up keys. By default, the local keyring is + * tried first. Then, each method listed in the --auto-key-locate is + * tried in the order it appears. + * + * This can be changed as follows: + * + * - if nodefault appears anywhere in the list of options, then + * the local keyring is not tried first, or, + * + * - if local appears anywhere in the list of options, then the + * local keyring is not tried first, but in the order in which + * it was listed in the --auto-key-locate option. + * + * Note: we only save the search context in RETCTX if the local + * method is the first method tried (either explicitly or + * implicitly). */ + if (mode == GET_PUBKEY_NO_LOCAL) + nodefault = 1; /* Auto-key-locate but ignore "local". */ + else if (mode != GET_PUBKEY_NO_AKL) + { + /* auto-key-locate is enabled. */ + + /* nodefault is true if "nodefault" or "local" appear. */ + for (akl = opt.auto_key_locate; akl; akl = akl->next) + if (akl->type == AKL_NODEFAULT || akl->type == AKL_LOCAL) + { + nodefault = 1; + break; + } + /* anylocalfirst is true if "local" appears before any other + search methods (except "nodefault"). */ + for (akl = opt.auto_key_locate; akl; akl = akl->next) + if (akl->type != AKL_NODEFAULT) + { + if (akl->type == AKL_LOCAL) + anylocalfirst = 1; + break; + } + } + + if (!nodefault) + { + /* "nodefault" didn't occur. Thus, "local" is implicitly the + * first method to try. */ + anylocalfirst = 1; + } + + if (mode == GET_PUBKEY_NO_LOCAL) + { + /* Force using the AKL. If IS_MBOX is not set this is the final + * error code. */ + rc = GPG_ERR_NO_PUBKEY; + } + else if (nodefault && is_mbox) + { + /* Either "nodefault" or "local" (explicitly) appeared in the + * auto key locate list and NAME appears to be an email address. + * Don't try the local keyring. */ + rc = GPG_ERR_NO_PUBKEY; + } + else + { + /* Either "nodefault" and "local" don't appear in the auto key + * locate list (in which case we try the local keyring first) or + * NAME does not appear to be an email address (in which case we + * only try the local keyring). In this case, lookup NAME in + * the local keyring. */ + add_to_strlist (&namelist, name); + rc = key_byname (ctrl, retctx, namelist, pk, 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 (gpg_err_code (rc) == GPG_ERR_NO_PUBKEY + && mode != GET_PUBKEY_NO_AKL + && (is_mbox || is_fpr)) + { + /* NAME wasn't present in the local keyring (or we didn't try + * the local keyring). Since the auto key locate feature is + * enabled and NAME appears to be an email address, try the auto + * locate feature. */ + for (akl = opt.auto_key_locate; akl; akl = akl->next) + { + unsigned char *fpr = NULL; + size_t fpr_len; + int did_akl_local = 0; + int no_fingerprint = 0; + const char *mechanism_string = "?"; + + mechanism_type = akl->type; + switch (mechanism_type) + { + case AKL_NODEFAULT: + /* This is a dummy mechanism. */ + mechanism_string = ""; + rc = GPG_ERR_NO_PUBKEY; + break; + + case AKL_LOCAL: + if (mode == GET_PUBKEY_NO_LOCAL) + { + /* Note that we get here in is_fpr more, so there is + * no extra check for it required. */ + mechanism_string = ""; + rc = GPG_ERR_NO_PUBKEY; + } + else + { + mechanism_string = "Local"; + did_akl_local = 1; + if (retctx) + { + getkey_end (ctrl, *retctx); + *retctx = NULL; + } + add_to_strlist (&namelist, name); + rc = key_byname (ctrl, anylocalfirst ? retctx : NULL, + namelist, pk, 0, + include_unusable, ret_keyblock, ret_kdbhd); + } + break; + + case AKL_CERT: + if (is_fpr) + { + mechanism_string = ""; + rc = GPG_ERR_NO_PUBKEY; + } + else + { + mechanism_string = "DNS CERT"; + glo_ctrl.in_auto_key_retrieve++; + rc = keyserver_import_cert (ctrl, name, 0, &fpr, &fpr_len); + glo_ctrl.in_auto_key_retrieve--; + } + break; + + case AKL_PKA: + if (is_fpr) + { + mechanism_string = ""; + rc = GPG_ERR_NO_PUBKEY; + } + else + { + mechanism_string = "PKA"; + glo_ctrl.in_auto_key_retrieve++; + rc = keyserver_import_pka (ctrl, name, &fpr, &fpr_len); + glo_ctrl.in_auto_key_retrieve--; + } + break; + + case AKL_DANE: + if (is_fpr) + { + mechanism_string = ""; + rc = GPG_ERR_NO_PUBKEY; + } + else + { + mechanism_string = "DANE"; + glo_ctrl.in_auto_key_retrieve++; + rc = keyserver_import_cert (ctrl, name, 1, &fpr, &fpr_len); + glo_ctrl.in_auto_key_retrieve--; + } + break; + + case AKL_WKD: + if (is_fpr) + { + mechanism_string = ""; + rc = GPG_ERR_NO_PUBKEY; + } + else + { + mechanism_string = "WKD"; + glo_ctrl.in_auto_key_retrieve++; + rc = keyserver_import_wkd (ctrl, name, 0, &fpr, &fpr_len); + glo_ctrl.in_auto_key_retrieve--; + } + break; + + case AKL_LDAP: + if (is_fpr) + { + mechanism_string = ""; + rc = GPG_ERR_NO_PUBKEY; + } + else + { + mechanism_string = "LDAP"; + glo_ctrl.in_auto_key_retrieve++; + rc = keyserver_import_ldap (ctrl, name, &fpr, &fpr_len); + glo_ctrl.in_auto_key_retrieve--; + } + break; + + case AKL_NTDS: + mechanism_string = "NTDS"; + glo_ctrl.in_auto_key_retrieve++; + if (is_fpr) + rc = keyserver_import_fprint_ntds (ctrl, + fprbuf.u.fpr, fprbuf_fprlen); + else + rc = keyserver_import_ntds (ctrl, name, &fpr, &fpr_len); + glo_ctrl.in_auto_key_retrieve--; + 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 (keyserver_any_configured (ctrl)) + { + mechanism_string = "keyserver"; + glo_ctrl.in_auto_key_retrieve++; + if (is_fpr) + { + rc = keyserver_import_fprint (ctrl, + fprbuf.u.fpr, fprbuf_fprlen, + opt.keyserver, + KEYSERVER_IMPORT_FLAG_LDAP); + /* Map error codes because Dirmngr returns NO + * DATA if the keyserver does not have the + * requested key. It returns NO KEYSERVER if no + * LDAP keyservers are configured. */ + if (gpg_err_code (rc) == GPG_ERR_NO_DATA + || gpg_err_code (rc) == GPG_ERR_NO_KEYSERVER) + rc = gpg_error (GPG_ERR_NO_PUBKEY); + } + else + { + rc = keyserver_import_mbox (ctrl, name, &fpr, &fpr_len, + opt.keyserver); + } + glo_ctrl.in_auto_key_retrieve--; + } + else + { + mechanism_string = "Unconfigured keyserver"; + rc = GPG_ERR_NO_PUBKEY; + } + break; + + case AKL_SPEC: + { + struct keyserver_spec *keyserver; + + mechanism_string = akl->spec->uri; + keyserver = keyserver_match (akl->spec); + glo_ctrl.in_auto_key_retrieve++; + if (is_fpr) + { + rc = keyserver_import_fprint (ctrl, + fprbuf.u.fpr, fprbuf_fprlen, + opt.keyserver, + KEYSERVER_IMPORT_FLAG_LDAP); + if (gpg_err_code (rc) == GPG_ERR_NO_DATA + || gpg_err_code (rc) == GPG_ERR_NO_KEYSERVER) + rc = gpg_error (GPG_ERR_NO_PUBKEY); + } + else + { + rc = keyserver_import_mbox (ctrl, name, + &fpr, &fpr_len, keyserver); + } + glo_ctrl.in_auto_key_retrieve--; + } + 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 && (fpr || is_fpr)) + { + char fpr_string[MAX_FINGERPRINT_LEN * 2 + 1]; + + if (is_fpr) + { + log_assert (fprbuf_fprlen <= MAX_FINGERPRINT_LEN); + bin2hex (fprbuf.u.fpr, fprbuf_fprlen, fpr_string); + } + else + { + log_assert (fpr_len <= MAX_FINGERPRINT_LEN); + bin2hex (fpr, fpr_len, fpr_string); + } + + if (opt.verbose) + log_info ("auto-key-locate found fingerprint %s\n", + fpr_string); + + free_strlist (namelist); + namelist = NULL; + add_to_strlist (&namelist, fpr_string); + } + else if (!rc && !fpr && !did_akl_local) + { /* The acquisition method said no failure occurred, but + * it didn't return a fingerprint. That's a failure. */ + no_fingerprint = 1; + rc = GPG_ERR_NO_PUBKEY; + } + xfree (fpr); + fpr = NULL; + + if (!rc && !did_akl_local) + { /* There was no error and we didn't do a local lookup. + * This means that we imported a key into the local + * keyring. Try to read the imported key from the + * keyring. */ + if (retctx) + { + getkey_end (ctrl, *retctx); + *retctx = NULL; + } + rc = key_byname (ctrl, anylocalfirst ? retctx : NULL, + namelist, pk, 0, + include_unusable, ret_keyblock, ret_kdbhd); + } + if (!rc) + { + /* Key found. */ + if (opt.verbose) + log_info (_("automatically retrieved '%s' via %s\n"), + name, mechanism_string); + break; + } + if ((gpg_err_code (rc) != GPG_ERR_NO_PUBKEY + || opt.verbose || no_fingerprint) && *mechanism_string) + log_info (_("error retrieving '%s' via %s: %s\n"), + name, mechanism_string, + no_fingerprint ? _("No fingerprint") : gpg_strerror (rc)); + } + } + + if (rc && retctx) + { + getkey_end (ctrl, *retctx); + *retctx = NULL; + } + + if (retctx && *retctx) + { + log_assert (!(*retctx)->extra_list); + (*retctx)->extra_list = namelist; + (*retctx)->found_via_akl = mechanism_type; + } + else + free_strlist (namelist); + + return rc; +} + + + + +/* Comparison machinery for get_best_pubkey_byname. */ + +/* First we have a struct to cache computed information about the key + * in question. */ +struct pubkey_cmp_cookie +{ + int valid; /* Is this cookie valid? */ + PKT_public_key key; /* The key. */ + PKT_user_id *uid; /* The matching UID packet. */ + unsigned int validity; /* Computed validity of (KEY, UID). */ + u32 creation_time; /* Creation time of the newest subkey + capable of encryption. */ +}; + + +/* Then we have a series of helper functions. */ +static int +key_is_ok (const PKT_public_key *key) +{ + return (! key->has_expired && ! key->flags.revoked + && key->flags.valid && ! key->flags.disabled); +} + + +static int +uid_is_ok (const PKT_public_key *key, const PKT_user_id *uid) +{ + return key_is_ok (key) && ! uid->flags.revoked; +} + + +static int +subkey_is_ok (const PKT_public_key *sub) +{ + return ! sub->flags.revoked && sub->flags.valid && ! sub->flags.disabled; +} + +/* Return true if KEYBLOCK has only expired encryption subkyes. Note + * that the function returns false if the key has no encryption + * subkeys at all or the subkeys are revoked. */ +static int +only_expired_enc_subkeys (kbnode_t keyblock) +{ + kbnode_t node; + PKT_public_key *sub; + int any = 0; + + for (node = find_next_kbnode (keyblock, PKT_PUBLIC_SUBKEY); + node; node = find_next_kbnode (node, PKT_PUBLIC_SUBKEY)) + { + sub = node->pkt->pkt.public_key; + + if (!(sub->pubkey_usage & PUBKEY_USAGE_ENC)) + continue; + + if (!subkey_is_ok (sub)) + continue; + + any = 1; + if (!sub->has_expired) + return 0; + } + + return any? 1 : 0; +} + +/* Finally this function compares a NEW key to the former candidate + * OLD. Returns < 0 if the old key is worse, > 0 if the old key is + * better, == 0 if it is a tie. */ +static int +pubkey_cmp (ctrl_t ctrl, const char *name, struct pubkey_cmp_cookie *old, + struct pubkey_cmp_cookie *new, KBNODE new_keyblock) +{ + kbnode_t n; + + new->creation_time = 0; + for (n = find_next_kbnode (new_keyblock, PKT_PUBLIC_SUBKEY); + n; n = find_next_kbnode (n, PKT_PUBLIC_SUBKEY)) + { + PKT_public_key *sub = n->pkt->pkt.public_key; + + if ((sub->pubkey_usage & PUBKEY_USAGE_ENC) == 0) + continue; + + if (! subkey_is_ok (sub)) + continue; + + if (sub->timestamp > new->creation_time) + new->creation_time = sub->timestamp; + } + + for (n = find_next_kbnode (new_keyblock, PKT_USER_ID); + n; n = find_next_kbnode (n, PKT_USER_ID)) + { + PKT_user_id *uid = n->pkt->pkt.user_id; + char *mbox = mailbox_from_userid (uid->name); + int match = mbox ? strcasecmp (name, mbox) == 0 : 0; + + xfree (mbox); + if (! match) + continue; + + new->uid = scopy_user_id (uid); + new->validity = + get_validity (ctrl, new_keyblock, &new->key, uid, NULL, 0) & TRUST_MASK; + new->valid = 1; + + if (! old->valid) + return -1; /* No OLD key. */ + + if (! uid_is_ok (&old->key, old->uid) && uid_is_ok (&new->key, uid)) + return -1; /* Validity of the NEW key is better. */ + + if (old->validity < new->validity) + return -1; /* Validity of the NEW key is better. */ + + if (old->validity == new->validity && uid_is_ok (&new->key, uid) + && old->creation_time < new->creation_time) + return -1; /* Both keys are of the same validity, but the + NEW key is newer. */ + } + + /* Stick with the OLD key. */ + return 1; +} + + +/* This function works like get_pubkey_byname, but if the name + * resembles a mail address, the results are ranked and only the best + * result is returned. */ +gpg_error_t +get_best_pubkey_byname (ctrl_t ctrl, enum get_pubkey_modes mode, + GETKEY_CTX *retctx, PKT_public_key *pk, + const char *name, KBNODE *ret_keyblock, + int include_unusable) +{ + gpg_error_t err; + struct getkey_ctx_s *ctx = NULL; + int is_mbox; + int wkd_tried = 0; + + if (retctx) + *retctx = NULL; + + is_mbox = is_valid_mailbox (name); + if (!is_mbox && *name == '<' && name[1] && name[strlen(name)-1]=='>' + && name[1] != '>' + && is_valid_mailbox_mem (name+1, strlen (name)-2)) + { + /* The mailbox is in the form "" which is not + * detected by is_valid_mailbox. Set the flag but keep name as + * it is because get_pubkey_byname does an is_valid_mailbox_mem + * itself. */ + is_mbox = 1; + } + + start_over: + if (ctx) /* Clear in case of a start over. */ + { + if (ret_keyblock) + { + release_kbnode (*ret_keyblock); + *ret_keyblock = NULL; + } + getkey_end (ctrl, ctx); + ctx = NULL; + } + err = get_pubkey_byname (ctrl, mode, + &ctx, pk, name, ret_keyblock, + NULL, include_unusable); + if (err) + { + goto leave; + } + + /* If the keyblock was retrieved from the local database and the key + * has expired, do further checks. However, we can do this only if + * the caller requested a keyblock. */ + if (is_mbox && ctx && ctx->found_via_akl == AKL_LOCAL && ret_keyblock) + { + u32 now = make_timestamp (); + PKT_public_key *pk2 = (*ret_keyblock)->pkt->pkt.public_key; + int found; + + /* If the key has expired and its origin was the WKD then try to + * get a fresh key from the WKD. We also try this if the key + * has any only expired encryption subkeys. In case we checked + * for a fresh copy in the last 3 hours we won't do that again. + * Unfortunately that does not yet work because KEYUPDATE is + * only updated during import iff the key has actually changed + * (see import.c:import_one). */ + if (!wkd_tried && pk2->keyorg == KEYORG_WKD + && (pk2->keyupdate + 3*3600) < now + && (pk2->has_expired || only_expired_enc_subkeys (*ret_keyblock))) + { + if (opt.verbose) + log_info (_("checking for a fresh copy of an expired key via %s\n"), + "WKD"); + wkd_tried = 1; + glo_ctrl.in_auto_key_retrieve++; + found = !keyserver_import_wkd (ctrl, name, 0, NULL, NULL); + glo_ctrl.in_auto_key_retrieve--; + if (found) + goto start_over; + } + } + + if (is_mbox && ctx) + { + /* Rank results and return only the most relevant key. */ + struct pubkey_cmp_cookie best = { 0 }; + struct pubkey_cmp_cookie new = { 0 }; + kbnode_t new_keyblock; + + while (getkey_next (ctrl, ctx, &new.key, &new_keyblock) == 0) + { + int diff = pubkey_cmp (ctrl, name, &best, &new, new_keyblock); + release_kbnode (new_keyblock); + if (diff < 0) + { + /* New key is better. */ + release_public_key_parts (&best.key); + free_user_id (best.uid); + best = new; + } + else if (diff > 0) + { + /* Old key is better. */ + release_public_key_parts (&new.key); + free_user_id (new.uid); + } + else + { + /* A tie. Keep the old key. */ + release_public_key_parts (&new.key); + free_user_id (new.uid); + } + new.uid = NULL; + } + getkey_end (ctrl, ctx); + ctx = NULL; + free_user_id (best.uid); + best.uid = NULL; + + if (best.valid) + { + if (retctx || ret_keyblock) + { + ctx = xtrycalloc (1, sizeof **retctx); + if (! ctx) + err = gpg_error_from_syserror (); + else + { + ctx->kr_handle = keydb_new (); + if (! ctx->kr_handle) + { + err = gpg_error_from_syserror (); + xfree (ctx); + ctx = NULL; + if (retctx) + *retctx = NULL; + } + else + { + u32 *keyid = pk_keyid (&best.key); + ctx->exact = 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]; + + if (ret_keyblock) + { + release_kbnode (*ret_keyblock); + *ret_keyblock = NULL; + err = getkey_next (ctrl, ctx, NULL, ret_keyblock); + } + } + } + } + + if (pk) + { + release_public_key_parts (pk); + *pk = best.key; + } + else + release_public_key_parts (&best.key); + } + } + + if (err && ctx) + { + getkey_end (ctrl, ctx); + ctx = NULL; + } + + if (retctx && ctx) + { + *retctx = ctx; + ctx = NULL; + } + + leave: + getkey_end (ctrl, ctx); + return err; +} + + + +/* Get a public key from a file. + * + * PK is the buffer to store the key. The caller needs to make sure + * that PK->REQ_USAGE is valid. PK->REQ_USAGE is passed through to + * the lookup function and is a mask of PUBKEY_USAGE_SIG, + * PUBKEY_USAGE_ENC and PUBKEY_USAGE_CERT. If this is non-zero, only + * keys with the specified usage will be returned. + * + * FNAME is the file name. That file should contain exactly one + * keyblock. + * + * This function returns 0 on success. Otherwise, an error code is + * returned. In particular, GPG_ERR_NO_PUBKEY is returned if the key + * is not found. + * + * The self-signed data has already been merged into the public key + * using merge_selfsigs. The caller must release the content of PK by + * calling release_public_key_parts (or, if PK was malloced, using + * free_public_key). + */ +gpg_error_t +get_pubkey_fromfile (ctrl_t ctrl, PKT_public_key *pk, const char *fname) +{ + gpg_error_t err; + kbnode_t keyblock; + kbnode_t found_key; + unsigned int infoflags; + + err = read_key_from_file_or_buffer (ctrl, fname, NULL, 0, &keyblock); + if (!err) + { + /* Warning: node flag bits 0 and 1 should be preserved by + * merge_selfsigs. FIXME: Check whether this still holds. */ + merge_selfsigs (ctrl, keyblock); + found_key = finish_lookup (keyblock, pk->req_usage, 0, 0, &infoflags); + print_status_key_considered (keyblock, infoflags); + if (found_key) + pk_from_block (pk, keyblock, found_key); + else + err = gpg_error (GPG_ERR_UNUSABLE_PUBKEY); + } + + release_kbnode (keyblock); + return err; +} + + +/* Return a public key from the buffer (BUFFER, BUFLEN). The key is + * onlyretruned if it matches the keyid given in WANT_KEYID. On + * success the key is stored at the caller provided PKBUF structure. + * The caller must release the content of PK by calling + * release_public_key_parts (or, if PKBUF was malloced, using + * free_public_key). If R_KEYBLOCK is not NULL the full keyblock is + * also stored there. */ +gpg_error_t +get_pubkey_from_buffer (ctrl_t ctrl, PKT_public_key *pkbuf, + const void *buffer, size_t buflen, u32 *want_keyid, + kbnode_t *r_keyblock) +{ + gpg_error_t err; + kbnode_t keyblock; + kbnode_t node; + PKT_public_key *pk; + + if (r_keyblock) + *r_keyblock = NULL; + + err = read_key_from_file_or_buffer (ctrl, NULL, buffer, buflen, &keyblock); + if (!err) + { + merge_selfsigs (ctrl, keyblock); + for (node = keyblock; node; node = node->next) + { + if (node->pkt->pkttype == PKT_PUBLIC_KEY + || node->pkt->pkttype == PKT_PUBLIC_SUBKEY) + { + pk = node->pkt->pkt.public_key; + keyid_from_pk (pk, NULL); + if (pk->keyid[0] == want_keyid[0] + && pk->keyid[1] == want_keyid[1]) + break; + } + } + if (node) + copy_public_key (pkbuf, pk); + else + err = gpg_error (GPG_ERR_NO_PUBKEY); + } + + if (!err && r_keyblock) + *r_keyblock = keyblock; + else + release_kbnode (keyblock); + return err; +} + + +/* Lookup a key with the specified fingerprint. + * + * If PK is not NULL, the public key of the first result is returned + * in *PK. Note: this function does an exact search and thus the + * returned public key may be a subkey rather than the primary key. + * Note: The self-signed data has already been merged into the public + * key using merge_selfsigs. Free *PK by calling + * release_public_key_parts (or, if PK was allocated using xfree, you + * can use free_public_key, which calls release_public_key_parts(PK) + * and then xfree(PK)). + * + * If PK->REQ_USAGE is set, it is used to filter the search results. + * (Thus, if PK is not NULL, PK->REQ_USAGE must be valid!!!) See the + * documentation for finish_lookup to understand exactly how this is + * used. + * + * If R_KEYBLOCK is not NULL, then the first result's keyblock is + * returned in *R_KEYBLOCK. This should be freed using + * release_kbnode(). + * + * FPRINT is a byte array whose contents is the fingerprint to use as + * the search term. FPRINT_LEN specifies the length of the + * fingerprint (in bytes). Currently, only 16 and 20-byte + * fingerprints are supported. + * + * FIXME: We should replace this with the _byname function. This can + * be done by creating a userID conforming to the unified fingerprint + * style. */ +int +get_pubkey_byfprint (ctrl_t ctrl, PKT_public_key *pk, kbnode_t *r_keyblock, + const byte * fprint, size_t fprint_len) +{ + int rc; + + if (r_keyblock) + *r_keyblock = NULL; + + if (fprint_len == 20 || fprint_len == 16) + { + struct getkey_ctx_s ctx; + KBNODE kb = NULL; + KBNODE found_key = NULL; + + memset (&ctx, 0, sizeof ctx); + ctx.exact = 1; + ctx.not_allocated = 1; + /* FIXME: We should get the handle from the cache like we do in + * get_pubkey. */ + ctx.kr_handle = keydb_new (); + if (!ctx.kr_handle) + return gpg_error_from_syserror (); + + 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); + if (pk) + ctx.req_usage = pk->req_usage; + rc = lookup (ctrl, &ctx, 0, &kb, &found_key); + if (!rc && pk) + pk_from_block (pk, kb, found_key); + if (!rc && r_keyblock) + { + *r_keyblock = kb; + kb = NULL; + } + release_kbnode (kb); + getkey_end (ctrl, &ctx); + } + else + rc = GPG_ERR_GENERAL; /* Oops */ + return rc; +} + + +/* This function is similar to get_pubkey_byfprint, but it doesn't + * merge the self-signed data into the public key and subkeys or into + * the user ids. It also doesn't add the key to the user id cache. + * Further, this function ignores PK->REQ_USAGE. + * + * This function is intended to avoid recursion and, as such, should + * only be used in very specific situations. + * + * Like get_pubkey_byfprint, PK may be NULL. In that case, this + * function effectively just checks for the existence of the key. */ +gpg_error_t +get_pubkey_byfprint_fast (PKT_public_key * pk, + const byte * fprint, size_t fprint_len) +{ + gpg_error_t err; + KBNODE keyblock; + + err = get_keyblock_byfprint_fast (&keyblock, NULL, fprint, fprint_len, 0); + if (!err) + { + if (pk) + copy_public_key (pk, keyblock->pkt->pkt.public_key); + release_kbnode (keyblock); + } + + return err; +} + + +/* This function is similar to get_pubkey_byfprint_fast but returns a + * keydb handle at R_HD and the keyblock at R_KEYBLOCK. R_KEYBLOCK or + * R_HD may be NULL. If LOCK is set the handle has been opend in + * locked mode and keydb_disable_caching () has been called. On error + * R_KEYBLOCK is set to NULL but R_HD must be released by the caller; + * it may have a value of NULL, though. This allows to do an insert + * operation on a locked keydb handle. */ +gpg_error_t +get_keyblock_byfprint_fast (kbnode_t *r_keyblock, KEYDB_HANDLE *r_hd, + const byte *fprint, size_t fprint_len, int lock) +{ + gpg_error_t err; + KEYDB_HANDLE hd; + kbnode_t keyblock; + byte fprbuf[MAX_FINGERPRINT_LEN]; + int i; + + if (r_keyblock) + *r_keyblock = NULL; + if (r_hd) + *r_hd = NULL; + + 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 (); + if (!hd) + return gpg_error_from_syserror (); + + if (lock) + { + err = keydb_lock (hd); + if (err) + { + /* If locking did not work, we better don't return a handle + * at all - there was a reason that locking has been + * requested. */ + keydb_release (hd); + return err; + } + keydb_disable_caching (hd); + } + + /* Fo all other errors we return the handle. */ + if (r_hd) + *r_hd = hd; + + err = keydb_search_fpr (hd, fprbuf); + if (gpg_err_code (err) == GPG_ERR_NOT_FOUND) + { + if (!r_hd) + keydb_release (hd); + return gpg_error (GPG_ERR_NO_PUBKEY); + } + err = keydb_get_keyblock (hd, &keyblock); + if (err) + { + log_error ("keydb_get_keyblock failed: %s\n", gpg_strerror (err)); + if (!r_hd) + keydb_release (hd); + return gpg_error (GPG_ERR_NO_PUBKEY); + } + + log_assert (keyblock->pkt->pkttype == PKT_PUBLIC_KEY + || keyblock->pkt->pkttype == PKT_PUBLIC_SUBKEY); + + /* Not caching key here since it won't have all of the fields + properly set. */ + + if (r_keyblock) + *r_keyblock = keyblock; + else + release_kbnode (keyblock); + + if (!r_hd) + keydb_release (hd); + + return 0; +} + + +const char * +parse_def_secret_key (ctrl_t ctrl) +{ + KEYDB_HANDLE hd = NULL; + strlist_t t; + static int warned; + + for (t = opt.def_secret_key; t; t = t->next) + { + gpg_error_t err; + KEYDB_SEARCH_DESC desc; + KBNODE kb; + KBNODE node; + + err = classify_user_id (t->d, &desc, 1); + if (err) + { + log_error (_("secret key \"%s\" not found: %s\n"), + t->d, gpg_strerror (err)); + if (!opt.quiet) + log_info (_("(check argument of option '%s')\n"), "--default-key"); + continue; + } + + if (! hd) + { + hd = keydb_new (); + if (!hd) + return NULL; + } + else + keydb_search_reset (hd); + + + err = keydb_search (hd, &desc, 1, NULL); + if (gpg_err_code (err) == GPG_ERR_NOT_FOUND) + continue; + + if (err) + { + log_error (_("key \"%s\" not found: %s\n"), t->d, gpg_strerror (err)); + t = NULL; + break; + } + + err = keydb_get_keyblock (hd, &kb); + if (err) + { + log_error (_("error reading keyblock: %s\n"), + gpg_strerror (err)); + continue; + } + + merge_selfsigs (ctrl, kb); + + err = gpg_error (GPG_ERR_NO_SECKEY); + node = kb; + do + { + PKT_public_key *pk = node->pkt->pkt.public_key; + + /* Check if the key is valid. */ + if (pk->flags.revoked) + { + if (DBG_LOOKUP) + log_debug ("not using %s as default key, %s", + keystr_from_pk (pk), "revoked"); + continue; + } + if (pk->has_expired) + { + if (DBG_LOOKUP) + log_debug ("not using %s as default key, %s", + keystr_from_pk (pk), "expired"); + continue; + } + if (pk_is_disabled (pk)) + { + if (DBG_LOOKUP) + log_debug ("not using %s as default key, %s", + keystr_from_pk (pk), "disabled"); + continue; + } + + err = agent_probe_secret_key (ctrl, pk); + if (! err) + /* This is a valid key. */ + break; + } + while ((node = find_next_kbnode (node, PKT_PUBLIC_SUBKEY))); + + release_kbnode (kb); + if (err) + { + if (! warned && ! opt.quiet) + { + log_info (_("Warning: not using '%s' as default key: %s\n"), + t->d, gpg_strerror (GPG_ERR_NO_SECKEY)); + print_reported_error (err, GPG_ERR_NO_SECKEY); + } + } + else + { + if (! warned && ! opt.quiet) + log_info (_("using \"%s\" as default secret key for signing\n"), + t->d); + break; + } + } + + if (! warned && opt.def_secret_key && ! t) + log_info (_("all values passed to '%s' ignored\n"), + "--default-key"); + + warned = 1; + + if (hd) + keydb_release (hd); + + if (t) + return t->d; + return NULL; +} + + +/* Look up a secret key. + * + * If PK is not NULL, the public key of the first result is returned + * in *PK. Note: PK->REQ_USAGE must be valid!!! If PK->REQ_USAGE is + * set, it is used to filter the search results. See the + * documentation for finish_lookup to understand exactly how this is + * used. Note: The self-signed data has already been merged into the + * public key using merge_selfsigs. Free *PK by calling + * release_public_key_parts (or, if PK was allocated using xfree, you + * can use free_public_key, which calls release_public_key_parts(PK) + * and then xfree(PK)). + * + * If --default-key was set, then the specified key is looked up. (In + * this case, the default key is returned even if it is considered + * unusable. See the documentation for skip_unusable for exactly what + * this means.) + * + * Otherwise, this initiates a DB scan that returns all keys that are + * usable (see previous paragraph for exactly what usable means) and + * for which a secret key is available. + * + * This function returns the first match. Additional results can be + * returned using getkey_next. */ +gpg_error_t +get_seckey_default (ctrl_t ctrl, PKT_public_key *pk) +{ + gpg_error_t err; + strlist_t namelist = NULL; + int include_unusable = 1; + + + const char *def_secret_key = parse_def_secret_key (ctrl); + if (def_secret_key) + add_to_strlist (&namelist, def_secret_key); + else + include_unusable = 0; + + err = key_byname (ctrl, NULL, namelist, pk, 1, include_unusable, NULL, NULL); + + free_strlist (namelist); + + return err; +} + + + +/* Search for keys matching some criteria. + * + * If RETCTX is not NULL, then the constructed context is returned in + * *RETCTX so that getpubkey_next can be used to get subsequent + * results. In this case, getkey_end() must be used to free the + * search context. If RETCTX is not NULL, then RET_KDBHD must be + * NULL. + * + * If PK is not NULL, the public key of the first result is returned + * in *PK. Note: PK->REQ_USAGE must be valid!!! If PK->REQ_USAGE is + * set, it is used to filter the search results. See the + * documentation for finish_lookup to understand exactly how this is + * used. Note: The self-signed data has already been merged into the + * public key using merge_selfsigs. Free *PK by calling + * release_public_key_parts (or, if PK was allocated using xfree, you + * can use free_public_key, which calls release_public_key_parts(PK) + * and then xfree(PK)). + * + * If NAMES is not NULL, then a search query is constructed using + * classify_user_id on each of the strings in the list. (Recall: the + * database does an OR of the terms, not an AND.) If NAMES is + * NULL, then all results are returned. + * + * If WANT_SECRET is set, then only keys with an available secret key + * (either locally or via key registered on a smartcard) are returned. + * + * This function does not skip unusable keys (see the documentation + * for skip_unusable for an exact definition). + * + * If RET_KEYBLOCK is not NULL, the keyblock is returned in + * *RET_KEYBLOCK. This should be freed using release_kbnode(). + * + * This function returns 0 on success. Otherwise, an error code is + * returned. In particular, GPG_ERR_NO_PUBKEY or GPG_ERR_NO_SECKEY + * (if want_secret is set) is returned if the key is not found. */ +gpg_error_t +getkey_bynames (ctrl_t ctrl, getkey_ctx_t *retctx, PKT_public_key *pk, + strlist_t names, int want_secret, kbnode_t *ret_keyblock) +{ + return key_byname (ctrl, retctx, names, pk, want_secret, 1, + ret_keyblock, NULL); +} + + +/* Search for one key matching some criteria. + * + * If RETCTX is not NULL, then the constructed context is returned in + * *RETCTX so that getpubkey_next can be used to get subsequent + * results. In this case, getkey_end() must be used to free the + * search context. If RETCTX is not NULL, then RET_KDBHD must be + * NULL. + * + * If PK is not NULL, the public key of the first result is returned + * in *PK. Note: PK->REQ_USAGE must be valid!!! If PK->REQ_USAGE is + * set, it is used to filter the search results. See the + * documentation for finish_lookup to understand exactly how this is + * used. Note: The self-signed data has already been merged into the + * public key using merge_selfsigs. Free *PK by calling + * release_public_key_parts (or, if PK was allocated using xfree, you + * can use free_public_key, which calls release_public_key_parts(PK) + * and then xfree(PK)). + * + * If NAME is not NULL, then a search query is constructed using + * classify_user_id on the string. In this case, even unusable keys + * (see the documentation for skip_unusable for an exact definition of + * unusable) are returned. Otherwise, if --default-key was set, then + * that key is returned (even if it is unusable). If neither of these + * conditions holds, then the first usable key is returned. + * + * If WANT_SECRET is set, then only keys with an available secret key + * (either locally or via key registered on a smartcard) are returned. + * + * This function does not skip unusable keys (see the documentation + * for skip_unusable for an exact definition). + * + * If RET_KEYBLOCK is not NULL, the keyblock is returned in + * *RET_KEYBLOCK. This should be freed using release_kbnode(). + * + * This function returns 0 on success. Otherwise, an error code is + * returned. In particular, GPG_ERR_NO_PUBKEY or GPG_ERR_NO_SECKEY + * (if want_secret is set) is returned if the key is not found. + * + * FIXME: We also have the get_pubkey_byname function which has a + * different semantic. Should be merged with this one. */ +gpg_error_t +getkey_byname (ctrl_t ctrl, getkey_ctx_t *retctx, PKT_public_key *pk, + const char *name, int want_secret, kbnode_t *ret_keyblock) +{ + gpg_error_t err; + strlist_t namelist = NULL; + int with_unusable = 1; + const char *def_secret_key = NULL; + + if (want_secret && !name) + def_secret_key = parse_def_secret_key (ctrl); + + if (want_secret && !name && def_secret_key) + add_to_strlist (&namelist, def_secret_key); + else if (name) + add_to_strlist (&namelist, name); + else + with_unusable = 0; + + err = key_byname (ctrl, retctx, namelist, pk, want_secret, with_unusable, + ret_keyblock, NULL); + + /* FIXME: Check that we really return GPG_ERR_NO_SECKEY if + WANT_SECRET has been used. */ + + free_strlist (namelist); + + return err; +} + + +/* Return the next search result. + * + * If PK is not NULL, the public key of the next result is returned in + * *PK. Note: The self-signed data has already been merged into the + * public key using merge_selfsigs. Free *PK by calling + * release_public_key_parts (or, if PK was allocated using xmalloc, you + * can use free_public_key, which calls release_public_key_parts(PK) + * and then xfree(PK)). + * + * RET_KEYBLOCK can be given as NULL; if it is not NULL it the entire + * found keyblock is returned which must be released with + * release_kbnode. If the function returns an error NULL is stored at + * RET_KEYBLOCK. + * + * The self-signed data has already been merged into the public key + * using merge_selfsigs. */ +gpg_error_t +getkey_next (ctrl_t ctrl, getkey_ctx_t ctx, + PKT_public_key *pk, kbnode_t *ret_keyblock) +{ + int rc; /* Fixme: Make sure this is proper gpg_error */ + KBNODE keyblock = NULL; + KBNODE found_key = NULL; + + /* We need to disable the caching so that for an exact key search we + won't get the result back from the cache and thus end up in an + endless loop. The endless loop can occur, because the cache is + used without respecting the current file pointer! */ + keydb_disable_caching (ctx->kr_handle); + + /* FOUND_KEY is only valid as long as RET_KEYBLOCK is. If the + * caller wants PK, but not RET_KEYBLOCK, we need hand in our own + * keyblock. */ + if (pk && ret_keyblock == NULL) + ret_keyblock = &keyblock; + + rc = lookup (ctrl, ctx, ctx->want_secret, + ret_keyblock, pk ? &found_key : NULL); + if (!rc && pk) + { + log_assert (found_key); + pk_from_block (pk, NULL, found_key); + release_kbnode (keyblock); + } + + return rc; +} + + +/* Release any resources used by a key listing context. This must be + * called on the context returned by, e.g., getkey_byname. */ +void +getkey_end (ctrl_t ctrl, getkey_ctx_t ctx) +{ + if (ctx) + { +#ifdef HAVE_W32_SYSTEM + + /* FIXME: This creates a big regression for Windows because the + * keyring is only released after the global ctrl is released. + * So if an operation does a getkey and then tries to modify the + * keyring it will fail on Windows with a sharing violation. We + * need to modify all keyring write operations to also take the + * ctrl and close the cached_getkey_kdb handle to make writing + * work. See: GnuPG-bug-id: 3097 */ + (void)ctrl; + keydb_release (ctx->kr_handle); + +#else /*!HAVE_W32_SYSTEM*/ + + if (ctrl && !ctrl->cached_getkey_kdb) + ctrl->cached_getkey_kdb = ctx->kr_handle; + else + keydb_release (ctx->kr_handle); + +#endif /*!HAVE_W32_SYSTEM*/ + + free_strlist (ctx->extra_list); + if (!ctx->not_allocated) + xfree (ctx); + } +} + + + +/************************************************ + ************* Merging stuff ******************** + ************************************************/ + +/* Set the mainkey_id fields for all keys in KEYBLOCK. This is + * usually done by merge_selfsigs but at some places we only need the + * main_kid not a full merge. The function also guarantees that all + * pk->keyids are computed. */ +void +setup_main_keyids (kbnode_t keyblock) +{ + u32 kid[2], mainkid[2]; + kbnode_t kbctx, node; + PKT_public_key *pk; + + if (keyblock->pkt->pkttype != PKT_PUBLIC_KEY) + BUG (); + pk = keyblock->pkt->pkt.public_key; + + keyid_from_pk (pk, mainkid); + for (kbctx=NULL; (node = walk_kbnode (keyblock, &kbctx, 0)); ) + { + if (!(node->pkt->pkttype == PKT_PUBLIC_KEY + || node->pkt->pkttype == PKT_PUBLIC_SUBKEY)) + continue; + pk = node->pkt->pkt.public_key; + keyid_from_pk (pk, kid); /* Make sure pk->keyid is set. */ + if (!pk->main_keyid[0] && !pk->main_keyid[1]) + { + pk->main_keyid[0] = mainkid[0]; + pk->main_keyid[1] = mainkid[1]; + } + } +} + + +/* KEYBLOCK corresponds to a public key block. This function merges + * much of the information from the self-signed data into the public + * key, public subkey and user id data structures. If you use the + * high-level search API (e.g., get_pubkey) for looking up key blocks, + * then you don't need to call this function. This function is + * useful, however, if you change the keyblock, e.g., by adding or + * removing a self-signed data packet. */ +void +merge_keys_and_selfsig (ctrl_t ctrl, kbnode_t keyblock) +{ + if (!keyblock) + ; + else if (keyblock->pkt->pkttype == PKT_PUBLIC_KEY) + merge_selfsigs (ctrl, keyblock); + else + log_debug ("FIXME: merging secret key blocks is not anymore available\n"); +} + + +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; + + if (!key_usage) + key_usage |= PUBKEY_USAGE_NONE; + } + else if (p) /* Key flags of length zero. */ + key_usage |= PUBKEY_USAGE_NONE; + + /* 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. Likewise we use PUBKEY_USAGE_NONE to indicate that + key_flags have been given but they do not specify any usage. */ + + 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, *aead, *hash, *zip; + size_t n, nsym, naead, nhash, nzip; + + sig->flags.chosen_selfsig = 1;/* We chose this one. */ + uid->created = 0; /* Not created == invalid. */ + if (IS_UID_REV (sig)) + { + uid->flags.revoked = 1; + return; /* Has been revoked. */ + } + else + uid->flags.revoked = 0; + + uid->expiredate = sig->expiredate; + + if (sig->flags.expired) + { + uid->flags.expired = 1; + return; /* Has expired. */ + } + else + uid->flags.expired = 0; + + uid->created = sig->timestamp; /* This one is okay. */ + uid->selfsigversion = sig->version; + /* If we got this far, it's not expired :) */ + uid->flags.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 && buf32_to_u32 (p)) + uid->help_key_expire = keycreated + buf32_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->flags.primary = 0; + p = parse_sig_subpkt (sig->hashed, SIGSUBPKT_PRIMARY_UID, NULL); + if (p && *p) + uid->flags.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_AEAD, &n); + aead = p; + naead = 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 + naead + 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 (; naead; naead--, n++) + { + uid->prefs[n].type = PREFTYPE_AEAD; + uid->prefs[n].value = *aead++; + } + 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; + + /* See whether we have the AEAD feature. */ + uid->flags.aead = 0; + p = parse_sig_subpkt (sig->hashed, SIGSUBPKT_FEATURES, &n); + if (p && n && (p[0] & 0x02)) + uid->flags.aead = 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]; +} + + +/* Given a keyblock, parse the key block and extract various pieces of + * information and save them with the primary key packet and the user + * id packets. For instance, some information is stored in signature + * packets. We find the latest such valid packet (since the user can + * change that information) and copy its contents into the + * PKT_public_key. + * + * Note that R_REVOKED may be set to 0, 1 or 2. + * + * This function fills in the following fields in the primary key's + * keyblock: + * + * main_keyid (computed) + * revkey / numrevkeys (derived from self signed key data) + * flags.valid (whether we have at least 1 self-sig) + * flags.maybe_revoked (whether a designed revoked the key, but + * we are missing the key to check the sig) + * selfsigversion (highest version of any valid self-sig) + * pubkey_usage (derived from most recent self-sig or most + * recent user id) + * has_expired (various sources) + * expiredate (various sources) + * + * See the documentation for fixup_uidnode for how the user id packets + * are modified. In addition to that the primary user id's is_primary + * field is set to 1 and the other user id's is_primary are set to 0. + */ +static void +merge_selfsigs_main (ctrl_t ctrl, kbnode_t 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; /* Creation time of the key. */ + u32 key_expire = 0; + int key_expire_seen = 0; + byte sigversion = 0; + + *r_revoked = 0; + memset (rinfo, 0, sizeof (*rinfo)); + + /* Section 11.1 of RFC 4880 determines the order of packets within a + * message. There are three sections, which must occur in the + * following order: the public key, the user ids and user attributes + * and the subkeys. Within each section, each primary packet (e.g., + * a user id packet) is followed by one or more signature packets, + * which modify that packet. */ + + /* According to Section 11.1 of RFC 4880, the public key must be the + first packet. Note that parse_keyblock_image ensures that the + first packet is the public key. */ + 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. We do not support v3 keys anymore but + * we keep the code in case a future key versions introduces a + * hadr expire time again. */ + 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. + * + * - Determine whether the key has been revoked. + * + * - Gather all revocation keys (unlike other data, we don't just + * take them from the latest self-signed packet). + * + * - Determine max (sig[...]->version). + */ + + /* Reset this in case this key was already merged. */ + xfree (pk->revkey); + pk->revkey = NULL; + pk->numrevkeys = 0; + + signode = NULL; + sigdate = 0; /* Helper variable to find the latest signature. */ + + /* According to Section 11.1 of RFC 4880, the public key comes first + * and is immediately followed by any signature packets that modify + * it. */ + for (k = keyblock; + k && k->pkt->pkttype != PKT_USER_ID + && k->pkt->pkttype != PKT_ATTRIBUTE + && k->pkt->pkttype != PKT_PUBLIC_SUBKEY; + 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]) + { /* Self sig. */ + + if (check_key_signature (ctrl, 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 the indicated revocations keys from all + * signatures not just the latest. We do this + * because 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) + { /* This is the latest signature so far. */ + + 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)); + } + + /* SIGNODE is the direct key signature packet (sigclass 0x1f) with + * the latest creation time. Extract some information from it. */ + 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 && buf32_to_u32 (p)) + { + key_expire = keytimestamp + buf32_to_u32 (p); + key_expire_seen = 1; + } + + /* Mark that key as valid: One direct key signature should + * render a key as valid. */ + pk->flags.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 (ctrl, 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 (gpg_err_code (rc) == GPG_ERR_NO_PUBKEY) + pk->flags.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. */ + + /* Fixme: 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. */ + + /* According to RFC 4880 section 11.1, user id and attribute packets + * are in the second section, after the public key packet and before + * the subkey packets. */ + signode = uidnode = NULL; + sigdate = 0; /* Helper variable to find the latest signature in one UID. */ + for (k = keyblock; k && k->pkt->pkttype != PKT_PUBLIC_SUBKEY; k = k->next) + { + if (k->pkt->pkttype == PKT_USER_ID || k->pkt->pkttype == PKT_ATTRIBUTE) + { /* New user id packet. */ + + /* Apply the data from the most recent self-signed packet to + * the preceding user id packet. */ + if (uidnode && signode) + { + fixup_uidnode (uidnode, signode, keytimestamp); + pk->flags.valid = 1; + } + + /* Clear SIGNODE. The only relevant self-signed data for + * UIDNODE follows it. */ + if (k->pkt->pkttype == PKT_USER_ID) + uidnode = k; + else + uidnode = NULL; + + 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 (ctrl, keyblock, k, NULL)) + ; /* signature did not verify */ + else if ((IS_UID_SIG (sig) || IS_UID_REV (sig)) + && sig->timestamp >= sigdate) + { + /* Note: we allow invalidation of cert revocations + * by a newer signature. An attacker can't use this + * because a key should be revoked 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->flags.valid = 1; + } + + /* If the key isn't valid yet, and we have + * --allow-non-selfsigned-uid set, then force it valid. */ + if (!pk->flags.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->flags.valid = 1; + } + + /* The key STILL isn't valid, so try and find an ultimately + * trusted signature. */ + if (!pk->flags.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 the user + * should also remove the ultimate trust flag. */ + if (get_pubkey_fast (ultimate_pk, sig->keyid) == 0 + && check_key_signature2 (ctrl, + keyblock, k, ultimate_pk, + NULL, NULL, NULL, NULL) == 0 + && get_ownertrust (ctrl, ultimate_pk) == TRUST_ULTIMATE) + { + free_public_key (ultimate_pk); + pk->flags.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 || (!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. + * This may be a different one than from usage computation 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 || (!uid->created && !uiddate))) + { + key_expire = uid->help_key_expire; + uiddate = uid->created; + } + } + } + } + + /* Currently only the not anymore supported v3 keys have a maximum + * expiration date, but future key versions may 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->flags.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->flags.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->flags.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->flags.primary = 1; + continue; + } + else + { + if (cmp_user_ids (k->pkt->pkt.user_id, + uidnode->pkt->pkt.user_id) > 0) + { + uidnode->pkt->pkt.user_id->flags.primary = 0; + uidnode = k; + uidnode->pkt->pkt.user_id->flags.primary = 1; + } + else + { + /* just to be safe: */ + k->pkt->pkt.user_id->flags.primary = 0; + } + } + } + } + } +} + + +/* Convert a buffer to a signature. Useful for 0x19 embedded sigs. + * Caller must free the signature when they are done. */ +static PKT_signature * +buf_to_sig (const byte * buf, size_t len) +{ + PKT_signature *sig = xmalloc_clear (sizeof (PKT_signature)); + IOBUF iobuf = iobuf_temp_with_content (buf, len); + int save_mode = set_packet_list_mode (0); + + if (parse_signature (iobuf, PKT_SIGNATURE, len, sig) != 0) + { + free_seckey_enc (sig); + sig = NULL; + } + + set_packet_list_mode (save_mode); + iobuf_close (iobuf); + + return sig; +} + + +/* Use the self-signed data to fill in various fields in subkeys. + * + * KEYBLOCK is the whole keyblock. SUBNODE is the subkey to fill in. + * + * Sets the following fields on the subkey: + * + * main_keyid + * flags.valid if the subkey has a valid self-sig binding + * flags.revoked + * flags.backsig + * pubkey_usage + * has_expired + * expired_date + * + * On this subkey's most revent valid self-signed packet, the + * following field is set: + * + * flags.chosen_selfsig + */ +static void +merge_selfsigs_subkey (ctrl_t ctrl, kbnode_t keyblock, kbnode_t 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->flags.valid = 0; + subpk->flags.exact = 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 (ctrl, 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->flags.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 && buf32_to_u32 (p)) + key_expire = keytimestamp + buf32_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 (openpgp_pk_test_algo (subpk->pubkey_algo)) + return; + + subpk->flags.valid = 1; + + /* Find the most recent 0x19 embedded signature on our self-sig. */ + if (!subpk->flags.backsig) + { + int seq = 0; + size_t n; + PKT_signature *backsig = NULL; + + sigdate = 0; + + /* 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))) + { + PKT_signature *tempsig = buf_to_sig (p, n); + if (tempsig) + { + if (tempsig->timestamp > sigdate) + { + if (backsig) + free_seckey_enc (backsig); + + backsig = tempsig; + sigdate = backsig->timestamp; + } + else + free_seckey_enc (tempsig); + } + } + + 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))) + { + PKT_signature *tempsig = buf_to_sig (p, n); + if (tempsig) + { + if (tempsig->timestamp > sigdate) + { + if (backsig) + free_seckey_enc (backsig); + + backsig = tempsig; + sigdate = backsig->timestamp; + } + else + free_seckey_enc (tempsig); + } + } + + if (backsig) + { + /* At this point, backsig contains the most recent 0x19 sig. + * Let's see if it is good. */ + + /* 2==valid, 1==invalid, 0==didn't check */ + if (check_backsig (mainpk, subpk, backsig) == 0) + subpk->flags.backsig = 2; + else + subpk->flags.backsig = 1; + + free_seckey_enc (backsig); + } + } +} + + +/* Merge information from the self-signatures with the public key, + * subkeys and user ids to make using them more easy. + * + * See documentation for merge_selfsigs_main, merge_selfsigs_subkey + * and fixup_uidnode for exactly which fields are updated. */ +static void +merge_selfsigs (ctrl_t ctrl, kbnode_t keyblock) +{ + KBNODE k; + int revoked; + struct revoke_info rinfo; + PKT_public_key *main_pk; + prefitem_t *prefs; + unsigned int mdc_feature; + unsigned int aead_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 because 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 (ctrl, 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 (ctrl, keyblock, k); + } + } + + main_pk = keyblock->pkt->pkt.public_key; + if (revoked || main_pk->has_expired || !main_pk->flags.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->flags.valid) + pk->flags.valid = 0; + if (revoked && !pk->flags.revoked) + { + pk->flags.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 = aead_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->flags.primary) + { + prefs = k->pkt->pkt.user_id->prefs; + mdc_feature = k->pkt->pkt.user_id->flags.mdc; + aead_feature = k->pkt->pkt.user_id->flags.aead; + 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->flags.mdc = mdc_feature; + pk->flags.aead = aead_feature; + } + } +} + + + +/* See whether the key satisfies any additional requirements specified + * in CTX. If so, return the node of an appropriate key or subkey. + * Otherwise, return NULL if there was no appropriate key. + * + * Note that we do not return a reference, i.e. the result must not be + * freed using 'release_kbnode'. + * + * In case the primary key is not required, select a suitable subkey. + * We need the primary key if PUBKEY_USAGE_CERT is set in REQ_USAGE or + * we are in PGP6 or PGP7 mode and PUBKEY_USAGE_SIG is set in + * REQ_USAGE. + * + * If any of PUBKEY_USAGE_SIG, PUBKEY_USAGE_ENC and PUBKEY_USAGE_CERT + * are set in REQ_USAGE, we filter by the key's function. Concretely, + * if PUBKEY_USAGE_SIG and PUBKEY_USAGE_CERT are set, then we only + * return a key if it is (at least) either a signing or a + * certification key. + * + * If REQ_USAGE is set, then we reject any keys that are not good + * (i.e., valid, not revoked, not expired, etc.). This allows the + * getkey functions to be used for plain key listings. + * + * Sets the matched key's user id field (pk->user_id) to the user id + * that matched the low-level search criteria or NULL. + * + * If R_FLAGS is not NULL set certain flags for more detailed error + * reporting. Used flags are: + * + * - LOOKUP_ALL_SUBKEYS_EXPIRED :: All Subkeys are expired or have + * been revoked. + * - LOOKUP_NOT_SELECTED :: No suitable key found + * + * This function needs to handle several different cases: + * + * 1. No requested 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 + * FIXME + * 4. Usage but no primary key requested + * FIXME + * + */ +static kbnode_t +finish_lookup (kbnode_t keyblock, unsigned int req_usage, int want_exact, + int want_secret, unsigned int *r_flags) +{ + kbnode_t k; + + /* If WANT_EXACT is set, the key or subkey that actually matched the + low-level search criteria. */ + kbnode_t foundk = NULL; + /* The user id (if any) that matched the low-level search criteria. */ + PKT_user_id *foundu = NULL; + + u32 latest_date; + kbnode_t latest_key; + PKT_public_key *pk; + int req_prim; + u32 curtime = make_timestamp (); + + if (r_flags) + *r_flags = 0; + +#define USAGE_MASK (PUBKEY_USAGE_SIG|PUBKEY_USAGE_ENC|PUBKEY_USAGE_CERT) + 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. */ + req_prim = ((req_usage & PUBKEY_USAGE_CERT) + || ((PGP6 || PGP7) && (req_usage & PUBKEY_USAGE_SIG))); + + + log_assert (keyblock->pkt->pkttype == PKT_PUBLIC_KEY); + + /* For an exact match mark the primary or subkey that matched the + low-level search criteria. */ + if (want_exact) + { + for (k = keyblock; k; k = k->next) + { + if ((k->flag & 1)) + { + log_assert (k->pkt->pkttype == PKT_PUBLIC_KEY + || k->pkt->pkttype == PKT_PUBLIC_SUBKEY); + foundk = k; + pk = k->pkt->pkt.public_key; + pk->flags.exact = 1; + break; + } + } + } + + /* Get the user id that matched that low-level search criteria. */ + for (k = keyblock; k; k = k->next) + { + if ((k->flag & 2)) + { + log_assert (k->pkt->pkttype == PKT_USER_ID); + foundu = k->pkt->pkt.user_id; + break; + } + } + + if (DBG_LOOKUP) + 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; + } + + latest_date = 0; + latest_key = NULL; + /* Set LATEST_KEY to the latest (the one with the most recent + * timestamp) good (valid, not revoked, not expired, etc.) subkey. + * + * Don't bother if we are only looking for a primary key or we need + * an exact match and the exact match is not a subkey. */ + if (req_prim || (foundk && foundk->pkt->pkttype != PKT_PUBLIC_SUBKEY)) + ; + else + { + kbnode_t nextk; + int n_subkeys = 0; + int n_revoked_or_expired = 0; + + /* Either start a loop or check just this one subkey. */ + for (k = foundk ? foundk : keyblock; k; k = nextk) + { + if (foundk) + { + /* If FOUNDK is not NULL, then only consider that exact + key, i.e., don't iterate. */ + nextk = NULL; + } + else + nextk = k->next; + + if (k->pkt->pkttype != PKT_PUBLIC_SUBKEY) + continue; + + pk = k->pkt->pkt.public_key; + if (DBG_LOOKUP) + log_debug ("\tchecking subkey %08lX\n", + (ulong) keyid_from_pk (pk, NULL)); + + if (!pk->flags.valid) + { + if (DBG_LOOKUP) + log_debug ("\tsubkey not valid\n"); + continue; + } + if (!((pk->pubkey_usage & USAGE_MASK) & req_usage)) + { + if (DBG_LOOKUP) + log_debug ("\tusage does not match: want=%x have=%x\n", + req_usage, pk->pubkey_usage); + continue; + } + + n_subkeys++; + if (pk->flags.revoked) + { + if (DBG_LOOKUP) + log_debug ("\tsubkey has been revoked\n"); + n_revoked_or_expired++; + continue; + } + if (pk->has_expired) + { + if (DBG_LOOKUP) + log_debug ("\tsubkey has expired\n"); + n_revoked_or_expired++; + continue; + } + if (pk->timestamp > curtime && !opt.ignore_valid_from) + { + if (DBG_LOOKUP) + log_debug ("\tsubkey not yet valid\n"); + continue; + } + + if (want_secret && agent_probe_secret_key (NULL, pk)) + { + if (DBG_LOOKUP) + log_debug ("\tno secret key\n"); + continue; + } + + if (DBG_LOOKUP) + log_debug ("\tsubkey might be fine\n"); + /* In case a key has a timestamp of 0 set, we make sure + that it is used. A better change would be to compare + ">=" but that might also change the selected keys and + is as such a more intrusive change. */ + if (pk->timestamp > latest_date || (!pk->timestamp && !latest_date)) + { + latest_date = pk->timestamp; + latest_key = k; + } + } + if (n_subkeys == n_revoked_or_expired && r_flags) + *r_flags |= LOOKUP_ALL_SUBKEYS_EXPIRED; + } + + /* Check if the primary key is ok (valid, not revoke, not expire, + * matches requested usage) if: + * + * - we didn't find an appropriate subkey and we're not doing an + * exact search, + * + * - we're doing an exact match and the exact match was the + * primary key, or, + * + * - we're just considering the primary key. */ + if ((!latest_key && !want_exact) || foundk == keyblock || req_prim) + { + if (DBG_LOOKUP && !foundk && !req_prim) + log_debug ("\tno suitable subkeys found - trying primary\n"); + pk = keyblock->pkt->pkt.public_key; + if (!pk->flags.valid) + { + if (DBG_LOOKUP) + log_debug ("\tprimary key not valid\n"); + } + else if (!((pk->pubkey_usage & USAGE_MASK) & req_usage)) + { + if (DBG_LOOKUP) + log_debug ("\tprimary key usage does not match: " + "want=%x have=%x\n", req_usage, pk->pubkey_usage); + } + else if (pk->flags.revoked) + { + if (DBG_LOOKUP) + log_debug ("\tprimary key has been revoked\n"); + } + else if (pk->has_expired) + { + if (DBG_LOOKUP) + log_debug ("\tprimary key has expired\n"); + } + else /* Okay. */ + { + if (DBG_LOOKUP) + log_debug ("\tprimary key may be used\n"); + latest_key = keyblock; + } + } + + if (!latest_key) + { + if (DBG_LOOKUP) + log_debug ("\tno suitable key found - giving up\n"); + if (r_flags) + *r_flags |= LOOKUP_NOT_SELECTED; + return NULL; /* Not found. */ + } + + found: + if (DBG_LOOKUP) + log_debug ("\tusing key %08lX\n", + (ulong) keyid_from_pk (latest_key->pkt->pkt.public_key, NULL)); + + if (latest_key) + { + pk = latest_key->pkt->pkt.public_key; + free_user_id (pk->user_id); + pk->user_id = scopy_user_id (foundu); + } + + 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 latest_key ? latest_key : keyblock; /* Found. */ +} + + +/* Print a KEY_CONSIDERED status line. */ +static void +print_status_key_considered (kbnode_t keyblock, unsigned int flags) +{ + char hexfpr[2*MAX_FINGERPRINT_LEN + 1]; + kbnode_t node; + char flagbuf[20]; + + if (!is_status_enabled ()) + return; + + for (node=keyblock; node; node = node->next) + if (node->pkt->pkttype == PKT_PUBLIC_KEY + || node->pkt->pkttype == PKT_SECRET_KEY) + break; + if (!node) + { + log_error ("%s: keyblock w/o primary key\n", __func__); + return; + } + + hexfingerprint (node->pkt->pkt.public_key, hexfpr, sizeof hexfpr); + snprintf (flagbuf, sizeof flagbuf, " %u", flags); + write_status_strings (STATUS_KEY_CONSIDERED, hexfpr, flagbuf, NULL); +} + + + +/* A high-level function to lookup keys. + * + * This function builds on top of the low-level keydb API. It first + * searches the database using the description stored in CTX->ITEMS, + * then it filters the results using CTX and, finally, if WANT_SECRET + * is set, it ignores any keys for which no secret key is available. + * + * Unlike the low-level search functions, this function also merges + * all of the self-signed data into the keys, subkeys and user id + * packets (see the merge_selfsigs for details). + * + * On success the key's keyblock is stored at *RET_KEYBLOCK, and the + * specific subkey is stored at *RET_FOUND_KEY. Note that we do not + * return a reference in *RET_FOUND_KEY, i.e. the result must not be + * freed using 'release_kbnode', and it is only valid until + * *RET_KEYBLOCK is deallocated. Therefore, if RET_FOUND_KEY is not + * NULL, then RET_KEYBLOCK must not be NULL. */ +static int +lookup (ctrl_t ctrl, getkey_ctx_t ctx, int want_secret, + kbnode_t *ret_keyblock, kbnode_t *ret_found_key) +{ + int rc; + int no_suitable_key = 0; + KBNODE keyblock = NULL; + KBNODE found_key = NULL; + unsigned int infoflags; + + log_assert (ret_found_key == NULL || ret_keyblock != NULL); + if (ret_keyblock) + *ret_keyblock = NULL; + + for (;;) + { + rc = keydb_search (ctx->kr_handle, ctx->items, ctx->nitems, NULL); + if (rc) + break; + + /* If we are iterating over the entire database, then we need to + * change from KEYDB_SEARCH_MODE_FIRST, which does an implicit + * reset, to KEYDB_SEARCH_MODE_NEXT, which gets the next record. */ + if (ctx->nitems && ctx->items->mode == KEYDB_SEARCH_MODE_FIRST) + ctx->items->mode = KEYDB_SEARCH_MODE_NEXT; + + rc = keydb_get_keyblock (ctx->kr_handle, &keyblock); + if (rc) + { + log_error ("keydb_get_keyblock failed: %s\n", gpg_strerror (rc)); + goto skip; + } + + if (want_secret) + { + rc = agent_probe_any_secret_key (NULL, keyblock); + if (gpg_err_code(rc) == GPG_ERR_NO_SECKEY) + goto skip; /* No secret key available. */ + if (rc) + goto found; /* Unexpected error. */ + } + + /* Warning: node flag bits 0 and 1 should be preserved by + * merge_selfsigs. */ + merge_selfsigs (ctrl, keyblock); + found_key = finish_lookup (keyblock, ctx->req_usage, ctx->exact, + want_secret, &infoflags); + print_status_key_considered (keyblock, infoflags); + if (found_key) + { + no_suitable_key = 0; + goto found; + } + else + { + no_suitable_key = 1; + } + + skip: + /* Release resources and continue search. */ + release_kbnode (keyblock); + keyblock = NULL; + /* The keyblock cache ignores the current "file position". + * Thus, if we request the next result and the cache matches + * (and it will since it is what we just looked for), we'll get + * the same entry back! We can avoid this infinite loop by + * disabling the cache. */ + keydb_disable_caching (ctx->kr_handle); + } + + found: + if (rc && gpg_err_code (rc) != GPG_ERR_NOT_FOUND) + log_error ("keydb_search failed: %s\n", gpg_strerror (rc)); + + if (!rc) + { + if (ret_keyblock) + { + *ret_keyblock = keyblock; /* Return the keyblock. */ + keyblock = NULL; + } + } + else if (gpg_err_code (rc) == GPG_ERR_NOT_FOUND && no_suitable_key) + rc = want_secret? GPG_ERR_UNUSABLE_SECKEY : GPG_ERR_UNUSABLE_PUBKEY; + else if (gpg_err_code (rc) == GPG_ERR_NOT_FOUND) + rc = want_secret? GPG_ERR_NO_SECKEY : GPG_ERR_NO_PUBKEY; + + release_kbnode (keyblock); + + if (ret_found_key) + { + if (! rc) + *ret_found_key = found_key; + else + *ret_found_key = NULL; + } + + return rc; +} + + +/* If a default key has been specified, return that key. If a card + * based key is also available as indicated by FPR_CARD not being + * NULL, return that key if suitable. */ +gpg_error_t +get_seckey_default_or_card (ctrl_t ctrl, PKT_public_key *pk, + const byte *fpr_card, size_t fpr_len) +{ + gpg_error_t err; + strlist_t namelist = NULL; + const char *def_secret_key; + + def_secret_key = parse_def_secret_key (ctrl); + + if (def_secret_key) + add_to_strlist (&namelist, def_secret_key); + else if (fpr_card) + { + err = get_pubkey_byfprint (ctrl, pk, NULL, fpr_card, fpr_len); + if (gpg_err_code (err) == GPG_ERR_NO_PUBKEY) + { + if (opt.debug) + log_debug ("using LDAP to find public key for current card\n"); + err = keyserver_import_fprint (ctrl, fpr_card, fpr_len, + opt.keyserver, + KEYSERVER_IMPORT_FLAG_LDAP); + if (!err) + err = get_pubkey_byfprint (ctrl, pk, NULL, fpr_card, fpr_len); + else if (gpg_err_code (err) == GPG_ERR_NO_DATA + || gpg_err_code (err) == GPG_ERR_NO_KEYSERVER) + { + /* Dirmngr returns NO DATA is the selected keyserver + * does not have the requested key. It returns NO + * KEYSERVER if no LDAP keyservers are configured. */ + err = gpg_error (GPG_ERR_NO_PUBKEY); + } + } + + /* The key on card can be not suitable for requested usage. */ + if (gpg_err_code (err) == GPG_ERR_UNUSABLE_PUBKEY) + fpr_card = NULL; /* Fallthrough as no card. */ + else + return err; /* Success or other error. */ + } + + if (!fpr_card || (def_secret_key && *def_secret_key + && def_secret_key[strlen (def_secret_key)-1] == '!')) + { + err = key_byname (ctrl, NULL, namelist, pk, 1, 0, NULL, NULL); + } + else + { /* Default key is specified and card key is also available. */ + kbnode_t k, keyblock = NULL; + + err = key_byname (ctrl, NULL, namelist, pk, 1, 0, &keyblock, NULL); + if (err) + goto leave; + for (k = keyblock; k; k = k->next) + { + PKT_public_key *pk_candidate; + char fpr[MAX_FINGERPRINT_LEN]; + + if (k->pkt->pkttype != PKT_PUBLIC_KEY + &&k->pkt->pkttype != PKT_PUBLIC_SUBKEY) + continue; + + pk_candidate = k->pkt->pkt.public_key; + if (!pk_candidate->flags.valid) + continue; + if (!((pk_candidate->pubkey_usage & USAGE_MASK) & pk->req_usage)) + continue; + fingerprint_from_pk (pk_candidate, fpr, NULL); + if (!memcmp (fpr_card, fpr, fpr_len)) + { + release_public_key_parts (pk); + copy_public_key (pk, pk_candidate); + break; + } + } + release_kbnode (keyblock); + } + + leave: + free_strlist (namelist); + return err; +} + + + +/********************************************* + *********** User ID printing helpers ******* + *********************************************/ + +/* Return a string with a printable representation of the user_id. + * this string must be freed by xfree. If R_NOUID is not NULL it is + * set to true if a user id was not found; otherwise to false. */ +static char * +get_user_id_string (ctrl_t ctrl, u32 * keyid, int mode, size_t *r_len, + int *r_nouid) +{ + user_id_db_t r; + keyid_list_t a; + int pass = 0; + char *p; + + if (r_nouid) + *r_nouid = 0; + + /* Try it two times; second pass reads from the database. */ + do + { + for (r = user_id_db; r; r = r->next) + { + for (a = r->keyids; a; a = a->next) + { + if (a->keyid[0] == keyid[0] && a->keyid[1] == keyid[1]) + { + if (mode == 2) + { + /* An empty string as user id is possible. Make + sure that the malloc allocates one byte and + does not bail out. */ + p = xmalloc (r->len? r->len : 1); + memcpy (p, r->name, r->len); + if (r_len) + *r_len = r->len; + } + else + { + if (mode) + p = xasprintf ("%08lX%08lX %.*s", + (ulong) keyid[0], (ulong) keyid[1], + r->len, r->name); + else + p = xasprintf ("%s %.*s", keystr (keyid), + r->len, r->name); + if (r_len) + *r_len = strlen (p); + } + + return p; + } + } + } + } + while (++pass < 2 && !get_pubkey (ctrl, NULL, keyid)); + + if (mode == 2) + p = xstrdup (user_id_not_found_utf8 ()); + else if (mode) + p = xasprintf ("%08lX%08lX [?]", (ulong) keyid[0], (ulong) keyid[1]); + else + p = xasprintf ("%s [?]", keystr (keyid)); + + if (r_nouid) + *r_nouid = 1; + if (r_len) + *r_len = strlen (p); + return p; +} + + +char * +get_user_id_string_native (ctrl_t ctrl, u32 * keyid) +{ + char *p = get_user_id_string (ctrl, keyid, 0, NULL, NULL); + char *p2 = utf8_to_native (p, strlen (p), 0); + xfree (p); + return p2; +} + + +char * +get_long_user_id_string (ctrl_t ctrl, u32 * keyid) +{ + return get_user_id_string (ctrl, keyid, 1, NULL, NULL); +} + + +/* Please try to use get_user_byfpr instead of this one. */ +char * +get_user_id (ctrl_t ctrl, u32 *keyid, size_t *rn, int *r_nouid) +{ + return get_user_id_string (ctrl, keyid, 2, rn, r_nouid); +} + + +/* Please try to use get_user_id_byfpr_native instead of this one. */ +char * +get_user_id_native (ctrl_t ctrl, u32 *keyid) +{ + size_t rn; + char *p = get_user_id (ctrl, keyid, &rn, NULL); + char *p2 = utf8_to_native (p, rn, 0); + xfree (p); + return p2; +} + + +/* Return the user id for a key designated by its fingerprint, FPR, + which must be MAX_FINGERPRINT_LEN bytes in size. Note: the + returned string, which must be freed using xfree, may not be NUL + terminated. To determine the length of the string, you must use + *RN. */ +char * +get_user_id_byfpr (ctrl_t ctrl, const byte *fpr, size_t *rn) +{ + user_id_db_t r; + char *p; + int pass = 0; + + /* Try it two times; second pass reads from the database. */ + do + { + for (r = user_id_db; r; r = r->next) + { + keyid_list_t a; + for (a = r->keyids; a; a = a->next) + { + if (!memcmp (a->fpr, fpr, MAX_FINGERPRINT_LEN)) + { + /* An empty string as user id is possible. Make + sure that the malloc allocates one byte and does + not bail out. */ + p = xmalloc (r->len? r->len : 1); + memcpy (p, r->name, r->len); + *rn = r->len; + return p; + } + } + } + } + while (++pass < 2 + && !get_pubkey_byfprint (ctrl, NULL, NULL, fpr, MAX_FINGERPRINT_LEN)); + p = xstrdup (user_id_not_found_utf8 ()); + *rn = strlen (p); + return p; +} + +/* Like get_user_id_byfpr, but convert the string to the native + encoding. The returned string needs to be freed. Unlike + get_user_id_byfpr, the returned string is NUL terminated. */ +char * +get_user_id_byfpr_native (ctrl_t ctrl, const byte *fpr) +{ + size_t rn; + char *p = get_user_id_byfpr (ctrl, fpr, &rn); + char *p2 = utf8_to_native (p, rn, 0); + xfree (p); + return p2; +} + + +/* Return the database handle used by this context. The context still + owns the handle. */ +KEYDB_HANDLE +get_ctx_handle (GETKEY_CTX ctx) +{ + return ctx->kr_handle; +} + +static void +free_akl (struct akl *akl) +{ + if (! akl) + return; + + 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); + } +} + + +/* Returns true if the AKL is empty or has only the local method + * active. */ +int +akl_empty_or_only_local (void) +{ + struct akl *akl; + int any = 0; + + for (akl = opt.auto_key_locate; akl; akl = akl->next) + if (akl->type != AKL_NODEFAULT && akl->type != AKL_LOCAL) + { + any = 1; + break; + } + + return !any; +} + + +/* Returns false on error. */ +int +parse_auto_key_locate (const char *options_arg) +{ + char *tok; + char *options, *options_buf; + + options = options_buf = xstrdup (options_arg); + 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, "clear") == 0) + { + xfree (akl); + free_akl (opt.auto_key_locate); + opt.auto_key_locate = NULL; + continue; + } + else if (ascii_strcasecmp (tok, "nodefault") == 0) + akl->type = AKL_NODEFAULT; + else if (ascii_strcasecmp (tok, "local") == 0) + akl->type = AKL_LOCAL; + else if (ascii_strcasecmp (tok, "ldap") == 0) + akl->type = AKL_LDAP; + else if (ascii_strcasecmp (tok, "keyserver") == 0) + akl->type = AKL_KEYSERVER; + else if (ascii_strcasecmp (tok, "cert") == 0) + akl->type = AKL_CERT; + else if (ascii_strcasecmp (tok, "pka") == 0) + akl->type = AKL_PKA; + else if (ascii_strcasecmp (tok, "dane") == 0) + akl->type = AKL_DANE; + else if (ascii_strcasecmp (tok, "wkd") == 0) + akl->type = AKL_WKD; + else if (ascii_strcasecmp (tok, "ntds") == 0) + akl->type = AKL_NTDS; + else if ((akl->spec = parse_keyserver_uri (tok, 1))) + akl->type = AKL_SPEC; + else + { + free_akl (akl); + xfree (options_buf); + 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; + } + } + + xfree (options_buf); + return 1; +} + + + +/* The list of key origins. */ +static struct { + const char *name; + int origin; +} key_origin_list[] = + { + { "self", KEYORG_SELF }, + { "file", KEYORG_FILE }, + { "url", KEYORG_URL }, + { "wkd", KEYORG_WKD }, + { "dane", KEYORG_DANE }, + { "ks-pref", KEYORG_KS_PREF }, + { "ks", KEYORG_KS }, + { "unknown", KEYORG_UNKNOWN } + }; + +/* Parse the argument for --key-origin. Return false on error. */ +int +parse_key_origin (char *string) +{ + int i; + char *comma; + + comma = strchr (string, ','); + if (comma) + *comma = 0; + + if (!ascii_strcasecmp (string, "help")) + { + log_info (_("valid values for option '%s':\n"), "--key-origin"); + for (i=0; i < DIM (key_origin_list); i++) + log_info (" %s\n", key_origin_list[i].name); + g10_exit (1); + } + + for (i=0; i < DIM (key_origin_list); i++) + if (!ascii_strcasecmp (string, key_origin_list[i].name)) + { + opt.key_origin = key_origin_list[i].origin; + xfree (opt.key_origin_url); + opt.key_origin_url = NULL; + if (comma && comma[1]) + { + opt.key_origin_url = xstrdup (comma+1); + trim_spaces (opt.key_origin_url); + } + + return 1; + } + + if (comma) + *comma = ','; + return 0; +} + +/* Return a string or "?" for the key ORIGIN. */ +const char * +key_origin_string (int origin) +{ + int i; + + for (i=0; i < DIM (key_origin_list); i++) + if (key_origin_list[i].origin == origin) + return key_origin_list[i].name; + return "?"; +} + + + +/* Returns true if a secret key is available for the public key with + key id KEYID; returns false if not. This function ignores legacy + keys. Note: this is just a fast check and does not tell us whether + the secret key is valid; this check merely indicates whether there + is some secret key with the specified key id. */ +int +have_secret_key_with_kid (u32 *keyid) +{ + gpg_error_t err; + KEYDB_HANDLE kdbhd; + KEYDB_SEARCH_DESC desc; + kbnode_t keyblock; + kbnode_t node; + int result = 0; + + kdbhd = keydb_new (); + if (!kdbhd) + return 0; + memset (&desc, 0, sizeof desc); + desc.mode = KEYDB_SEARCH_MODE_LONG_KID; + desc.u.kid[0] = keyid[0]; + desc.u.kid[1] = keyid[1]; + while (!result) + { + err = keydb_search (kdbhd, &desc, 1, NULL); + if (err) + break; + + err = keydb_get_keyblock (kdbhd, &keyblock); + if (err) + { + log_error (_("error reading keyblock: %s\n"), gpg_strerror (err)); + break; + } + + for (node = keyblock; node; node = node->next) + { + /* Bit 0 of the flags is set if the search found the key + using that key or subkey. Note: a search will only ever + match a single key or subkey. */ + if ((node->flag & 1)) + { + log_assert (node->pkt->pkttype == PKT_PUBLIC_KEY + || node->pkt->pkttype == PKT_PUBLIC_SUBKEY); + + if (!agent_probe_secret_key (NULL, node->pkt->pkt.public_key)) + result = 1; /* Secret key available. */ + else + result = 0; + + break; + } + } + release_kbnode (keyblock); + } + + keydb_release (kdbhd); + return result; +} diff --git a/g10/gpg-w32info.rc b/g10/gpg-w32info.rc new file mode 100644 index 0000000..cc34c30 --- /dev/null +++ b/g10/gpg-w32info.rc @@ -0,0 +1,52 @@ +/* gpg-w32info.rc -*- c -*- + * Copyright (C) 2013 g10 Code GmbH + * + * This file is free software; as a special exception the author gives + * unlimited permission to copy and/or distribute it, with or without + * modifications, as long as this notice is preserved. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY, to the extent permitted by law; without even the + * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + */ + +#include "afxres.h" +#include "../common/w32info-rc.h" + +1 ICON "../common/gnupg.ico" + +1 VERSIONINFO + FILEVERSION W32INFO_VI_FILEVERSION + PRODUCTVERSION W32INFO_VI_PRODUCTVERSION + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x01L /* VS_FF_DEBUG (0x1)*/ +#else + FILEFLAGS 0x00L +#endif + FILEOS 0x40004L /* VOS_NT (0x40000) | VOS__WINDOWS32 (0x4) */ + FILETYPE 0x1L /* VFT_APP (0x1) */ + FILESUBTYPE 0x0L /* VFT2_UNKNOWN */ + BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" /* US English (0409), Unicode (04b0) */ + BEGIN + VALUE "FileDescription", L"GnuPG\x2019s OpenPGP tool\0" + VALUE "InternalName", "gpg\0" + VALUE "OriginalFilename", "gpg.exe\0" + VALUE "ProductName", W32INFO_PRODUCTNAME + VALUE "ProductVersion", W32INFO_PRODUCTVERSION + VALUE "CompanyName", W32INFO_COMPANYNAME + VALUE "FileVersion", W32INFO_FILEVERSION + VALUE "LegalCopyright", W32INFO_LEGALCOPYRIGHT + VALUE "Comments", W32INFO_COMMENTS + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 0x4b0 + END + END + +1 RT_MANIFEST "gpg.w32-manifest" diff --git a/g10/gpg.c b/g10/gpg.c new file mode 100644 index 0000000..bd65612 --- /dev/null +++ b/g10/gpg.c @@ -0,0 +1,5569 @@ +/* gpg.c - The GnuPG utility (main for gpg) + * Copyright (C) 1998-2020 Free Software Foundation, Inc. + * Copyright (C) 1997-2019 Werner Koch + * Copyright (C) 2015-2021 g10 Code GmbH + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include +#include +#include +#include +#include +#include +#include +#ifdef HAVE_STAT +#include /* for stat() */ +#endif +#include +#ifdef HAVE_W32_SYSTEM +# ifdef HAVE_WINSOCK2_H +# include +# endif +# include +#endif + +#define INCLUDED_BY_MAIN_MODULE 1 +#include "gpg.h" +#include +#include "../common/iobuf.h" +#include "../common/util.h" +#include "packet.h" +#include "../common/membuf.h" +#include "main.h" +#include "options.h" +#include "keydb.h" +#include "trustdb.h" +#include "filter.h" +#include "../common/ttyio.h" +#include "../common/i18n.h" +#include "../common/sysutils.h" +#include "../common/status.h" +#include "keyserver-internal.h" +#include "exec.h" +#include "../common/gc-opt-flags.h" +#include "../common/asshelp.h" +#include "call-dirmngr.h" +#include "tofu.h" +#include "../common/init.h" +#include "../common/mbox-util.h" +#include "../common/shareddefs.h" +#include "../common/compliance.h" + +#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', + oRecipientFile = 'f', + oHiddenRecipientFile = 'F', + oInteractive = 'i', + aListKeys = '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, + oInputSizeHint, + oSigNotation, + oCertNotation, + oShowNotation, + oNoShowNotation, + oKnownNotation, + aEncrFiles, + aEncrSym, + aDecryptFiles, + aClearsign, + aStore, + aQuickKeygen, + aFullKeygen, + aKeygen, + aSignEncr, + aSignEncrSym, + aSignSym, + aSignKey, + aLSignKey, + aQuickSignKey, + aQuickLSignKey, + aQuickRevSig, + aQuickAddUid, + aQuickAddKey, + aQuickRevUid, + aQuickSetExpire, + aQuickSetPrimaryUid, + aListConfig, + aListGcryptConfig, + aGPGConfList, + aGPGConfTest, + aListPackets, + aEditKey, + aDeleteKeys, + aDeleteSecretKeys, + aDeleteSecretAndPublicKeys, + aImport, + aFastImport, + aVerify, + aVerifyFiles, + aListSigs, + aSendKeys, + aRecvKeys, + aLocateKeys, + aLocateExtKeys, + aSearchKeys, + aRefreshKeys, + aFetchKeys, + aShowKeys, + aExport, + aExportSecret, + aExportSecretSub, + aExportSshKey, + aCheckKeys, + aGenRevoke, + aDesigRevoke, + aPrimegen, + aPrintMD, + aPrintMDs, + aCheckTrustDB, + aUpdateTrustDB, + aFixTrustDB, + aListTrustDB, + aListTrustPath, + aExportOwnerTrust, + aImportOwnerTrust, + aDeArmor, + aEnArmor, + aGenRandom, + aRebuildKeydbCaches, + aCardStatus, + aCardEdit, + aChangePIN, + aPasswd, + aServer, + aTOFUPolicy, + + oMimemode, + oTextmode, + oNoTextmode, + oExpert, + oNoExpert, + oDefSigExpire, + oAskSigExpire, + oNoAskSigExpire, + oDefCertExpire, + oAskCertExpire, + oNoAskCertExpire, + oDefCertLevel, + oMinCertLevel, + oAskCertLevel, + oNoAskCertLevel, + oFingerprint, + oWithFingerprint, + oWithSubkeyFingerprint, + oWithICAOSpelling, + oWithKeygrip, + oWithSecret, + oWithWKDHash, + oWithColons, + oWithKeyData, + oWithKeyOrigin, + oWithTofuInfo, + oWithSigList, + oWithSigCheck, + oAnswerYes, + oAnswerNo, + oKeyring, + oPrimaryKeyring, + oSecretKeyring, + oShowKeyring, + oDefaultKey, + oDefRecipient, + oDefRecipientSelf, + oNoDefRecipient, + oTrySecretKey, + oOptions, + oDebug, + oDebugLevel, + oDebugAll, + oDebugIOLBF, + oStatusFD, + oStatusFile, + oAttributeFD, + oAttributeFile, + oEmitVersion, + oNoEmitVersion, + oCompletesNeeded, + oMarginalsNeeded, + oMaxCertDepth, + oLoadExtension, + oCompliance, + oGnuPG, + oRFC2440, + oRFC4880, + oRFC4880bis, + oOpenPGP, + oPGP6, + oPGP7, + oPGP8, + oDE_VS, + oMinRSALength, + oRFC2440Text, + oNoRFC2440Text, + oCipherAlgo, + oDigestAlgo, + oCertDigestAlgo, + oCompressAlgo, + oCompressLevel, + oBZ2CompressLevel, + oBZ2DecompressLowmem, + oPassphrase, + oPassphraseFD, + oPassphraseFile, + oPassphraseRepeat, + oPinentryMode, + oCommandFD, + oCommandFile, + oQuickRandom, + oNoVerbose, + oTrustDBName, + oNoSecmemWarn, + oRequireSecmem, + oNoRequireSecmem, + oNoPermissionWarn, + oNoArmor, + oNoDefKeyring, + oNoKeyring, + oNoGreeting, + oNoTTY, + oNoOptions, + oNoBatch, + oHomedir, + oSkipVerify, + oSkipHiddenRecipients, + oNoSkipHiddenRecipients, + oAlwaysTrust, + oTrustModel, + oForceOwnertrust, + oSetFilename, + oForYourEyesOnly, + oNoForYourEyesOnly, + oSetPolicyURL, + oSigPolicyURL, + oCertPolicyURL, + oShowPolicyURL, + oNoShowPolicyURL, + oSigKeyserverURL, + oUseEmbeddedFilename, + oNoUseEmbeddedFilename, + oComment, + oDefaultComment, + oNoComments, + oThrowKeyids, + oNoThrowKeyids, + oShowPhotos, + oNoShowPhotos, + oPhotoViewer, + oS2KMode, + oS2KDigest, + oS2KCipher, + oS2KCount, + oDisplayCharset, + oNotDashEscaped, + oEscapeFrom, + oNoEscapeFrom, + oLockOnce, + oLockMultiple, + oLockNever, + oKeyServer, + oKeyServerOptions, + oImportOptions, + oImportFilter, + oExportOptions, + oExportFilter, + oListOptions, + oVerifyOptions, + oTempDir, + oExecPath, + oEncryptTo, + oHiddenEncryptTo, + oNoEncryptTo, + oEncryptToDefaultKey, + oLoggerFD, + oLoggerFile, + oUtf8Strings, + oNoUtf8Strings, + oDisableCipherAlgo, + oDisablePubkeyAlgo, + oAllowNonSelfsignedUID, + oNoAllowNonSelfsignedUID, + oAllowFreeformUID, + oNoAllowFreeformUID, + oAllowSecretKeyImport, + oEnableSpecialFilenames, + oNoLiteral, + oSetFilesize, + oHonorHttpProxy, + oFastListMode, + oListOnly, + oIgnoreTimeConflict, + oIgnoreValidFrom, + oIgnoreCrcError, + oIgnoreMDCError, + oShowSessionKey, + oOverrideSessionKey, + oOverrideSessionKeyFD, + oOverrideComplianceCheck, + oNoRandomSeedFile, + oAutoKeyRetrieve, + oNoAutoKeyRetrieve, + oAutoKeyImport, + oNoAutoKeyImport, + oUseAgent, + oNoUseAgent, + oGpgAgentInfo, + oMergeOnly, + oTryAllSecrets, + oTrustedKey, + oNoExpensiveTrustChecks, + oFixedListMode, + oLegacyListMode, + oNoSigCache, + oAutoCheckTrustDB, + oNoAutoCheckTrustDB, + oPreservePermissions, + oDefaultPreferenceList, + oDefaultKeyserverURL, + oPersonalCipherPreferences, + oPersonalDigestPreferences, + oPersonalCompressPreferences, + oAgentProgram, + oDirmngrProgram, + oDisableDirmngr, + oDisplay, + oTTYname, + oTTYtype, + oLCctype, + oLCmessages, + oXauthority, + oGroup, + oUnGroup, + oNoGroups, + oStrict, + oNoStrict, + oMangleDosFilenames, + oNoMangleDosFilenames, + oEnableProgressFilter, + oMultifile, + oKeyidFormat, + oExitOnStatusWriteError, + oLimitCardInsertTries, + oReaderPort, + octapiDriver, + opcscDriver, + oDisableCCID, + oRequireCrossCert, + oNoRequireCrossCert, + oAutoKeyLocate, + oNoAutoKeyLocate, + oAllowMultisigVerification, + oEnableLargeRSA, + oDisableLargeRSA, + oEnableDSA2, + oDisableDSA2, + oAllowMultipleMessages, + oNoAllowMultipleMessages, + oAllowWeakDigestAlgos, + oAllowWeakKeySignatures, + oFakedSystemTime, + oNoAutostart, + oPrintPKARecords, + oPrintDANERecords, + oTOFUDefaultPolicy, + oTOFUDBFormat, + oDefaultNewKeyAlgo, + oWeakDigest, + oUnwrap, + oOnlySignTextIDs, + oDisableSignerUID, + oSender, + oKeyOrigin, + oRequestOrigin, + oNoSymkeyCache, + oUseOnlyOpenPGPCard, + oIncludeKeyBlock, + oNoIncludeKeyBlock, + oForceSignKey, + oForbidGenKey, + oRequireCompliance, + + oNoop + }; + + +static ARGPARSE_OPTS opts[] = { + + ARGPARSE_group (300, N_("@Commands:\n ")), + + ARGPARSE_c (aSign, "sign", N_("make a signature")), + ARGPARSE_c (aClearsign, "clear-sign", N_("make a clear text signature")), + ARGPARSE_c (aClearsign, "clearsign", "@"), + ARGPARSE_c (aDetachedSign, "detach-sign", N_("make a detached signature")), + ARGPARSE_c (aEncr, "encrypt", N_("encrypt data")), + ARGPARSE_c (aEncrFiles, "encrypt-files", "@"), + ARGPARSE_c (aSym, "symmetric", N_("encryption only with symmetric cipher")), + ARGPARSE_c (aStore, "store", "@"), + ARGPARSE_c (aDecrypt, "decrypt", N_("decrypt data (default)")), + ARGPARSE_c (aDecryptFiles, "decrypt-files", "@"), + ARGPARSE_c (aVerify, "verify" , N_("verify a signature")), + ARGPARSE_c (aVerifyFiles, "verify-files" , "@" ), + ARGPARSE_c (aListKeys, "list-keys", N_("list keys")), + ARGPARSE_c (aListKeys, "list-public-keys", "@" ), + ARGPARSE_c (aListSigs, "list-signatures", N_("list keys and signatures")), + ARGPARSE_c (aListSigs, "list-sigs", "@"), + ARGPARSE_c (aCheckKeys, "check-signatures", + N_("list and check key signatures")), + ARGPARSE_c (aCheckKeys, "check-sigs", "@"), + ARGPARSE_c (oFingerprint, "fingerprint", N_("list keys and fingerprints")), + ARGPARSE_c (aListSecretKeys, "list-secret-keys", N_("list secret keys")), + ARGPARSE_c (aKeygen, "generate-key", + N_("generate a new key pair")), + ARGPARSE_c (aKeygen, "gen-key", "@"), + ARGPARSE_c (aQuickKeygen, "quick-generate-key" , + N_("quickly generate a new key pair")), + ARGPARSE_c (aQuickKeygen, "quick-gen-key", "@"), + ARGPARSE_c (aQuickAddUid, "quick-add-uid", + N_("quickly add a new user-id")), + ARGPARSE_c (aQuickAddUid, "quick-adduid", "@"), + ARGPARSE_c (aQuickAddKey, "quick-add-key", "@"), + ARGPARSE_c (aQuickAddKey, "quick-addkey", "@"), + ARGPARSE_c (aQuickRevUid, "quick-revoke-uid", + N_("quickly revoke a user-id")), + ARGPARSE_c (aQuickRevUid, "quick-revuid", "@"), + ARGPARSE_c (aQuickSetExpire, "quick-set-expire", + N_("quickly set a new expiration date")), + ARGPARSE_c (aQuickSetPrimaryUid, "quick-set-primary-uid", "@"), + ARGPARSE_c (aFullKeygen, "full-generate-key" , + N_("full featured key pair generation")), + ARGPARSE_c (aFullKeygen, "full-gen-key", "@"), + ARGPARSE_c (aGenRevoke, "generate-revocation", + N_("generate a revocation certificate")), + ARGPARSE_c (aGenRevoke, "gen-revoke", "@"), + ARGPARSE_c (aDeleteKeys,"delete-keys", + N_("remove keys from the public keyring")), + ARGPARSE_c (aDeleteSecretKeys, "delete-secret-keys", + N_("remove keys from the secret keyring")), + ARGPARSE_c (aQuickSignKey, "quick-sign-key" , + N_("quickly sign a key")), + ARGPARSE_c (aQuickLSignKey, "quick-lsign-key", + N_("quickly sign a key locally")), + ARGPARSE_c (aQuickRevSig, "quick-revoke-sig" , + N_("quickly revoke a key signature")), + ARGPARSE_c (aSignKey, "sign-key" ,N_("sign a key")), + ARGPARSE_c (aLSignKey, "lsign-key" ,N_("sign a key locally")), + ARGPARSE_c (aEditKey, "edit-key" ,N_("sign or edit a key")), + ARGPARSE_c (aEditKey, "key-edit" ,"@"), + ARGPARSE_c (aPasswd, "change-passphrase", N_("change a passphrase")), + ARGPARSE_c (aPasswd, "passwd", "@"), + ARGPARSE_c (aDesigRevoke, "generate-designated-revocation", "@"), + ARGPARSE_c (aDesigRevoke, "desig-revoke","@" ), + ARGPARSE_c (aExport, "export" , N_("export keys") ), + ARGPARSE_c (aSendKeys, "send-keys" , N_("export keys to a keyserver") ), + ARGPARSE_c (aRecvKeys, "receive-keys" , N_("import keys from a keyserver") ), + ARGPARSE_c (aRecvKeys, "recv-keys" , "@"), + ARGPARSE_c (aSearchKeys, "search-keys" , + N_("search for keys on a keyserver") ), + ARGPARSE_c (aRefreshKeys, "refresh-keys", + N_("update all keys from a keyserver")), + ARGPARSE_c (aLocateKeys, "locate-keys", "@"), + ARGPARSE_c (aLocateExtKeys, "locate-external-keys", "@"), + ARGPARSE_c (aFetchKeys, "fetch-keys" , "@" ), + ARGPARSE_c (aShowKeys, "show-keys" , "@" ), + ARGPARSE_c (aExportSecret, "export-secret-keys" , "@" ), + ARGPARSE_c (aExportSecretSub, "export-secret-subkeys" , "@" ), + ARGPARSE_c (aExportSshKey, "export-ssh-key", "@" ), + ARGPARSE_c (aImport, "import", N_("import/merge keys")), + ARGPARSE_c (aFastImport, "fast-import", "@"), +#ifdef ENABLE_CARD_SUPPORT + ARGPARSE_c (aCardStatus, "card-status", N_("print the card status")), + ARGPARSE_c (aCardEdit, "edit-card", N_("change data on a card")), + ARGPARSE_c (aCardEdit, "card-edit", "@"), + ARGPARSE_c (aChangePIN, "change-pin", N_("change a card's PIN")), +#endif + ARGPARSE_c (aListConfig, "list-config", "@"), + ARGPARSE_c (aListGcryptConfig, "list-gcrypt-config", "@"), + ARGPARSE_c (aGPGConfList, "gpgconf-list", "@" ), + ARGPARSE_c (aGPGConfTest, "gpgconf-test", "@" ), + ARGPARSE_c (aListPackets, "list-packets","@"), + +#ifndef NO_TRUST_MODELS + ARGPARSE_c (aExportOwnerTrust, "export-ownertrust", "@"), + ARGPARSE_c (aImportOwnerTrust, "import-ownertrust", "@"), + ARGPARSE_c (aUpdateTrustDB,"update-trustdb", + N_("update the trust database")), + ARGPARSE_c (aCheckTrustDB, "check-trustdb", "@"), + ARGPARSE_c (aFixTrustDB, "fix-trustdb", "@"), + ARGPARSE_c (aListTrustDB, "list-trustdb", "@"), +#endif + + ARGPARSE_c (aDeArmor, "dearmor", "@"), + ARGPARSE_c (aDeArmor, "dearmour", "@"), + ARGPARSE_c (aEnArmor, "enarmor", "@"), + ARGPARSE_c (aEnArmor, "enarmour", "@"), + ARGPARSE_c (aPrintMD, "print-md", N_("print message digests")), + ARGPARSE_c (aPrintMDs, "print-mds", "@"), /* old */ + ARGPARSE_c (aPrimegen, "gen-prime", "@" ), + ARGPARSE_c (aGenRandom,"gen-random", "@" ), + ARGPARSE_c (aServer, "server", N_("run in server mode")), + ARGPARSE_c (aTOFUPolicy, "tofu-policy", + N_("|VALUE|set the TOFU policy for a key")), + /* Not yet used: + ARGPARSE_c (aListTrustPath, "list-trust-path", "@"), */ + ARGPARSE_c (aDeleteSecretAndPublicKeys, + "delete-secret-and-public-keys", "@"), + ARGPARSE_c (aRebuildKeydbCaches, "rebuild-keydb-caches", "@"), + ARGPARSE_c (aListKeys, "list-key", "@"), /* alias */ + ARGPARSE_c (aListSigs, "list-sig", "@"), /* alias */ + ARGPARSE_c (aCheckKeys, "check-sig", "@"), /* alias */ + ARGPARSE_c (aShowKeys, "show-key", "@"), /* alias */ + + + ARGPARSE_header ("Monitor", N_("Options controlling the diagnostic output")), + + ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")), + ARGPARSE_s_n (oNoVerbose, "no-verbose", "@"), + ARGPARSE_s_n (oQuiet, "quiet", N_("be somewhat more quiet")), + ARGPARSE_s_n (oNoTTY, "no-tty", "@"), + ARGPARSE_s_n (oNoGreeting, "no-greeting", "@"), + ARGPARSE_s_s (oDebug, "debug", "@"), + ARGPARSE_s_s (oDebugLevel, "debug-level", "@"), + ARGPARSE_s_n (oDebugAll, "debug-all", "@"), + ARGPARSE_s_n (oDebugIOLBF, "debug-iolbf", "@"), + ARGPARSE_s_s (oDisplayCharset, "display-charset", "@"), + ARGPARSE_s_s (oDisplayCharset, "charset", "@"), + ARGPARSE_conffile (oOptions, "options", N_("|FILE|read options from FILE")), + ARGPARSE_noconffile (oNoOptions, "no-options", "@"), + ARGPARSE_s_i (oLoggerFD, "logger-fd", "@"), + ARGPARSE_s_s (oLoggerFile, "log-file", + N_("|FILE|write server mode logs to FILE")), + ARGPARSE_s_s (oLoggerFile, "logger-file", "@"), /* 1.4 compatibility. */ + ARGPARSE_s_n (oQuickRandom, "debug-quick-random", "@"), + + + ARGPARSE_header ("Configuration", + N_("Options controlling the configuration")), + + ARGPARSE_s_s (oHomedir, "homedir", "@"), + ARGPARSE_s_s (oFakedSystemTime, "faked-system-time", "@"), + ARGPARSE_s_s (oDefaultKey, "default-key", + N_("|NAME|use NAME as default secret key")), + ARGPARSE_s_s (oEncryptTo, "encrypt-to", + N_("|NAME|encrypt to user ID NAME as well")), + ARGPARSE_s_n (oNoEncryptTo, "no-encrypt-to", "@"), + ARGPARSE_s_s (oHiddenEncryptTo, "hidden-encrypt-to", "@"), + ARGPARSE_s_n (oEncryptToDefaultKey, "encrypt-to-default-key", "@"), + ARGPARSE_s_s (oDefRecipient, "default-recipient", "@"), + ARGPARSE_s_n (oDefRecipientSelf, "default-recipient-self", "@"), + ARGPARSE_s_n (oNoDefRecipient, "no-default-recipient", "@"), + ARGPARSE_s_s (oGroup, "group", + N_("|SPEC|set up email aliases")), + ARGPARSE_s_s (oUnGroup, "ungroup", "@"), + ARGPARSE_s_n (oNoGroups, "no-groups", "@"), + ARGPARSE_s_s (oCompliance, "compliance", "@"), + ARGPARSE_s_n (oGnuPG, "gnupg", "@"), + ARGPARSE_s_n (oGnuPG, "no-pgp2", "@"), + ARGPARSE_s_n (oGnuPG, "no-pgp6", "@"), + ARGPARSE_s_n (oGnuPG, "no-pgp7", "@"), + ARGPARSE_s_n (oGnuPG, "no-pgp8", "@"), + ARGPARSE_s_n (oRFC2440, "rfc2440", "@"), + ARGPARSE_s_n (oRFC4880, "rfc4880", "@"), + ARGPARSE_s_n (oRFC4880bis, "rfc4880bis", "@"), + ARGPARSE_s_n (oOpenPGP, "openpgp", N_("use strict OpenPGP behavior")), + ARGPARSE_s_n (oPGP6, "pgp6", "@"), + ARGPARSE_s_n (oPGP7, "pgp7", "@"), + ARGPARSE_s_n (oPGP8, "pgp8", "@"), + ARGPARSE_s_s (oDefaultNewKeyAlgo, "default-new-key-algo", "@"), + ARGPARSE_p_u (oMinRSALength, "min-rsa-length", "@"), +#ifndef NO_TRUST_MODELS + ARGPARSE_s_n (oAlwaysTrust, "always-trust", "@"), +#endif + ARGPARSE_s_s (oTrustModel, "trust-model", "@"), + ARGPARSE_s_s (oPhotoViewer, "photo-viewer", "@"), + ARGPARSE_s_s (oKnownNotation, "known-notation", "@"), + ARGPARSE_s_s (oAgentProgram, "agent-program", "@"), + ARGPARSE_s_s (oDirmngrProgram, "dirmngr-program", "@"), + ARGPARSE_s_n (oExitOnStatusWriteError, "exit-on-status-write-error", "@"), + ARGPARSE_s_i (oLimitCardInsertTries, "limit-card-insert-tries", "@"), + ARGPARSE_s_n (oEnableProgressFilter, "enable-progress-filter", "@"), + ARGPARSE_s_s (oTempDir, "temp-directory", "@"), + ARGPARSE_s_s (oExecPath, "exec-path", "@"), + ARGPARSE_s_n (oExpert, "expert", "@"), + ARGPARSE_s_n (oNoExpert, "no-expert", "@"), + ARGPARSE_s_n (oNoSecmemWarn, "no-secmem-warning", "@"), + ARGPARSE_s_n (oRequireSecmem, "require-secmem", "@"), + ARGPARSE_s_n (oNoRequireSecmem, "no-require-secmem", "@"), + ARGPARSE_s_n (oNoPermissionWarn, "no-permission-warning", "@"), + ARGPARSE_s_n (oDryRun, "dry-run", N_("do not make any changes")), + ARGPARSE_s_n (oInteractive, "interactive", N_("prompt before overwriting")), + ARGPARSE_s_s (oDefSigExpire, "default-sig-expire", "@"), + ARGPARSE_s_n (oAskSigExpire, "ask-sig-expire", "@"), + ARGPARSE_s_n (oNoAskSigExpire, "no-ask-sig-expire", "@"), + ARGPARSE_s_s (oDefCertExpire, "default-cert-expire", "@"), + ARGPARSE_s_n (oAskCertExpire, "ask-cert-expire", "@"), + ARGPARSE_s_n (oNoAskCertExpire, "no-ask-cert-expire", "@"), + ARGPARSE_s_i (oDefCertLevel, "default-cert-level", "@"), + ARGPARSE_s_i (oMinCertLevel, "min-cert-level", "@"), + ARGPARSE_s_n (oAskCertLevel, "ask-cert-level", "@"), + ARGPARSE_s_n (oNoAskCertLevel, "no-ask-cert-level", "@"), + ARGPARSE_s_n (oOnlySignTextIDs, "only-sign-text-ids", "@"), + ARGPARSE_s_n (oEnableLargeRSA, "enable-large-rsa", "@"), + ARGPARSE_s_n (oDisableLargeRSA, "disable-large-rsa", "@"), + ARGPARSE_s_n (oEnableDSA2, "enable-dsa2", "@"), + ARGPARSE_s_n (oDisableDSA2, "disable-dsa2", "@"), + ARGPARSE_s_s (oPersonalCipherPreferences, "personal-cipher-preferences","@"), + ARGPARSE_s_s (oPersonalDigestPreferences, "personal-digest-preferences","@"), + ARGPARSE_s_s (oPersonalCompressPreferences, + "personal-compress-preferences", "@"), + ARGPARSE_s_s (oDefaultPreferenceList, "default-preference-list", "@"), + ARGPARSE_s_s (oDefaultKeyserverURL, "default-keyserver-url", "@"), + ARGPARSE_s_n (oNoExpensiveTrustChecks, "no-expensive-trust-checks", "@"), + ARGPARSE_s_n (oAllowNonSelfsignedUID, "allow-non-selfsigned-uid", "@"), + ARGPARSE_s_n (oNoAllowNonSelfsignedUID, "no-allow-non-selfsigned-uid", "@"), + ARGPARSE_s_n (oAllowFreeformUID, "allow-freeform-uid", "@"), + ARGPARSE_s_n (oNoAllowFreeformUID, "no-allow-freeform-uid", "@"), + ARGPARSE_s_n (oPreservePermissions, "preserve-permissions", "@"), + ARGPARSE_s_i (oDefCertLevel, "default-cert-check-level", "@"), /* old */ + ARGPARSE_s_s (oTOFUDefaultPolicy, "tofu-default-policy", "@"), + ARGPARSE_s_n (oLockOnce, "lock-once", "@"), + ARGPARSE_s_n (oLockMultiple, "lock-multiple", "@"), + ARGPARSE_s_n (oLockNever, "lock-never", "@"), + ARGPARSE_s_s (oCompressAlgo,"compress-algo", "@"), + ARGPARSE_s_s (oCompressAlgo, "compression-algo", "@"), /* Alias */ + ARGPARSE_s_n (oBZ2DecompressLowmem, "bzip2-decompress-lowmem", "@"), + ARGPARSE_s_i (oCompletesNeeded, "completes-needed", "@"), + ARGPARSE_s_i (oMarginalsNeeded, "marginals-needed", "@"), + ARGPARSE_s_i (oMaxCertDepth, "max-cert-depth", "@" ), +#ifndef NO_TRUST_MODELS + ARGPARSE_s_s (oTrustDBName, "trustdb-name", "@"), + ARGPARSE_s_n (oAutoCheckTrustDB, "auto-check-trustdb", "@"), + ARGPARSE_s_n (oNoAutoCheckTrustDB, "no-auto-check-trustdb", "@"), + ARGPARSE_s_s (oForceOwnertrust, "force-ownertrust", "@"), +#endif + + + ARGPARSE_header ("Input", N_("Options controlling the input")), + + ARGPARSE_s_n (oMultifile, "multifile", "@"), + ARGPARSE_s_s (oInputSizeHint, "input-size-hint", "@"), + ARGPARSE_s_n (oUtf8Strings, "utf8-strings", "@"), + ARGPARSE_s_n (oNoUtf8Strings, "no-utf8-strings", "@"), + ARGPARSE_p_u (oSetFilesize, "set-filesize", "@"), + ARGPARSE_s_n (oNoLiteral, "no-literal", "@"), + ARGPARSE_s_s (oSetNotation, "set-notation", "@"), + ARGPARSE_s_s (oSigNotation, "sig-notation", "@"), + ARGPARSE_s_s (oCertNotation, "cert-notation", "@"), + ARGPARSE_s_s (oSetPolicyURL, "set-policy-url", "@"), + ARGPARSE_s_s (oSigPolicyURL, "sig-policy-url", "@"), + ARGPARSE_s_s (oCertPolicyURL, "cert-policy-url", "@"), + ARGPARSE_s_s (oSigKeyserverURL, "sig-keyserver-url", "@"), + + + + ARGPARSE_header ("Output", N_("Options controlling the output")), + + ARGPARSE_s_n (oArmor, "armor", N_("create ascii armored output")), + ARGPARSE_s_n (oArmor, "armour", "@"), + ARGPARSE_s_n (oNoArmor, "no-armor", "@"), + ARGPARSE_s_n (oNoArmor, "no-armour", "@"), + ARGPARSE_s_s (oOutput, "output", N_("|FILE|write output to FILE")), + ARGPARSE_p_u (oMaxOutput, "max-output", "@"), + ARGPARSE_s_s (oComment, "comment", "@"), + ARGPARSE_s_n (oDefaultComment, "default-comment", "@"), + ARGPARSE_s_n (oNoComments, "no-comments", "@"), + ARGPARSE_s_n (oEmitVersion, "emit-version", "@"), + ARGPARSE_s_n (oNoEmitVersion, "no-emit-version", "@"), + ARGPARSE_s_n (oNoEmitVersion, "no-version", "@"), /* alias */ + ARGPARSE_s_n (oNotDashEscaped, "not-dash-escaped", "@"), + ARGPARSE_s_n (oEscapeFrom, "escape-from-lines", "@"), + ARGPARSE_s_n (oNoEscapeFrom, "no-escape-from-lines", "@"), + ARGPARSE_s_n (oMimemode, "mimemode", "@"), + ARGPARSE_s_n (oTextmodeShort, NULL, "@"), + ARGPARSE_s_n (oTextmode, "textmode", N_("use canonical text mode")), + ARGPARSE_s_n (oNoTextmode, "no-textmode", "@"), + ARGPARSE_s_s (oSetFilename, "set-filename", "@"), + ARGPARSE_s_n (oForYourEyesOnly, "for-your-eyes-only", "@"), + ARGPARSE_s_n (oNoForYourEyesOnly, "no-for-your-eyes-only", "@"), + ARGPARSE_s_n (oShowNotation, "show-notation", "@"), + ARGPARSE_s_n (oNoShowNotation, "no-show-notation", "@"), + ARGPARSE_s_n (oShowSessionKey, "show-session-key", "@"), + ARGPARSE_s_n (oUseEmbeddedFilename, "use-embedded-filename", "@"), + ARGPARSE_s_n (oNoUseEmbeddedFilename, "no-use-embedded-filename", "@"), + ARGPARSE_s_n (oUnwrap, "unwrap", "@"), + ARGPARSE_s_n (oMangleDosFilenames, "mangle-dos-filenames", "@"), + ARGPARSE_s_n (oNoMangleDosFilenames, "no-mangle-dos-filenames", "@"), + ARGPARSE_s_n (oNoSymkeyCache, "no-symkey-cache", "@"), + ARGPARSE_s_n (oSkipVerify, "skip-verify", "@"), + ARGPARSE_s_n (oListOnly, "list-only", "@"), + ARGPARSE_s_i (oCompress, NULL, + N_("|N|set compress level to N (0 disables)")), + ARGPARSE_s_i (oCompressLevel, "compress-level", "@"), + ARGPARSE_s_i (oBZ2CompressLevel, "bzip2-compress-level", "@"), + ARGPARSE_s_n (oDisableSignerUID, "disable-signer-uid", "@"), + + + ARGPARSE_header ("ImportExport", + N_("Options controlling key import and export")), + + ARGPARSE_s_s (oAutoKeyLocate, "auto-key-locate", + N_("|MECHANISMS|use MECHANISMS to locate keys by mail address")), + ARGPARSE_s_n (oNoAutoKeyLocate, "no-auto-key-locate", "@"), + ARGPARSE_s_n (oAutoKeyImport, "auto-key-import", + N_("import missing key from a signature")), + ARGPARSE_s_n (oNoAutoKeyImport, "no-auto-key-import", "@"), + ARGPARSE_s_n (oAutoKeyRetrieve, "auto-key-retrieve", "@"), + ARGPARSE_s_n (oNoAutoKeyRetrieve, "no-auto-key-retrieve", "@"), + ARGPARSE_s_n (oIncludeKeyBlock, "include-key-block", + N_("include the public key in signatures")), + ARGPARSE_s_n (oNoIncludeKeyBlock, "no-include-key-block", "@"), + ARGPARSE_s_n (oDisableDirmngr, "disable-dirmngr", + N_("disable all access to the dirmngr")), + ARGPARSE_s_s (oKeyServer, "keyserver", "@"), /* Deprecated. */ + ARGPARSE_s_s (oKeyServerOptions, "keyserver-options", "@"), + ARGPARSE_s_s (oKeyOrigin, "key-origin", "@"), + ARGPARSE_s_s (oImportOptions, "import-options", "@"), + ARGPARSE_s_s (oImportFilter, "import-filter", "@"), + ARGPARSE_s_s (oExportOptions, "export-options", "@"), + ARGPARSE_s_s (oExportFilter, "export-filter", "@"), + ARGPARSE_s_n (oMergeOnly, "merge-only", "@" ), + ARGPARSE_s_n (oAllowSecretKeyImport, "allow-secret-key-import", "@"), + + + ARGPARSE_header ("Keylist", N_("Options controlling key listings")), + + ARGPARSE_s_s (oListOptions, "list-options", "@"), + ARGPARSE_s_n (oShowPhotos, "show-photos", "@"), + ARGPARSE_s_n (oNoShowPhotos, "no-show-photos", "@"), + ARGPARSE_s_n (oShowPolicyURL, "show-policy-url", "@"), + ARGPARSE_s_n (oNoShowPolicyURL, "no-show-policy-url", "@"), + ARGPARSE_s_n (oWithColons, "with-colons", "@"), + ARGPARSE_s_n (oWithTofuInfo,"with-tofu-info", "@"), + ARGPARSE_s_n (oWithKeyData,"with-key-data", "@"), + ARGPARSE_s_n (oWithSigList,"with-sig-list", "@"), + ARGPARSE_s_n (oWithSigCheck,"with-sig-check", "@"), + ARGPARSE_s_n (oWithFingerprint, "with-fingerprint", "@"), + ARGPARSE_s_n (oWithSubkeyFingerprint, "with-subkey-fingerprint", "@"), + ARGPARSE_s_n (oWithSubkeyFingerprint, "with-subkey-fingerprints", "@"), + ARGPARSE_s_n (oWithICAOSpelling, "with-icao-spelling", "@"), + ARGPARSE_s_n (oWithKeygrip, "with-keygrip", "@"), + ARGPARSE_s_n (oWithSecret, "with-secret", "@"), + ARGPARSE_s_n (oWithWKDHash, "with-wkd-hash", "@"), + ARGPARSE_s_n (oWithKeyOrigin, "with-key-origin", "@"), + ARGPARSE_s_n (oFastListMode, "fast-list-mode", "@"), + ARGPARSE_s_n (oFixedListMode, "fixed-list-mode", "@"), + ARGPARSE_s_n (oLegacyListMode, "legacy-list-mode", "@"), + ARGPARSE_s_n (oPrintPKARecords, "print-pka-records", "@"), + ARGPARSE_s_n (oPrintDANERecords, "print-dane-records", "@"), + ARGPARSE_s_s (oKeyidFormat, "keyid-format", "@"), + ARGPARSE_s_n (oShowKeyring, "show-keyring", "@"), + + + ARGPARSE_header (NULL, N_("Options to specify keys")), + + ARGPARSE_s_s (oRecipient, "recipient", N_("|USER-ID|encrypt for USER-ID")), + ARGPARSE_s_s (oHiddenRecipient, "hidden-recipient", "@"), + ARGPARSE_s_s (oRecipientFile, "recipient-file", "@"), + ARGPARSE_s_s (oHiddenRecipientFile, "hidden-recipient-file", "@"), + ARGPARSE_s_s (oRecipient, "remote-user", "@"), /* (old option name) */ + ARGPARSE_s_n (oThrowKeyids, "throw-keyids", "@"), + ARGPARSE_s_n (oNoThrowKeyids, "no-throw-keyids", "@"), + ARGPARSE_s_s (oLocalUser, "local-user", + N_("|USER-ID|use USER-ID to sign or decrypt")), + ARGPARSE_s_s (oTrustedKey, "trusted-key", "@"), + ARGPARSE_s_s (oSender, "sender", "@"), + ARGPARSE_s_s (oTrySecretKey, "try-secret-key", "@"), + ARGPARSE_s_n (oTryAllSecrets, "try-all-secrets", "@"), + ARGPARSE_s_n (oNoDefKeyring, "no-default-keyring", "@"), + ARGPARSE_s_n (oNoKeyring, "no-keyring", "@"), + ARGPARSE_s_s (oKeyring, "keyring", "@"), + ARGPARSE_s_s (oPrimaryKeyring, "primary-keyring", "@"), + ARGPARSE_s_s (oSecretKeyring, "secret-keyring", "@"), + ARGPARSE_s_n (oSkipHiddenRecipients, "skip-hidden-recipients", "@"), + ARGPARSE_s_n (oNoSkipHiddenRecipients, "no-skip-hidden-recipients", "@"), + ARGPARSE_s_s (oOverrideSessionKey, "override-session-key", "@"), + ARGPARSE_s_i (oOverrideSessionKeyFD, "override-session-key-fd", "@"), + + + ARGPARSE_header ("Security", N_("Options controlling the security")), + + ARGPARSE_s_i (oS2KMode, "s2k-mode", "@"), + ARGPARSE_s_s (oS2KDigest, "s2k-digest-algo", "@"), + ARGPARSE_s_s (oS2KCipher, "s2k-cipher-algo", "@"), + ARGPARSE_s_i (oS2KCount, "s2k-count", "@"), + ARGPARSE_s_n (oRequireCrossCert, "require-backsigs", "@"), + ARGPARSE_s_n (oRequireCrossCert, "require-cross-certification", "@"), + ARGPARSE_s_n (oNoRequireCrossCert, "no-require-backsigs", "@"), + ARGPARSE_s_n (oNoRequireCrossCert, "no-require-cross-certification", "@"), + ARGPARSE_s_s (oVerifyOptions, "verify-options", "@"), + ARGPARSE_s_n (oEnableSpecialFilenames, "enable-special-filenames", "@"), + ARGPARSE_s_n (oNoRandomSeedFile, "no-random-seed-file", "@"), + ARGPARSE_s_n (oNoSigCache, "no-sig-cache", "@"), + ARGPARSE_s_n (oIgnoreTimeConflict, "ignore-time-conflict", "@"), + ARGPARSE_s_n (oIgnoreValidFrom, "ignore-valid-from", "@"), + ARGPARSE_s_n (oIgnoreCrcError, "ignore-crc-error", "@"), + ARGPARSE_s_n (oIgnoreMDCError, "ignore-mdc-error", "@"), + ARGPARSE_s_s (oDisableCipherAlgo, "disable-cipher-algo", "@"), + ARGPARSE_s_s (oDisablePubkeyAlgo, "disable-pubkey-algo", "@"), + ARGPARSE_s_s (oCipherAlgo, "cipher-algo", "@"), + ARGPARSE_s_s (oDigestAlgo, "digest-algo", "@"), + ARGPARSE_s_s (oCertDigestAlgo, "cert-digest-algo", "@"), + ARGPARSE_s_n (oOverrideComplianceCheck, "override-compliance-check", "@"), + /* Options to override new security defaults. */ + ARGPARSE_s_n (oAllowWeakKeySignatures, "allow-weak-key-signatures", "@"), + ARGPARSE_s_n (oAllowWeakDigestAlgos, "allow-weak-digest-algos", "@"), + ARGPARSE_s_s (oWeakDigest, "weak-digest","@"), + ARGPARSE_s_n (oAllowMultisigVerification, + "allow-multisig-verification", "@"), + ARGPARSE_s_n (oAllowMultipleMessages, "allow-multiple-messages", "@"), + ARGPARSE_s_n (oNoAllowMultipleMessages, "no-allow-multiple-messages", "@"), + + + + ARGPARSE_header (NULL, N_("Options for unattended use")), + + ARGPARSE_s_n (oBatch, "batch", "@"), + ARGPARSE_s_n (oNoBatch, "no-batch", "@"), + ARGPARSE_s_n (oAnswerYes, "yes", "@"), + ARGPARSE_s_n (oAnswerNo, "no", "@"), + ARGPARSE_s_i (oStatusFD, "status-fd", "@"), + ARGPARSE_s_s (oStatusFile, "status-file", "@"), + ARGPARSE_s_i (oAttributeFD, "attribute-fd", "@"), + ARGPARSE_s_s (oAttributeFile, "attribute-file", "@"), + ARGPARSE_s_i (oCommandFD, "command-fd", "@"), + ARGPARSE_s_s (oCommandFile, "command-file", "@"), + ARGPARSE_o_s (oPassphrase, "passphrase", "@"), + ARGPARSE_s_i (oPassphraseFD, "passphrase-fd", "@"), + ARGPARSE_s_s (oPassphraseFile, "passphrase-file", "@"), + ARGPARSE_s_i (oPassphraseRepeat,"passphrase-repeat", "@"), + ARGPARSE_s_s (oPinentryMode, "pinentry-mode", "@"), + ARGPARSE_s_n (oForceSignKey, "force-sign-key", "@"), + + ARGPARSE_header (NULL, N_("Other options")), + + ARGPARSE_s_s (oRequestOrigin, "request-origin", "@"), + ARGPARSE_s_s (oDisplay, "display", "@"), + ARGPARSE_s_s (oTTYname, "ttyname", "@"), + ARGPARSE_s_s (oTTYtype, "ttytype", "@"), + ARGPARSE_s_s (oLCctype, "lc-ctype", "@"), + ARGPARSE_s_s (oLCmessages, "lc-messages","@"), + ARGPARSE_s_s (oXauthority, "xauthority", "@"), + ARGPARSE_s_n (oNoAutostart, "no-autostart", "@"), + ARGPARSE_s_n (oForbidGenKey, "forbid-gen-key", "@"), + ARGPARSE_s_n (oRequireCompliance, "require-compliance", "@"), + /* Options which can be used in special circumstances. They are not + * published and we hope they are never required. */ + ARGPARSE_s_n (oUseOnlyOpenPGPCard, "use-only-openpgp-card", "@"), + /* Esoteric compatibility options. */ + ARGPARSE_s_n (oRFC2440Text, "rfc2440-text", "@"), + ARGPARSE_s_n (oNoRFC2440Text, "no-rfc2440-text", "@"), + + ARGPARSE_header (NULL, ""), /* Stop the header group. */ + + + /* Aliases. I constantly mistype these, and assume other people do + as well. */ + ARGPARSE_s_s (oPersonalCipherPreferences, "personal-cipher-prefs", "@"), + ARGPARSE_s_s (oPersonalDigestPreferences, "personal-digest-prefs", "@"), + ARGPARSE_s_s (oPersonalCompressPreferences, "personal-compress-prefs", "@"), + + /* 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. */ + ARGPARSE_s_s (oLocalUser, "sign-with", "@"), + ARGPARSE_s_s (oRecipient, "user", "@"), + + + /* Dummy options with warnings. */ + ARGPARSE_s_n (oUseAgent, "use-agent", "@"), + ARGPARSE_s_n (oNoUseAgent, "no-use-agent", "@"), + ARGPARSE_s_s (oGpgAgentInfo, "gpg-agent-info", "@"), + ARGPARSE_s_s (oReaderPort, "reader-port", "@"), + ARGPARSE_s_s (octapiDriver, "ctapi-driver", "@"), + ARGPARSE_s_s (opcscDriver, "pcsc-driver", "@"), + ARGPARSE_s_n (oDisableCCID, "disable-ccid", "@"), + ARGPARSE_s_n (oHonorHttpProxy, "honor-http-proxy", "@"), + ARGPARSE_s_s (oTOFUDBFormat, "tofu-db-format", "@"), + + /* Dummy options. */ + ARGPARSE_ignore (oStrict, "strict"), + ARGPARSE_ignore (oNoStrict, "no-strict"), + ARGPARSE_ignore (oLoadExtension, "load-extension"), /* from 1.4. */ + + ARGPARSE_s_n (oNoop, "sk-comments", "@"), + ARGPARSE_s_n (oNoop, "no-sk-comments", "@"), + ARGPARSE_s_n (oNoop, "compress-keys", "@"), + ARGPARSE_s_n (oNoop, "compress-sigs", "@"), + ARGPARSE_s_n (oNoop, "force-v3-sigs", "@"), + ARGPARSE_s_n (oNoop, "no-force-v3-sigs", "@"), + ARGPARSE_s_n (oNoop, "force-v4-certs", "@"), + ARGPARSE_s_n (oNoop, "no-force-v4-certs", "@"), + ARGPARSE_s_n (oNoop, "no-mdc-warning", "@"), + ARGPARSE_s_n (oNoop, "force-mdc", "@"), + ARGPARSE_s_n (oNoop, "no-force-mdc", "@"), + ARGPARSE_s_n (oNoop, "disable-mdc", "@"), + ARGPARSE_s_n (oNoop, "no-disable-mdc", "@"), + + + ARGPARSE_group (302, N_( + "@\n(See the man page for a complete listing of all commands and options)\n" + )), + + ARGPARSE_group (303, N_("@\nExamples:\n\n" + " -se -r Bob [file] sign and encrypt for user Bob\n" + " --clear-sign [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")), + + + ARGPARSE_end () +}; + + +/* The list of supported debug flags. */ +static struct debug_flags_s debug_flags [] = + { + { DBG_PACKET_VALUE , "packet" }, + { DBG_MPI_VALUE , "mpi" }, + { DBG_CRYPTO_VALUE , "crypto" }, + { DBG_FILTER_VALUE , "filter" }, + { DBG_IOBUF_VALUE , "iobuf" }, + { DBG_MEMORY_VALUE , "memory" }, + { DBG_CACHE_VALUE , "cache" }, + { DBG_MEMSTAT_VALUE, "memstat" }, + { DBG_TRUST_VALUE , "trust" }, + { DBG_HASHING_VALUE, "hashing" }, + { DBG_IPC_VALUE , "ipc" }, + { DBG_CLOCK_VALUE , "clock" }, + { DBG_LOOKUP_VALUE , "lookup" }, + { DBG_EXTPROG_VALUE, "extprog" }, + { 0, NULL } + }; + + +#ifdef ENABLE_SELINUX_HACKS +#define ALWAYS_ADD_KEYRINGS 1 +#else +#define ALWAYS_ADD_KEYRINGS 0 +#endif + +/* The list of the default AKL methods. */ +#define DEFAULT_AKL_LIST "local,wkd" + + +int g10_errors_seen = 0; + +static int utf8_strings = +#ifdef HAVE_W32_SYSTEM + 1 +#else + 0 +#endif + ; +static int maybe_setuid = 1; + +/* Collection of options used only in this module. */ +static struct { + unsigned int forbid_gen_key; +} mopt; + + +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 ); +static void emergency_cleanup (void); +static void read_sessionkey_from_fd (int fd); + + +static char * +make_libversion (const char *libname, const char *(*getfnc)(const char*)) +{ + const char *s; + char *result; + + if (maybe_setuid) + { + gcry_control (GCRYCTL_INIT_SECMEM, 0, 0); /* Drop setuid. */ + maybe_setuid = 0; + } + s = getfnc (NULL); + result = xmalloc (strlen (libname) + 1 + strlen (s) + 1); + strcpy (stpcpy (stpcpy (result, libname), " "), s); + return result; +} + + +static int +build_list_pk_test_algo (int algo) +{ + /* Show only one "RSA" string. If RSA_E or RSA_S is available RSA + is also available. */ + if (algo == PUBKEY_ALGO_RSA_E + || algo == PUBKEY_ALGO_RSA_S) + return GPG_ERR_DIGEST_ALGO; + + return openpgp_pk_test_algo (algo); +} + +static const char * +build_list_pk_algo_name (int algo) +{ + return openpgp_pk_algo_name (algo); +} + +static int +build_list_cipher_test_algo (int algo) +{ + return openpgp_cipher_test_algo (algo); +} + +static const char * +build_list_cipher_algo_name (int algo) +{ + return openpgp_cipher_algo_name (algo); +} + +static int +build_list_md_test_algo (int algo) +{ + /* By default we do not accept MD5 based signatures. To avoid + confusion we do not announce support for it either. */ + if (algo == DIGEST_ALGO_MD5) + return GPG_ERR_DIGEST_ALGO; + + return openpgp_md_test_algo (algo); +} + +static const char * +build_list_md_algo_name (int algo) +{ + return openpgp_md_algo_name (algo); +} + + +static const char * +my_strusage( int level ) +{ + static char *digests, *pubkeys, *ciphers, *zips, *ver_gcry; + const char *p; + + switch (level) + { + case 9: p = "GPL-3.0-or-later"; break; + case 11: p = "@GPG@ (@GNUPG@)"; + break; + case 13: p = VERSION; break; + case 14: p = GNUPG_DEF_COPYRIGHT_LINE; break; + case 17: p = PRINTABLE_OS_NAME; break; + case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break; + + case 20: + if (!ver_gcry) + ver_gcry = make_libversion ("libgcrypt", gcry_check_version); + p = ver_gcry; + break; + +#ifdef IS_DEVELOPMENT_VERSION + case 25: + p="NOTE: THIS IS A DEVELOPMENT VERSION!"; + break; + case 26: + p="It is only intended for test purposes and should NOT be"; + break; + case 27: + 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 = gnupg_homedir (); break; +#else /* __riscos__ */ + case 32: p = make_filename(gnupg_homedir (), NULL); break; +#endif /* __riscos__ */ + case 33: p = _("\nSupported algorithms:\n"); break; + case 34: + if (!pubkeys) + pubkeys = build_list (_("Pubkey: "), 1, + build_list_pk_algo_name, + build_list_pk_test_algo ); + p = pubkeys; + break; + case 35: + if( !ciphers ) + ciphers = build_list(_("Cipher: "), 'S', + build_list_cipher_algo_name, + build_list_cipher_test_algo ); + p = ciphers; + break; + case 36: + if( !digests ) + digests = build_list(_("Hash: "), 'H', + build_list_md_algo_name, + build_list_md_test_algo ); + p = digests; + break; + case 37: + if( !zips ) + zips = build_list(_("Compression: "),'Z', + compress_algo_to_string, + check_compress_algo); + p = zips; + break; + + case 95: + p = "1"; /* <-- Enable globbing under Windows (see init.c) */ + break; + + default: p = NULL; + } + return p; +} + + +static char * +build_list (const char *text, char letter, + const char * (*mapf)(int), int (*chkf)(int)) +{ + membuf_t mb; + int indent; + int i, j, len; + const char *s; + char *string; + + if (maybe_setuid) + gcry_control (GCRYCTL_INIT_SECMEM, 0, 0); /* Drop setuid. */ + + indent = utf8_charcount (text, -1); + len = 0; + init_membuf (&mb, 512); + + for (i=0; i <= 110; i++ ) + { + if (!chkf (i) && (s = mapf (i))) + { + if (mb.len - len > 60) + { + put_membuf_str (&mb, ",\n"); + len = mb.len; + for (j=0; j < indent; j++) + put_membuf_str (&mb, " "); + } + else if (mb.len) + put_membuf_str (&mb, ", "); + else + put_membuf_str (&mb, text); + + put_membuf_str (&mb, s); + if (opt.verbose && letter) + { + char num[20]; + if (letter == 1) + snprintf (num, sizeof num, " (%d)", i); + else + snprintf (num, sizeof num, " (%c%d)", letter, i); + put_membuf_str (&mb, num); + } + } + } + if (mb.len) + put_membuf_str (&mb, "\n"); + put_membuf (&mb, "", 1); + + string = get_membuf (&mb, NULL); + return xrealloc (string, strlen (string)+1); +} + + +static void +wrong_args( const char *text) +{ + es_fprintf (es_stderr, _("usage: %s [options] %s\n"), GPG_NAME, text); + log_inc_errorcount (); + 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_opt_session_env (const char *name, const char *value) +{ + gpg_error_t err; + + err = session_env_setenv (opt.session_env, name, value); + if (err) + log_fatal ("error setting session environment: %s\n", + gpg_strerror (err)); +} + + +/* Setup the debugging. With a LEVEL of NULL only the active debug + flags are propagated to the subsystems. With LEVEL set, a specific + set of debug flags is set; thus overriding all flags already + set. */ +static void +set_debug (const char *level) +{ + int numok = (level && digitp (level)); + int numlvl = numok? atoi (level) : 0; + + if (!level) + ; + else if (!strcmp (level, "none") || (numok && numlvl < 1)) + opt.debug = 0; + else if (!strcmp (level, "basic") || (numok && numlvl <= 2)) + opt.debug = DBG_MEMSTAT_VALUE; + else if (!strcmp (level, "advanced") || (numok && numlvl <= 5)) + opt.debug = DBG_MEMSTAT_VALUE|DBG_TRUST_VALUE|DBG_EXTPROG_VALUE; + else if (!strcmp (level, "expert") || (numok && numlvl <= 8)) + opt.debug = (DBG_MEMSTAT_VALUE|DBG_TRUST_VALUE|DBG_EXTPROG_VALUE + |DBG_CACHE_VALUE|DBG_LOOKUP|DBG_FILTER_VALUE|DBG_PACKET_VALUE); + else if (!strcmp (level, "guru") || numok) + { + opt.debug = ~0; + /* Unless the "guru" string has been used we don't want to allow + hashing debugging. The rationale is that people tend to + select the highest debug value and would then clutter their + disk with debug files which may reveal confidential data. */ + if (numok) + opt.debug &= ~(DBG_HASHING_VALUE); + } + else + { + log_error (_("invalid debug-level '%s' given\n"), level); + g10_exit (2); + } + + if ((opt.debug & DBG_MEMORY_VALUE)) + memory_debug_mode = 1; + if ((opt.debug & DBG_MEMSTAT_VALUE)) + memory_stat_debug_mode = 1; + if (DBG_MPI) + gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 2); + if (DBG_CRYPTO) + gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 1); + if ((opt.debug & DBG_IOBUF_VALUE)) + iobuf_debug_mode = 1; + gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose); + + if (opt.debug) + parse_debug_flag (NULL, &opt.debug, debug_flags); + + /* Make sure that we are --verbose in debug mode. */ + if (opt.debug && !opt.verbose) + opt.verbose = 1; + if (opt.debug && opt.quiet) + opt.quiet = 0; +} + + +/* 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 HAVE_W32_SYSTEM + 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, int binary) +{ +#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. */ + (void)fname; + (void)for_write; + (void)binary; + return -1; +#else + int fd; + + if (binary) + binary = MY_O_BINARY; + +/* if (is_secured_filename (fname)) */ +/* { */ +/* fd = -1; */ +/* gpg_err_set_errno (EPERM); */ +/* } */ +/* else */ +/* { */ + do + { + if (for_write) + fd = gnupg_open (fname, O_CREAT | O_TRUNC | O_WRONLY | binary, + S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP); + else + fd = gnupg_open (fname, O_RDONLY | binary, 0); + } + 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 == 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; + + log_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 (gnupg_homedir (), tmppath, + strlen (gnupg_homedir ()))) + { + ret=homedir_cache; + goto end; + } + + /* It's okay if the file or directory doesn't exist */ + if (gnupg_stat (tmppath,&statbuf)) + { + 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 (gnupg_stat (dir,&dirbuf) || !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; + +#else /*!(HAVE_STAT && !HAVE_DOSISH_SYSTEM)*/ + (void)path; + (void)item; + return 0; +#endif /*!(HAVE_STAT && !HAVE_DOSISH_SYSTEM)*/ +} + + +/* Print the OpenPGP defined algo numbers. */ +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 + es_printf (";"); + es_printf ("%d",i); + } + } +} + + +static void +print_algo_names(int (*checker)(int),const char *(*mapper)(int)) +{ + int i,first=1; + + for(i=0;i<=110;i++) + { + if(!checker(i)) + { + if(first) + first=0; + else + es_printf (";"); + es_printf ("%s",mapper(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; + char *name = NULL; + const char *s; + struct groupitem *giter; + int first, iter; + + if(!opt.with_colons) + return; + + while(show_all || (name=strsep(&items," "))) + { + int any=0; + + if(show_all || ascii_strcasecmp(name,"group")==0) + { + for (giter = opt.grouplist; giter; giter = giter->next) + { + strlist_t sl; + + es_fprintf (es_stdout, "cfg:group:"); + es_write_sanitized (es_stdout, giter->name, strlen(giter->name), + ":", NULL); + es_putc (':', es_stdout); + + for(sl=giter->values; sl; sl=sl->next) + { + es_write_sanitized (es_stdout, sl->d, strlen (sl->d), + ":;", NULL); + if(sl->next) + es_printf(";"); + } + + es_printf("\n"); + } + + any=1; + } + + if(show_all || ascii_strcasecmp(name,"version")==0) + { + es_printf("cfg:version:"); + es_write_sanitized (es_stdout, VERSION, strlen(VERSION), ":", NULL); + es_printf ("\n"); + any=1; + } + + if(show_all || ascii_strcasecmp(name,"pubkey")==0) + { + es_printf ("cfg:pubkey:"); + print_algo_numbers (build_list_pk_test_algo); + es_printf ("\n"); + any=1; + } + + if(show_all || ascii_strcasecmp(name,"pubkeyname")==0) + { + es_printf ("cfg:pubkeyname:"); + print_algo_names (build_list_pk_test_algo, + build_list_pk_algo_name); + es_printf ("\n"); + any=1; + } + + if(show_all || ascii_strcasecmp(name,"cipher")==0) + { + es_printf ("cfg:cipher:"); + print_algo_numbers (build_list_cipher_test_algo); + es_printf ("\n"); + any=1; + } + + if (show_all || !ascii_strcasecmp (name,"ciphername")) + { + es_printf ("cfg:ciphername:"); + print_algo_names (build_list_cipher_test_algo, + build_list_cipher_algo_name); + es_printf ("\n"); + any = 1; + } + + if(show_all + || ascii_strcasecmp(name,"digest")==0 + || ascii_strcasecmp(name,"hash")==0) + { + es_printf ("cfg:digest:"); + print_algo_numbers (build_list_md_test_algo); + es_printf ("\n"); + any=1; + } + + if (show_all + || !ascii_strcasecmp(name,"digestname") + || !ascii_strcasecmp(name,"hashname")) + { + es_printf ("cfg:digestname:"); + print_algo_names (build_list_md_test_algo, + build_list_md_algo_name); + es_printf ("\n"); + any=1; + } + + if(show_all || ascii_strcasecmp(name,"compress")==0) + { + es_printf ("cfg:compress:"); + print_algo_numbers(check_compress_algo); + es_printf ("\n"); + any=1; + } + + if(show_all || ascii_strcasecmp (name, "compressname") == 0) + { + es_printf ("cfg:compressname:"); + print_algo_names (check_compress_algo, + compress_algo_to_string); + es_printf ("\n"); + any=1; + } + + if (show_all || !ascii_strcasecmp(name,"ccid-reader-id")) + { + /* We ignore this for GnuPG 1.4 backward compatibility. */ + any=1; + } + + if (show_all || !ascii_strcasecmp (name,"curve")) + { + es_printf ("cfg:curve:"); + for (iter=0, first=1; (s = openpgp_enum_curves (&iter)); first=0) + es_printf ("%s%s", first?"":";", s); + es_printf ("\n"); + any=1; + } + + /* Curve OIDs are rarely useful and thus only printed if requested. */ + if (name && !ascii_strcasecmp (name,"curveoid")) + { + es_printf ("cfg:curveoid:"); + for (iter=0, first=1; (s = openpgp_enum_curves (&iter)); first = 0) + { + s = openpgp_curve_to_oid (s, NULL, NULL); + es_printf ("%s%s", first?"":";", s? s:"[?]"); + } + es_printf ("\n"); + 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) +{ + char *configfile_esc = percent_escape (configfile, NULL); + + es_printf ("debug-level:%lu:\"none:\n", GC_OPT_FLAG_DEFAULT); + es_printf ("compliance:%lu:\"%s:\n", GC_OPT_FLAG_DEFAULT, "gnupg"); + + /* The next one is an info only item and should match the macros at + the top of keygen.c */ + es_printf ("default_pubkey_algo:%lu:\"%s:\n", GC_OPT_FLAG_DEFAULT, + get_default_pubkey_algo ()); + /* This info only mode tells whether the we are running in de-vs + * compliance mode. This does not test all parameters but the basic + * conditions like a proper RNG and Libgcrypt. */ + es_printf ("compliance_de_vs:%lu:%d:\n", GC_OPT_FLAG_DEFAULT, + opt.compliance==CO_DE_VS && gnupg_rng_is_compliant (CO_DE_VS)); + + xfree (configfile_esc); +} + + +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-usage",LIST_SHOW_USAGE,NULL, + N_("show key usage information 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}, + {"show-only-fpr-mbox",LIST_SHOW_ONLY_FPR_MBOX, 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[13].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;imagic = SERVER_CONTROL_MAGIC; +} + + +/* This function is called to deinitialize a control object. It is + not deallocated. */ +static void +gpg_deinit_default_ctrl (ctrl_t ctrl) +{ +#ifdef USE_TOFU + tofu_closedbs (ctrl); +#endif + gpg_dirmngr_deinit_session_data (ctrl); + + keydb_release (ctrl->cached_getkey_kdb); +} + + +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_t sl; + strlist_t remusr = NULL; + strlist_t locusr = NULL; + strlist_t nrings = NULL; + armor_filter_context_t *afx = NULL; + int detached_sig = 0; + char *last_configname = NULL; + const char *configname = NULL; /* NULL or points to last_configname. + * NULL also indicates that we are + * processing options from the cmdline. */ + int debug_argparser = 0; + int default_keyring = 1; + int greeting = 0; + int nogreeting = 0; + char *logfile = NULL; + int use_random_seed = 1; + enum cmd_and_opt_values cmd = 0; + const char *debug_level = NULL; +#ifndef NO_TRUST_MODELS + const char *trustdb_name = NULL; +#endif /*!NO_TRUST_MODELS*/ + 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 ovrseskeyfd = -1; + int fpr_maybe_cmd = 0; /* --fingerprint maybe a command. */ + int any_explicit_recipient = 0; + int default_akl = 1; + int require_secmem = 0; + int got_secmem = 0; + struct assuan_malloc_hooks malloc_hooks; + ctrl_t ctrl; + + static int print_dane_records; + static int print_pka_records; + + +#ifdef __riscos__ + opt.lock_once = 1; +#endif /* __riscos__ */ + + /* 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. */ + early_system_init (); + gnupg_reopen_std (GPG_NAME); + trap_unaligned (); + gnupg_rl_initialize (); + set_strusage (my_strusage); + gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN); + log_set_prefix (GPG_NAME, GPGRT_LOG_WITH_PREFIX|GPGRT_LOG_NO_REGISTRY); + + /* Make sure that our subsystems are ready. */ + i18n_init(); + init_common_subsystems (&argc, &argv); + + /* Use our own logging handler for Libcgrypt. */ + setup_libgcrypt_logging (); + + /* Put random number into secure memory */ + gcry_control (GCRYCTL_USE_SECURE_RNDPOOL); + + may_coredump = disable_core_dumps(); + + gnupg_init_signals (0, emergency_cleanup); + + dotlock_create (NULL, 0); /* Register lock file cleanup. */ + + /* Tell the compliance module who we are. */ + gnupg_initialize_compliance (GNUPG_MODULE_NAME_GPG); + + opt.autostart = 1; + opt.session_env = session_env_new (); + if (!opt.session_env) + log_fatal ("error allocating session environment block: %s\n", + strerror (errno)); + + 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 = 0; /* Auto-calibrate when needed. */ + opt.s2k_cipher_algo = DEFAULT_CIPHER_ALGO; + opt.completes_needed = 1; + opt.marginals_needed = 3; + opt.max_cert_depth = 5; + opt.escape_from = 1; + opt.flags.require_cross_cert = 1; + opt.import_options = IMPORT_REPAIR_KEYS; + opt.export_options = EXPORT_ATTRIBUTES; + opt.keyserver_options.import_options = (IMPORT_REPAIR_KEYS + | IMPORT_REPAIR_PKS_SUBKEY_BUG + | IMPORT_SELF_SIGS_ONLY + | IMPORT_CLEAN); + opt.keyserver_options.export_options = EXPORT_ATTRIBUTES; + opt.keyserver_options.options = KEYSERVER_HONOR_PKA_RECORD; + opt.verify_options = (LIST_SHOW_UID_VALIDITY + | VERIFY_SHOW_POLICY_URLS + | VERIFY_SHOW_STD_NOTATIONS + | VERIFY_SHOW_KEYSERVER_URLS); + opt.list_options = (LIST_SHOW_UID_VALIDITY + | LIST_SHOW_USAGE); +#ifdef NO_TRUST_MODELS + opt.trust_model = TM_ALWAYS; +#else + opt.trust_model = TM_AUTO; +#endif + opt.tofu_default_policy = TOFU_POLICY_AUTO; + opt.mangle_dos_filenames = 0; + opt.min_cert_level = 2; + set_screen_dimensions (); + opt.keyid_format = KF_NONE; + opt.def_sig_expire = "0"; + opt.def_cert_expire = "0"; + gnupg_set_homedir (NULL); + opt.passphrase_repeat = 1; + opt.emit_version = 0; + opt.weak_digests = NULL; + + /* Check special options given on the command line. */ + orig_argc = argc; + orig_argv = argv; + pargs.argc = &argc; + pargs.argv = &argv; + pargs.flags= (ARGPARSE_FLAG_KEEP | ARGPARSE_FLAG_NOVERSION); + while (gnupg_argparse (NULL, &pargs, opts)) + { + switch (pargs.r_opt) + { + case oDebug: + case oDebugAll: + debug_argparser++; + break; + + case oDebugIOLBF: + es_setvbuf (es_stdout, NULL, _IOLBF, 0); + break; + + case oNoOptions: + /* Set here here because the homedir would otherwise be + * created before main option parsing starts. */ + opt.no_homedir_creation = 1; + break; + + case oHomedir: + gnupg_set_homedir (pargs.r.ret_str); + break; + + case oNoPermissionWarn: + opt.no_perm_warn = 1; + break; + } + } + /* Reset the flags. */ + pargs.flags &= ~(ARGPARSE_FLAG_KEEP | ARGPARSE_FLAG_NOVERSION); + +#ifdef HAVE_DOSISH_SYSTEM + if ( strchr (gnupg_homedir (), '\\') ) { + char *d, *buf = xmalloc (strlen (gnupg_homedir ())+1); + const char *s; + for (d=buf, s = gnupg_homedir (); *s; s++) + { + *d++ = *s == '\\'? '/': *s; +#ifdef HAVE_W32_SYSTEM + if (s[1] && IsDBCSLeadByte (*s)) + *d++ = *++s; +#endif + } + *d = 0; + gnupg_set_homedir (buf); + } +#endif + + /* Initialize the secure memory. */ + if (!gcry_control (GCRYCTL_INIT_SECMEM, SECMEM_BUFFER_SIZE, 0)) + got_secmem = 1; +#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 + maybe_setuid = 0; + + /* Okay, we are now working under our real uid */ + + /* malloc hooks go here ... */ + malloc_hooks.malloc = gcry_malloc; + malloc_hooks.realloc = gcry_realloc; + malloc_hooks.free = gcry_free; + assuan_set_malloc_hooks (&malloc_hooks); + assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT); + setup_libassuan_logging (&opt.debug, NULL); + + /* Set default options which require that malloc stuff is ready. */ + additional_weak_digest ("MD5"); + parse_auto_key_locate (DEFAULT_AKL_LIST); + + argc = orig_argc; + argv = orig_argv; + pargs.argc = &argc; + pargs.argv = &argv; + /* We are re-using the struct, thus the reset flag. We OR the + * flags so that the internal intialized flag won't be cleared. */ + pargs.flags |= (ARGPARSE_FLAG_RESET + | ARGPARSE_FLAG_KEEP + | ARGPARSE_FLAG_SYS + | ARGPARSE_FLAG_USER + | ARGPARSE_FLAG_USERVERS); + + /* By this point we have a homedir, and cannot change it. */ + check_permissions (gnupg_homedir (), 0); + + /* The configuraton directories for use by gpgrt_argparser. */ + gnupg_set_confdir (GNUPG_CONFDIR_SYS, gnupg_sysconfdir ()); + gnupg_set_confdir (GNUPG_CONFDIR_USER, gnupg_homedir ()); + + while (gnupg_argparser (&pargs, opts, GPG_NAME EXTSEP_S "conf")) + { + switch (pargs.r_opt) + { + case ARGPARSE_CONFFILE: + if (debug_argparser) + log_info (_("reading options from '%s'\n"), + pargs.r_type? pargs.r.ret_str: "[cmdline]"); + if (pargs.r_type) + { + xfree (last_configname); + last_configname = xstrdup (pargs.r.ret_str); + configname = last_configname; + if (is_secured_filename (configname)) + { + pargs.r_opt = ARGPARSE_PERMISSION_ERROR; + pargs.err = ARGPARSE_PRINT_ERROR; + } + else if (strncmp (configname, gnupg_sysconfdir (), + strlen (gnupg_sysconfdir ()))) + { + /* This is not the global config file and thus we + * need to check the permissions: If the 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. */ + if (check_permissions (configname, 1)) + opt.exec_disable=1; + } + } + else + configname = NULL; + break; + + /* case oOptions: + * case oNoOptions: + * We will never see these options here because + * gpgrt_argparse handles them for us. + */ + + case aListConfig: + case aListGcryptConfig: + case aGPGConfList: + case aGPGConfTest: + set_cmd (&cmd, pargs.r_opt); + /* Do not register a keyring for these commands. */ + default_keyring = -1; + break; + + case aCheckKeys: + case aListPackets: + case aImport: + case aFastImport: + case aSendKeys: + case aRecvKeys: + case aSearchKeys: + case aRefreshKeys: + case aFetchKeys: + case aExport: +#ifdef ENABLE_CARD_SUPPORT + case aCardStatus: + case aCardEdit: + case aChangePIN: +#endif /* ENABLE_CARD_SUPPORT*/ + case aListKeys: + case aLocateKeys: + case aLocateExtKeys: + case aListSigs: + case aExportSecret: + case aExportSecretSub: + case aExportSshKey: + case aSym: + case aClearsign: + case aGenRevoke: + case aDesigRevoke: + case aPrimegen: + case aGenRandom: + case aPrintMD: + case aPrintMDs: + case aListTrustDB: + case aCheckTrustDB: + case aUpdateTrustDB: + case aFixTrustDB: + case aListTrustPath: + case aDeArmor: + case aEnArmor: + case aSign: + case aQuickSignKey: + case aQuickLSignKey: + case aQuickRevSig: + case aSignKey: + case aLSignKey: + case aStore: + case aQuickKeygen: + case aQuickAddUid: + case aQuickAddKey: + case aQuickRevUid: + case aQuickSetExpire: + case aQuickSetPrimaryUid: + case aExportOwnerTrust: + case aImportOwnerTrust: + case aRebuildKeydbCaches: + set_cmd (&cmd, pargs.r_opt); + break; + + case aKeygen: + case aFullKeygen: + case aEditKey: + case aDeleteSecretKeys: + case aDeleteSecretAndPublicKeys: + case aDeleteKeys: + case aPasswd: + set_cmd (&cmd, pargs.r_opt); + greeting=1; + break; + + case aShowKeys: + set_cmd (&cmd, pargs.r_opt); + opt.import_options |= IMPORT_SHOW; + opt.import_options |= IMPORT_DRY_RUN; + opt.import_options &= ~IMPORT_REPAIR_KEYS; + opt.list_options |= LIST_SHOW_UNUSABLE_UIDS; + opt.list_options |= LIST_SHOW_UNUSABLE_SUBKEYS; + opt.list_options |= LIST_SHOW_NOTATIONS; + opt.list_options |= LIST_SHOW_POLICY_URLS; + break; + + case aDetachedSign: detached_sig = 1; set_cmd( &cmd, aSign ); 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 aServer: + set_cmd (&cmd, pargs.r_opt); + opt.batch = 1; + break; + + case aTOFUPolicy: + set_cmd (&cmd, pargs.r_opt); + break; + + 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 oInputSizeHint: + opt.input_size_hint = string_to_u64 (pargs.r.ret_str); + 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: + opt.verbose++; + gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose); + opt.list_options|=LIST_SHOW_UNUSABLE_UIDS; + opt.list_options|=LIST_SHOW_UNUSABLE_SUBKEYS; + break; + + case oBatch: + opt.batch = 1; + nogreeting = 1; + break; + + case oUseAgent: /* Dummy. */ + break; + + case oNoUseAgent: + obsolete_option (configname, pargs.lineno, "no-use-agent"); + break; + case oGpgAgentInfo: + obsolete_option (configname, pargs.lineno, "gpg-agent-info"); + break; + case oReaderPort: + obsolete_scdaemon_option (configname, pargs.lineno, "reader-port"); + break; + case octapiDriver: + obsolete_scdaemon_option (configname, pargs.lineno, "ctapi-driver"); + break; + case opcscDriver: + obsolete_scdaemon_option (configname, pargs.lineno, "pcsc-driver"); + break; + case oDisableCCID: + obsolete_scdaemon_option (configname, pargs.lineno, "disable-ccid"); + break; + case oHonorHttpProxy: + obsolete_option (configname, pargs.lineno, "honor-http-proxy"); + break; + + case oAnswerYes: opt.answer_yes = 1; break; + case oAnswerNo: opt.answer_no = 1; break; + + case oForceSignKey: opt.flags.force_sign_key = 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 = KEYDB_RESOURCE_FLAG_PRIMARY; + break; + case oShowKeyring: + deprecated_warning(configname,pargs.lineno,"--show-keyring", + "--list-options ","show-keyring"); + opt.list_options|=LIST_SHOW_KEYRING; + break; + + case oDebug: + if (parse_debug_flag (pargs.r.ret_str, &opt.debug, debug_flags)) + { + pargs.r_opt = ARGPARSE_INVALID_ARG; + pargs.err = ARGPARSE_PRINT_ERROR; + } + break; + + case oDebugAll: opt.debug = ~0; break; + case oDebugLevel: debug_level = pargs.r.ret_str; break; + + case oDebugIOLBF: break; /* Already set in pre-parse step. */ + + case oStatusFD: + set_status_fd ( translate_sys2libc_fd_int (pargs.r.ret_int, 1) ); + break; + case oStatusFile: + set_status_fd ( open_info_file (pargs.r.ret_str, 1, 0) ); + break; + case oAttributeFD: + set_attrib_fd ( translate_sys2libc_fd_int (pargs.r.ret_int, 1) ); + break; + case oAttributeFile: + set_attrib_fd ( open_info_file (pargs.r.ret_str, 1, 1) ); + break; + case oLoggerFD: + log_set_fd (translate_sys2libc_fd_int (pargs.r.ret_int, 1)); + break; + case oLoggerFile: + logfile = pargs.r.ret_str; + break; + + case oWithFingerprint: + opt.with_fingerprint = 1; + opt.fingerprint++; + break; + case oWithSubkeyFingerprint: + opt.with_subkey_fingerprint = 1; + break; + case oWithICAOSpelling: + opt.with_icao_spelling = 1; + break; + case oFingerprint: + opt.fingerprint++; + fpr_maybe_cmd = 1; + break; + + case oWithKeygrip: + opt.with_keygrip = 1; + break; + + case oWithSecret: + opt.with_secret = 1; + break; + + case oWithWKDHash: + opt.with_wkd_hash = 1; + break; + + case oWithKeyOrigin: + opt.with_key_origin = 1; + break; + + case oSecretKeyring: + obsolete_option (configname, pargs.lineno, "secret-keyring"); + break; + + case oNoArmor: opt.no_armor=1; opt.armor=0; break; + + case oNoDefKeyring: + if (default_keyring > 0) + default_keyring = 0; + break; + case oNoKeyring: + default_keyring = -1; + break; + + case oNoGreeting: nogreeting = 1; break; + case oNoVerbose: + opt.verbose = 0; + gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose); + opt.list_sigs=0; + break; + case oQuickRandom: + gcry_control (GCRYCTL_ENABLE_QUICK_RANDOM, 0); + break; + case oEmitVersion: opt.emit_version++; break; + case oNoEmitVersion: opt.emit_version=0; 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; + +#ifndef NO_TRUST_MODELS + case oTrustDBName: trustdb_name = pargs.r.ret_str; break; + +#endif /*!NO_TRUST_MODELS*/ + case oDefaultKey: + sl = add_to_strlist (&opt.def_secret_key, pargs.r.ret_str); + sl->flags = (pargs.r_opt << PK_LIST_SHIFT); + if (configname) + sl->flags |= PK_LIST_CONFIG; + break; + case oDefRecipient: + if( *pargs.r.ret_str ) + { + xfree (opt.def_recipient); + 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 oHomedir: break; + case oNoBatch: opt.batch = 0; break; + + case oWithTofuInfo: opt.with_tofu_info = 1; break; + + case oWithKeyData: opt.with_key_data=1; /*FALLTHRU*/ + case oWithColons: opt.with_colons=':'; break; + + case oWithSigCheck: opt.check_sigs = 1; /*FALLTHRU*/ + case oWithSigList: opt.list_sigs = 1; break; + + case oSkipVerify: opt.skip_verify=1; break; + + case oSkipHiddenRecipients: opt.skip_hidden_recipients = 1; break; + case oNoSkipHiddenRecipients: opt.skip_hidden_recipients = 0; break; + + case aListSecretKeys: set_cmd( &cmd, aListSecretKeys); break; + +#ifndef NO_TRUST_MODELS + /* 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; +#endif /*!NO_TRUST_MODELS*/ + case oTOFUDefaultPolicy: + opt.tofu_default_policy = parse_tofu_policy (pargs.r.ret_str); + break; + case oTOFUDBFormat: + obsolete_option (configname, pargs.lineno, "tofu-db-format"); + 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 oCompliance: + { + int compliance = gnupg_parse_compliance_option + (pargs.r.ret_str, + compliance_options, DIM (compliance_options), + opt.quiet); + if (compliance < 0) + g10_exit (1); + set_compliance_option (compliance); + } + break; + case oOpenPGP: + case oRFC2440: + case oRFC4880: + case oRFC4880bis: + case oPGP6: + case oPGP7: + case oPGP8: + case oGnuPG: + set_compliance_option (pargs.r_opt); + break; + + case oMinRSALength: opt.min_rsa_length = pargs.r.ret_ulong; break; + + case oRFC2440Text: opt.rfc2440_text=1; break; + case oNoRFC2440Text: opt.rfc2440_text=0; 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,pargs.lineno,"--show-policy-url", + "--list-options ","show-policy-urls"); + deprecated_warning(configname,pargs.lineno,"--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,pargs.lineno,"--no-show-policy-url", + "--list-options ","no-show-policy-urls"); + deprecated_warning(configname,pargs.lineno,"--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,pargs.lineno, + "--default-comment","--no-comments",""); + /* fall through */ + case oNoComments: + free_strlist(opt.comments); + opt.comments=NULL; + break; + case oThrowKeyids: opt.throw_keyids = 1; break; + case oNoThrowKeyids: opt.throw_keyids = 0; break; + case oShowPhotos: + deprecated_warning(configname,pargs.lineno,"--show-photos", + "--list-options ","show-photos"); + deprecated_warning(configname,pargs.lineno,"--show-photos", + "--verify-options ","show-photos"); + opt.list_options|=LIST_SHOW_PHOTOS; + opt.verify_options|=VERIFY_SHOW_PHOTOS; + break; + case oNoShowPhotos: + deprecated_warning(configname,pargs.lineno,"--no-show-photos", + "--list-options ","no-show-photos"); + deprecated_warning(configname,pargs.lineno,"--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 oDisableSignerUID: opt.flags.disable_signer_uid = 1; break; + case oIncludeKeyBlock: opt.flags.include_key_block = 1; break; + case oNoIncludeKeyBlock: opt.flags.include_key_block = 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: + if (pargs.r.ret_int) + opt.s2k_count = encode_s2k_iterations (pargs.r.ret_int); + else + opt.s2k_count = 0; /* Auto-calibrate when needed. */ + break; + + case oRecipient: + case oHiddenRecipient: + case oRecipientFile: + case oHiddenRecipientFile: + /* Store the recipient. Note that we also store the + * option as private data in the flags. This is achieved + * by shifting the option value to the left so to keep + * enough space for the flags. */ + sl = add_to_strlist2( &remusr, pargs.r.ret_str, utf8_strings ); + sl->flags = (pargs.r_opt << PK_LIST_SHIFT); + if (configname) + sl->flags |= PK_LIST_CONFIG; + if (pargs.r_opt == oHiddenRecipient + || pargs.r_opt == oHiddenRecipientFile) + sl->flags |= PK_LIST_HIDDEN; + if (pargs.r_opt == oRecipientFile + || pargs.r_opt == oHiddenRecipientFile) + sl->flags |= PK_LIST_FROM_FILE; + any_explicit_recipient = 1; + break; + + case oEncryptTo: + case oHiddenEncryptTo: + /* Store an additional recipient. */ + sl = add_to_strlist2( &remusr, pargs.r.ret_str, utf8_strings ); + sl->flags = ((pargs.r_opt << PK_LIST_SHIFT) | PK_LIST_ENCRYPT_TO); + if (configname) + sl->flags |= PK_LIST_CONFIG; + if (pargs.r_opt == oHiddenEncryptTo) + sl->flags |= PK_LIST_HIDDEN; + break; + + case oNoEncryptTo: + opt.no_encrypt_to = 1; + break; + case oEncryptToDefaultKey: + opt.encrypt_to_default_key = configname ? 2 : 1; + break; + + case oTrySecretKey: + add_to_strlist2 (&opt.secret_keys_to_try, + pargs.r.ret_str, utf8_strings); + break; + + case oMimemode: opt.mimemode = opt.textmode = 1; break; + case oTextmodeShort: opt.textmode = 2; break; + case oTextmode: opt.textmode=1; break; + case oNoTextmode: opt.textmode=opt.mimemode=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 */ + sl = add_to_strlist2( &locusr, pargs.r.ret_str, utf8_strings ); + sl->flags = (pargs.r_opt << PK_LIST_SHIFT); + if (configname) + sl->flags |= PK_LIST_CONFIG; + break; + case oSender: + { + char *mbox = mailbox_from_userid (pargs.r.ret_str); + if (!mbox) + log_error (_("\"%s\" is not a proper mail address\n"), + pargs.r.ret_str); + else + { + add_to_strlist (&opt.sender_list, mbox); + xfree (mbox); + } + } + 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 oPassphrase: + set_passphrase_from_string (pargs.r_type ? pargs.r.ret_str : ""); + break; + case oPassphraseFD: + pwfd = translate_sys2libc_fd_int (pargs.r.ret_int, 0); + break; + case oPassphraseFile: + pwfd = open_info_file (pargs.r.ret_str, 0, 1); + break; + case oPassphraseRepeat: + opt.passphrase_repeat = pargs.r.ret_int; + break; + + case oPinentryMode: + opt.pinentry_mode = parse_pinentry_mode (pargs.r.ret_str); + if (opt.pinentry_mode == -1) + log_error (_("invalid pinentry mode '%s'\n"), pargs.r.ret_str); + break; + + case oRequestOrigin: + opt.request_origin = parse_request_origin (pargs.r.ret_str); + if (opt.request_origin == -1) + log_error (_("invalid request origin '%s'\n"), pargs.r.ret_str); + break; + + case oCommandFD: + opt.command_fd = translate_sys2libc_fd_int (pargs.r.ret_int, 0); + if (! gnupg_fd_valid (opt.command_fd)) + log_error ("command-fd is invalid: %s\n", strerror (errno)); + break; + case oCommandFile: + opt.command_fd = open_info_file (pargs.r.ret_str, 0, 1); + 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: + gcry_control (GCRYCTL_DISABLE_SECMEM_WARN); + break; + + case oRequireSecmem: require_secmem=1; break; + case oNoRequireSecmem: require_secmem=0; break; + case oNoPermissionWarn: opt.no_perm_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: + dotlock_disable (); + break; + case oLockMultiple: +#ifndef __riscos__ + opt.lock_once = 0; +#else /* __riscos__ */ + riscos_not_implemented("lock-multiple"); +#endif /* __riscos__ */ + break; + case oKeyServer: + { + keyserver_spec_t keyserver; + keyserver = parse_keyserver_uri (pargs.r.ret_str, 0); + if (!keyserver) + log_error (_("could not parse keyserver URL\n")); + else + { + /* We only support a single keyserver. Later ones + override earlier ones. (Since we parse the + config file first and then the command line + arguments, the command line takes + precedence.) */ + if (opt.keyserver) + free_keyserver_spec (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,pargs.lineno); + 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,pargs.lineno); + else + log_error(_("invalid import options\n")); + } + break; + case oImportFilter: + rc = parse_and_set_import_filter (pargs.r.ret_str); + if (rc) + log_error (_("invalid filter option: %s\n"), gpg_strerror (rc)); + 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,pargs.lineno); + else + log_error(_("invalid export options\n")); + } + break; + case oExportFilter: + rc = parse_and_set_export_filter (pargs.r.ret_str); + if (rc) + log_error (_("invalid filter option: %s\n"), gpg_strerror (rc)); + break; + case oListOptions: + if(!parse_list_options(pargs.r.ret_str)) + { + if(configname) + log_error(_("%s:%d: invalid list options\n"), + configname,pargs.lineno); + 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")}, + {"show-primary-uid-only",VERIFY_SHOW_PRIMARY_UID_ONLY,NULL, + N_("show only the primary user ID 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,pargs.lineno); + 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 oKnownNotation: register_known_notation (pargs.r.ret_str); break; + case oShowNotation: + deprecated_warning(configname,pargs.lineno,"--show-notation", + "--list-options ","show-notations"); + deprecated_warning(configname,pargs.lineno,"--show-notation", + "--verify-options ","show-notations"); + opt.list_options|=LIST_SHOW_NOTATIONS; + opt.verify_options|=VERIFY_SHOW_NOTATIONS; + break; + case oNoShowNotation: + deprecated_warning(configname,pargs.lineno,"--no-show-notation", + "--list-options ","no-show-notations"); + deprecated_warning(configname,pargs.lineno,"--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: +#ifdef HAVE_W32_SYSTEM + utf8_strings = 0; +#endif + break; + case oDisableCipherAlgo: + { + int algo = string_to_cipher_algo (pargs.r.ret_str); + gcry_cipher_ctl (NULL, GCRYCTL_DISABLE_ALGO, &algo, sizeof algo); + } + break; + case oDisablePubkeyAlgo: + { + int algo = gcry_pk_map_name (pargs.r.ret_str); + gcry_pk_ctl (GCRYCTL_DISABLE_ALGO, &algo, sizeof algo); + } + break; + case oNoSigCache: opt.no_sig_cache = 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 oFastListMode: opt.fast_list_mode = 1; break; + case oFixedListMode: /* Dummy */ break; + case oLegacyListMode: opt.legacy_list_mode = 1; break; + case oPrintPKARecords: print_pka_records = 1; break; + case oPrintDANERecords: print_dane_records = 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 oAutoKeyImport: opt.flags.auto_key_import = 1; break; + case oNoAutoKeyImport: opt.flags.auto_key_import = 0; break; + + case oAutoKeyRetrieve: + opt.keyserver_options.options |= KEYSERVER_AUTO_KEY_RETRIEVE; + break; + case oNoAutoKeyRetrieve: + opt.keyserver_options.options &= ~KEYSERVER_AUTO_KEY_RETRIEVE; + break; + + case oShowSessionKey: opt.show_session_key = 1; break; + case oOverrideSessionKey: + opt.override_session_key = pargs.r.ret_str; + break; + case oOverrideSessionKeyFD: + ovrseskeyfd = translate_sys2libc_fd_int (pargs.r.ret_int, 0); + break; + case oMergeOnly: + deprecated_warning(configname,pargs.lineno,"--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: + enable_special_filenames (); + 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: + { + keyserver_spec_t keyserver; + keyserver = parse_keyserver_uri (pargs.r.ret_str,1 ); + 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 oAgentProgram: opt.agent_program = pargs.r.ret_str; break; + case oDirmngrProgram: opt.dirmngr_program = pargs.r.ret_str; break; + case oDisableDirmngr: opt.disable_dirmngr = 1; break; + case oWeakDigest: + additional_weak_digest(pargs.r.ret_str); + break; + case oUnwrap: + opt.unwrap_encryption = 1; + break; + case oOnlySignTextIDs: + opt.only_sign_text_ids = 1; + break; + + case oDisplay: + set_opt_session_env ("DISPLAY", pargs.r.ret_str); + break; + case oTTYname: + set_opt_session_env ("GPG_TTY", pargs.r.ret_str); + break; + case oTTYtype: + set_opt_session_env ("TERM", pargs.r.ret_str); + break; + case oXauthority: + set_opt_session_env ("XAUTHORITY", pargs.r.ret_str); + break; + + case oLCctype: opt.lc_ctype = 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 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 if(ascii_strcasecmp(pargs.r.ret_str,"none")==0) + opt.keyid_format = KF_NONE; + 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 (default_akl) + { + /* This is the first time --auto-key-locate is seen. + * We need to reset the default akl. */ + default_akl = 0; + release_akl(); + } + if(!parse_auto_key_locate(pargs.r.ret_str)) + { + if(configname) + log_error(_("%s:%d: invalid auto-key-locate list\n"), + configname,pargs.lineno); + else + log_error(_("invalid auto-key-locate list\n")); + } + break; + case oNoAutoKeyLocate: + release_akl(); + break; + + case oKeyOrigin: + if(!parse_key_origin (pargs.r.ret_str)) + log_error (_("invalid argument for option \"%.50s\"\n"), + "--key-origin"); + break; + + case oEnableLargeRSA: +#if SECMEM_BUFFER_SIZE >= 65536 + opt.flags.large_rsa=1; +#else + if (configname) + log_info("%s:%d: WARNING: gpg not built with large secure " + "memory buffer. Ignoring enable-large-rsa\n", + configname,pargs.lineno); + else + log_info("WARNING: gpg not built with large secure " + "memory buffer. Ignoring --enable-large-rsa\n"); +#endif /* SECMEM_BUFFER_SIZE >= 65536 */ + break; + case oDisableLargeRSA: opt.flags.large_rsa=0; + break; + + case oEnableDSA2: opt.flags.dsa2=1; break; + case oDisableDSA2: opt.flags.dsa2=0; break; + + case oAllowMultisigVerification: + case oAllowMultipleMessages: + opt.flags.allow_multiple_messages=1; + break; + + case oNoAllowMultipleMessages: + opt.flags.allow_multiple_messages=0; + break; + + case oAllowWeakDigestAlgos: + opt.flags.allow_weak_digest_algos = 1; + break; + + case oAllowWeakKeySignatures: + opt.flags.allow_weak_key_signatures = 1; + break; + + case oOverrideComplianceCheck: + opt.flags.override_compliance_check = 1; + break; + + case oFakedSystemTime: + { + size_t len = strlen (pargs.r.ret_str); + int freeze = 0; + time_t faked_time; + + if (len > 0 && pargs.r.ret_str[len-1] == '!') + { + freeze = 1; + pargs.r.ret_str[len-1] = '\0'; + } + + faked_time = isotime2epoch (pargs.r.ret_str); + if (faked_time == (time_t)(-1)) + faked_time = (time_t)strtoul (pargs.r.ret_str, NULL, 10); + gnupg_set_time (faked_time, freeze); + } + break; + + case oNoAutostart: opt.autostart = 0; break; + case oNoSymkeyCache: opt.no_symkey_cache = 1; break; + + case oDefaultNewKeyAlgo: + opt.def_new_key_algo = pargs.r.ret_str; + break; + + case oUseOnlyOpenPGPCard: + opt.flags.use_only_openpgp_card = 1; + break; + + case oForbidGenKey: + mopt.forbid_gen_key = 1; + break; + + case oRequireCompliance: + opt.flags.require_compliance = 1; + break; + + case oNoop: break; + + default: + if (configname) + pargs.err = ARGPARSE_PRINT_WARNING; + else + { + pargs.err = ARGPARSE_PRINT_ERROR; + /* The argparse fucntion calls a plain exit and thus + * we need to print a status here. */ + write_status_failure ("option-parser", + gpg_error(GPG_ERR_GENERAL)); + } + break; + } + } + + gnupg_argparse (NULL, &pargs, NULL); /* Release internal state. */ + + if (log_get_errorcount (0)) + { + write_status_failure ("option-parser", gpg_error(GPG_ERR_GENERAL)); + g10_exit(2); + } + + /* The command --gpgconf-list is pretty simple and may be called + directly after the option parsing. */ + if (cmd == aGPGConfList) + { + /* Note: Here in gpg 2.2 we need to provide a proper config + * file even if that file does not exist. This is because + * gpgconf checks that an absolute filename is provided. */ + if (!last_configname) + last_configname= make_filename (gnupg_homedir (), + GPG_NAME EXTSEP_S "conf", NULL); + gpgconf_list (last_configname); + g10_exit (0); + } + xfree (last_configname); + last_configname = NULL; + + if (print_dane_records) + log_error ("invalid option \"%s\"; use \"%s\" instead\n", + "--print-dane-records", + "--export-options export-dane"); + if (print_pka_records) + log_error ("invalid option \"%s\"; use \"%s\" instead\n", + "--print-pks-records", + "--export-options export-pka"); + if (log_get_errorcount (0)) + { + write_status_failure ("option-checking", gpg_error(GPG_ERR_GENERAL)); + g10_exit(2); + } + + + if( nogreeting ) + greeting = 0; + + if( greeting ) + { + es_fprintf (es_stderr, "%s %s; %s\n", + strusage(11), strusage(13), strusage(14) ); + es_fprintf (es_stderr, "%s\n", strusage(15) ); + } +#ifdef IS_DEVELOPMENT_VERSION + if (!opt.batch) + { + const char *s; + + if((s=strusage(25))) + log_info("%s\n",s); + if((s=strusage(26))) + log_info("%s\n",s); + if((s=strusage(27))) + log_info("%s\n",s); + } +#endif + + /* FIXME: We should use logging to a file only in server mode; + however we have not yet implemetyed that. Thus we try to get + away with --batch as indication for logging to file + required. */ + if (logfile && opt.batch) + { + log_set_file (logfile); + log_set_prefix (NULL, GPGRT_LOG_WITH_PREFIX | GPGRT_LOG_WITH_TIME | GPGRT_LOG_WITH_PID); + } + + 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 (opt.flags.rfc4880bis) + log_info ("WARNING: using experimental features from RFC4880bis!\n"); + else + { + opt.mimemode = 0; /* This will use text mode instead. */ + } + + 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" ); + } + + + if (opt.set_filesize) + log_info(_("Note: %s is not for normal use!\n"), "--set-filesize"); + if( opt.batch ) + tty_batchmode( 1 ); + + if (gnupg_faked_time_p ()) + { + gnupg_isotime_t tbuf; + + log_info (_("WARNING: running with faked system time: ")); + gnupg_get_isotime (tbuf); + dump_isotime (tbuf); + log_printf ("\n"); + } + + /* Print a warning if an argument looks like an option. */ + if (!opt.quiet && !(pargs.flags & ARGPARSE_FLAG_STOP_SEEN)) + { + int i; + + for (i=0; i < argc; i++) + if (argv[i][0] == '-' && argv[i][1] == '-') + log_info (_("Note: '%s' is not considered an option\n"), argv[i]); + } + + + gcry_control (GCRYCTL_RESUME_SECMEM_WARN); + + if(require_secmem && !got_secmem) + { + log_info(_("will not run with insecure memory due to %s\n"), + "--require-secmem"); + write_status_failure ("option-checking", gpg_error(GPG_ERR_GENERAL)); + g10_exit(2); + } + + /* We allow overriding the compliance check only in non-batch mode + * so that the user has a chance to see the message. */ + if (opt.flags.override_compliance_check && opt.batch) + { + opt.flags.override_compliance_check = 0; + log_info ("Note: '%s' ignored due to batch mode\n", + "--override-compliance-check"); + } + + set_debug (debug_level); + gnupg_set_compliance_extra_info (opt.min_rsa_length); + if (DBG_CLOCK) + log_clock ("start"); + + /* Do these after the switch(), so they can override settings. */ + if(PGP6) + { + /* That does not anymore work because we have no more support + for v3 signatures. */ + opt.escape_from=1; + opt.ask_sig_expire=0; + } + else if(PGP7) + { + /* That does not anymore work because we have no more support + for v3 signatures. */ + opt.escape_from=1; + opt.ask_sig_expire=0; + } + else if(PGP8) + { + opt.escape_from=1; + } + + + if( def_cipher_string ) { + opt.def_cipher_algo = string_to_cipher_algo (def_cipher_string); + xfree(def_cipher_string); def_cipher_string = NULL; + if ( openpgp_cipher_test_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 ( openpgp_md_test_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 (openpgp_md_test_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 (openpgp_cipher_test_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 (openpgp_md_test_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: + if (!opt.quiet) + 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")); + + 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 aSignEncr: + cmdname="--sign --encrypt"; + break; + case aClearsign: + cmdname="--clear-sign"; + 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) ) + { + write_status_failure ("option-postprocessing", + gpg_error(GPG_ERR_GENERAL)); + 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 = openpgp_cipher_algo_name (opt.def_cipher_algo); + badtype = PREFTYPE_SYM; + } + else if(opt.def_digest_algo + && !algo_available(PREFTYPE_HASH,opt.def_digest_algo,NULL)) + { + badalg = gcry_md_algo_name (opt.def_digest_algo); + badtype = PREFTYPE_HASH; + } + else if(opt.cert_digest_algo + && !algo_available(PREFTYPE_HASH,opt.cert_digest_algo,NULL)) + { + badalg = gcry_md_algo_name (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 (_("cipher algorithm '%s'" + " may not be used in %s mode\n"), + badalg, + gnupg_compliance_option_string (opt.compliance)); + break; + case PREFTYPE_HASH: + log_info (_("digest algorithm '%s'" + " may not be used in %s mode\n"), + badalg, + gnupg_compliance_option_string (opt.compliance)); + break; + case PREFTYPE_ZIP: + log_info (_("compression algorithm '%s'" + " may not be used in %s mode\n"), + badalg, + gnupg_compliance_option_string (opt.compliance)); + break; + default: + BUG(); + } + + compliance_failure(); + } + } + + /* Check our chosen algorithms against the list of allowed + * algorithms in the current compliance mode, and fail hard if it + * is not. This is us being nice to the user informing her early + * that the chosen algorithms are not available. We also check + * and enforce this right before the actual operation. */ + if (opt.def_cipher_algo + && ! gnupg_cipher_is_allowed (opt.compliance, + cmd == aEncr + || cmd == aSignEncr + || cmd == aEncrSym + || cmd == aSym + || cmd == aSignSym + || cmd == aSignEncrSym, + opt.def_cipher_algo, + GCRY_CIPHER_MODE_NONE)) + log_error (_("cipher algorithm '%s' may not be used in %s mode\n"), + openpgp_cipher_algo_name (opt.def_cipher_algo), + gnupg_compliance_option_string (opt.compliance)); + + if (opt.def_digest_algo + && ! gnupg_digest_is_allowed (opt.compliance, + cmd == aSign + || cmd == aSignEncr + || cmd == aSignEncrSym + || cmd == aSignSym + || cmd == aClearsign, + opt.def_digest_algo)) + log_error (_("digest algorithm '%s' may not be used in %s mode\n"), + gcry_md_algo_name (opt.def_digest_algo), + gnupg_compliance_option_string (opt.compliance)); + + /* Fail hard. */ + if (log_get_errorcount (0)) + { + write_status_failure ("option-checking", gpg_error(GPG_ERR_GENERAL)); + g10_exit (2); + } + + /* Set the random seed file. */ + if (use_random_seed) + { + char *p = make_filename (gnupg_homedir (), "random_seed", NULL ); + gcry_control (GCRYCTL_SET_RANDOM_SEED_FILE, p); + if (!gnupg_access (p, F_OK)) + register_secured_file (p); + xfree(p); + } + + /* If there is no command but the --fingerprint is given, default + to the --list-keys command. */ + if (!cmd && fpr_maybe_cmd) + { + set_cmd (&cmd, aListKeys); + } + + + if( opt.verbose > 1 ) + set_packet_list_mode(1); + + /* Add the keyrings, but not for some special commands. 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. + * We do not add any keyring if --no-keyring has been used. */ + if (default_keyring >= 0 + && (ALWAYS_ADD_KEYRINGS + || (cmd != aDeArmor && cmd != aEnArmor && cmd != aGPGConfTest))) + { + if (!nrings || default_keyring > 0) /* Add default ring. */ + keydb_add_resource ("pubring" EXTSEP_S GPGEXT_GPG, + KEYDB_RESOURCE_FLAG_DEFAULT); + for (sl = nrings; sl; sl = sl->next ) + keydb_add_resource (sl->d, sl->flags); + } + FREE_STRLIST(nrings); + + if (opt.pinentry_mode == PINENTRY_MODE_LOOPBACK) + /* In loopback mode, never ask for the password multiple + times. */ + { + opt.passphrase_repeat = 0; + } + + if (cmd == aGPGConfTest) + g10_exit(0); + + + if (pwfd != -1) /* Read the passphrase now. */ + read_passphrase_from_fd (pwfd); + + if (ovrseskeyfd != -1 ) /* Read the sessionkey now. */ + read_sessionkey_from_fd (ovrseskeyfd); + + fname = argc? *argv : NULL; + + if(fname && utf8_strings) + opt.flags.utf8_filename=1; + + ctrl = xcalloc (1, sizeof *ctrl); + gpg_init_default_ctrl (ctrl); + +#ifndef NO_TRUST_MODELS + switch (cmd) + { + case aPrimegen: + case aPrintMD: + case aPrintMDs: + case aGenRandom: + case aDeArmor: + case aEnArmor: + case aListConfig: + case aListGcryptConfig: + break; + case aFixTrustDB: + case aExportOwnerTrust: + rc = setup_trustdb (0, trustdb_name); + break; + case aListTrustDB: + rc = setup_trustdb (argc? 1:0, trustdb_name); + break; + case aKeygen: + case aFullKeygen: + case aQuickKeygen: + rc = setup_trustdb (1, trustdb_name); + break; + default: + /* If we are using TM_ALWAYS, we do not need to create the + trustdb. */ + rc = setup_trustdb (opt.trust_model != TM_ALWAYS, trustdb_name); + break; + } + if (rc) + log_error (_("failed to initialize the TrustDB: %s\n"), + gpg_strerror (rc)); +#endif /*!NO_TRUST_MODELS*/ + + 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; + } + + + /* Check for certain command whether we need to migrate a + secring.gpg to the gpg-agent. */ + switch (cmd) + { + case aListSecretKeys: + case aSign: + case aSignEncr: + case aSignEncrSym: + case aSignSym: + case aClearsign: + case aDecrypt: + case aSignKey: + case aLSignKey: + case aEditKey: + case aPasswd: + case aDeleteSecretKeys: + case aDeleteSecretAndPublicKeys: + case aQuickKeygen: + case aQuickAddUid: + case aQuickAddKey: + case aQuickRevUid: + case aQuickSetPrimaryUid: + case aFullKeygen: + case aKeygen: + case aImport: + case aExportSecret: + case aExportSecretSub: + case aGenRevoke: + case aDesigRevoke: + case aCardEdit: + case aChangePIN: + migrate_secring (ctrl); + break; + case aListKeys: + if (opt.with_secret) + migrate_secring (ctrl); + break; + default: + break; + } + + /* The command dispatcher. */ + switch( cmd ) + { + case aServer: + gpg_server (ctrl); + break; + + case aStore: /* only store the file */ + if( argc > 1 ) + wrong_args("--store [filename]"); + if( (rc = encrypt_store(fname)) ) + { + write_status_failure ("store", rc); + log_error ("storing '%s' failed: %s\n", + print_fname_stdin(fname),gpg_strerror (rc) ); + } + break; + case aSym: /* encrypt the given file only with the symmetric cipher */ + if( argc > 1 ) + wrong_args("--symmetric [filename]"); + if( (rc = encrypt_symmetric(fname)) ) + { + write_status_failure ("symencrypt", rc); + log_error (_("symmetric encryption of '%s' failed: %s\n"), + print_fname_stdin(fname),gpg_strerror (rc) ); + } + break; + + case aEncr: /* encrypt the given file */ + if(multifile) + encrypt_crypt_files (ctrl, argc, argv, remusr); + else + { + if( argc > 1 ) + wrong_args("--encrypt [filename]"); + if( (rc = encrypt_crypt (ctrl, -1, fname, remusr, 0, NULL, -1)) ) + { + write_status_failure ("encrypt", rc); + log_error("%s: encryption failed: %s\n", + print_fname_stdin(fname), gpg_strerror (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(PGP6 || PGP7) + log_error(_("you cannot use --symmetric --encrypt" + " in %s mode\n"), + gnupg_compliance_option_string (opt.compliance)); + else + { + if( (rc = encrypt_crypt (ctrl, -1, fname, remusr, 1, NULL, -1)) ) + { + write_status_failure ("encrypt", rc); + log_error ("%s: encryption failed: %s\n", + print_fname_stdin(fname), gpg_strerror (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 (ctrl, sl, detached_sig, locusr, 0, NULL, NULL))) + { + write_status_failure ("sign", rc); + log_error ("signing failed: %s\n", gpg_strerror (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 (ctrl, sl, detached_sig, locusr, 1, remusr, NULL))) + { + write_status_failure ("sign-encrypt", rc); + log_error("%s: sign+encrypt failed: %s\n", + print_fname_stdin(fname), gpg_strerror (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(PGP6 || PGP7) + log_error(_("you cannot use --symmetric --sign --encrypt" + " in %s mode\n"), + gnupg_compliance_option_string (opt.compliance)); + else + { + if( argc ) + { + sl = xmalloc_clear( sizeof *sl + strlen(fname)); + strcpy(sl->d, fname); + } + else + sl = NULL; + if ((rc = sign_file (ctrl, sl, detached_sig, locusr, + 2, remusr, NULL))) + { + write_status_failure ("sign-encrypt", rc); + log_error("%s: symmetric+sign+encrypt failed: %s\n", + print_fname_stdin(fname), gpg_strerror (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 (ctrl, fname, locusr); + if (rc) + { + write_status_failure ("sign-symencrypt", rc); + log_error("%s: sign+symmetric failed: %s\n", + print_fname_stdin(fname), gpg_strerror (rc) ); + } + break; + + case aClearsign: /* make a clearsig */ + if( argc > 1 ) + wrong_args("--clear-sign [filename]"); + if( (rc = clearsign_file (ctrl, fname, locusr, NULL)) ) + { + write_status_failure ("sign", rc); + log_error("%s: clear-sign failed: %s\n", + print_fname_stdin(fname), gpg_strerror (rc) ); + } + break; + + case aVerify: + if (multifile) + { + if ((rc = verify_files (ctrl, argc, argv))) + log_error("verify files failed: %s\n", gpg_strerror (rc) ); + } + else + { + if ((rc = verify_signatures (ctrl, argc, argv))) + log_error("verify signatures failed: %s\n", gpg_strerror (rc) ); + } + if (rc) + write_status_failure ("verify", rc); + break; + + case aDecrypt: + if (multifile) + decrypt_messages (ctrl, argc, argv); + else + { + if( argc > 1 ) + wrong_args("--decrypt [filename]"); + if( (rc = decrypt_message (ctrl, fname) )) + { + write_status_failure ("decrypt", rc); + log_error("decrypt_message failed: %s\n", gpg_strerror (rc) ); + } + } + break; + + case aQuickSignKey: + case aQuickLSignKey: + { + const char *fpr; + + if (argc < 1) + wrong_args ("--quick-[l]sign-key fingerprint [userids]"); + fpr = *argv++; argc--; + sl = NULL; + for( ; argc; argc--, argv++) + append_to_strlist2 (&sl, *argv, utf8_strings); + keyedit_quick_sign (ctrl, fpr, sl, locusr, (cmd == aQuickLSignKey)); + free_strlist (sl); + } + break; + + case aQuickRevSig: + { + const char *userid, *siguserid; + + if (argc < 2) + wrong_args ("--quick-revoke-sig USER-ID SIG-USER-ID [userids]"); + userid = *argv++; argc--; + siguserid = *argv++; argc--; + sl = NULL; + for( ; argc; argc--, argv++) + append_to_strlist2 (&sl, *argv, utf8_strings); + keyedit_quick_revsig (ctrl, userid, siguserid, sl); + free_strlist (sl); + } + 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 (ctrl, username, 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 (ctrl, username, locusr, sl, 0, 1 ); + free_strlist(sl); + } + else + keyedit_menu (ctrl, username, locusr, NULL, 0, 1 ); + xfree(username); + break; + + case aPasswd: + if (argc != 1) + wrong_args("--change-passphrase "); + else + { + username = make_username (fname); + keyedit_passwd (ctrl, username); + xfree (username); + } + break; + + case aDeleteKeys: + case aDeleteSecretKeys: + case aDeleteSecretAndPublicKeys: + sl = NULL; + /* Print a note if the user did not specify any key. */ + if (!argc && !opt.quiet) + log_info (_("Note: %s\n"), gpg_strerror (GPG_ERR_NO_KEY)); + /* 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 (ctrl, sl, + cmd==aDeleteSecretKeys, cmd==aDeleteSecretAndPublicKeys); + free_strlist(sl); + break; + + case aCheckKeys: + opt.check_sigs = 1; /* fall through */ + case aListSigs: + opt.list_sigs = 1; /* fall through */ + case aListKeys: + sl = NULL; + for( ; argc; argc--, argv++ ) + add_to_strlist2( &sl, *argv, utf8_strings ); + public_key_list (ctrl, sl, 0, 0); + free_strlist(sl); + break; + case aListSecretKeys: + sl = NULL; + for( ; argc; argc--, argv++ ) + add_to_strlist2( &sl, *argv, utf8_strings ); + secret_key_list (ctrl, sl); + free_strlist(sl); + break; + case aLocateKeys: + case aLocateExtKeys: + sl = NULL; + for (; argc; argc--, argv++) + add_to_strlist2( &sl, *argv, utf8_strings ); + if (cmd == aLocateExtKeys && akl_empty_or_only_local ()) + { + /* This is a kludge to let --locate-external-keys even + * work if the config file has --no-auto-key-locate. This + * better matches the expectations of the user. */ + release_akl (); + parse_auto_key_locate (DEFAULT_AKL_LIST); + } + public_key_list (ctrl, sl, 1, cmd == aLocateExtKeys); + + + free_strlist (sl); + break; + + case aQuickKeygen: + { + const char *x_algo, *x_usage, *x_expire; + + if (argc < 1 || argc > 4) + wrong_args("--quick-generate-key USER-ID [ALGO [USAGE [EXPIRE]]]"); + username = make_username (fname); + argv++, argc--; + x_algo = ""; + x_usage = ""; + x_expire = ""; + if (argc) + { + x_algo = *argv++; argc--; + if (argc) + { + x_usage = *argv++; argc--; + if (argc) + { + x_expire = *argv++; argc--; + } + } + } + if (mopt.forbid_gen_key) + gen_key_forbidden (); + else + quick_generate_keypair (ctrl, username, x_algo, x_usage, x_expire); + xfree (username); + } + break; + + case aKeygen: /* generate a key */ + if (mopt.forbid_gen_key) + gen_key_forbidden (); + else if( opt.batch ) + { + if( argc > 1 ) + wrong_args("--generate-key [parameterfile]"); + generate_keypair (ctrl, 0, argc? *argv : NULL, NULL, 0); + } + else + { + if (opt.command_fd != -1 && argc) + { + if( argc > 1 ) + wrong_args("--generate-key [parameterfile]"); + + opt.batch = 1; + generate_keypair (ctrl, 0, argc? *argv : NULL, NULL, 0); + } + else if (argc) + wrong_args ("--generate-key"); + else + generate_keypair (ctrl, 0, NULL, NULL, 0); + } + break; + + case aFullKeygen: /* Generate a key with all options. */ + if (mopt.forbid_gen_key) + gen_key_forbidden (); + else if (opt.batch) + { + if (argc > 1) + wrong_args ("--full-generate-key [parameterfile]"); + generate_keypair (ctrl, 1, argc? *argv : NULL, NULL, 0); + } + else + { + if (argc) + wrong_args("--full-generate-key"); + generate_keypair (ctrl, 1, NULL, NULL, 0); + } + break; + + case aQuickAddUid: + { + const char *uid, *newuid; + + if (argc != 2) + wrong_args ("--quick-add-uid USER-ID NEW-USER-ID"); + uid = *argv++; argc--; + newuid = *argv++; argc--; + keyedit_quick_adduid (ctrl, uid, newuid); + } + break; + + case aQuickAddKey: + { + const char *x_fpr, *x_algo, *x_usage, *x_expire; + + if (argc < 1 || argc > 4) + wrong_args ("--quick-add-key FINGERPRINT [ALGO [USAGE [EXPIRE]]]"); + x_fpr = *argv++; argc--; + x_algo = ""; + x_usage = ""; + x_expire = ""; + if (argc) + { + x_algo = *argv++; argc--; + if (argc) + { + x_usage = *argv++; argc--; + if (argc) + { + x_expire = *argv++; argc--; + } + } + } + if (mopt.forbid_gen_key) + gen_key_forbidden (); + else + keyedit_quick_addkey (ctrl, x_fpr, x_algo, x_usage, x_expire); + } + break; + + case aQuickRevUid: + { + const char *uid, *uidtorev; + + if (argc != 2) + wrong_args ("--quick-revoke-uid USER-ID USER-ID-TO-REVOKE"); + uid = *argv++; argc--; + uidtorev = *argv++; argc--; + keyedit_quick_revuid (ctrl, uid, uidtorev); + } + break; + + case aQuickSetExpire: + { + const char *x_fpr, *x_expire; + + if (argc < 2) + wrong_args ("--quick-set-exipre FINGERPRINT EXPIRE [SUBKEY-FPRS]"); + x_fpr = *argv++; argc--; + x_expire = *argv++; argc--; + keyedit_quick_set_expire (ctrl, x_fpr, x_expire, argv); + } + break; + + case aQuickSetPrimaryUid: + { + const char *uid, *primaryuid; + + if (argc != 2) + wrong_args ("--quick-set-primary-uid USER-ID PRIMARY-USER-ID"); + uid = *argv++; argc--; + primaryuid = *argv++; argc--; + keyedit_quick_set_primary (ctrl, uid, primaryuid); + } + break; + + case aFastImport: + opt.import_options |= IMPORT_FAST; /* fall through */ + case aImport: + case aShowKeys: + import_keys (ctrl, argc? argv:NULL, argc, NULL, + opt.import_options, opt.key_origin, opt.key_origin_url); + 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 (ctrl, sl ); + else if( cmd == aRecvKeys ) + rc = keyserver_import (ctrl, sl ); + else + { + export_stats_t stats = export_new_stats (); + rc = export_pubkeys (ctrl, sl, opt.export_options, stats); + export_print_stats (stats); + export_release_stats (stats); + } + if(rc) + { + if(cmd==aSendKeys) + { + write_status_failure ("send-keys", rc); + log_error(_("keyserver send failed: %s\n"),gpg_strerror (rc)); + } + else if(cmd==aRecvKeys) + { + write_status_failure ("recv-keys", rc); + log_error (_("keyserver receive failed: %s\n"), + gpg_strerror (rc)); + } + else + { + write_status_failure ("export", rc); + log_error (_("key export failed: %s\n"), gpg_strerror (rc)); + } + } + free_strlist(sl); + break; + + case aExportSshKey: + if (argc != 1) + wrong_args ("--export-ssh-key "); + rc = export_ssh_key (ctrl, argv[0]); + if (rc) + { + write_status_failure ("export-ssh-key", rc); + log_error (_("export as ssh key failed: %s\n"), gpg_strerror (rc)); + } + break; + + case aSearchKeys: + sl = NULL; + for (; argc; argc--, argv++) + append_to_strlist2 (&sl, *argv, utf8_strings); + rc = keyserver_search (ctrl, sl); + if (rc) + { + write_status_failure ("search-keys", rc); + log_error (_("keyserver search failed: %s\n"), gpg_strerror (rc)); + } + free_strlist (sl); + break; + + case aRefreshKeys: + sl = NULL; + for( ; argc; argc--, argv++ ) + append_to_strlist2( &sl, *argv, utf8_strings ); + rc = keyserver_refresh (ctrl, sl); + if(rc) + { + write_status_failure ("refresh-keys", rc); + log_error (_("keyserver refresh failed: %s\n"),gpg_strerror (rc)); + } + free_strlist(sl); + break; + + case aFetchKeys: + sl = NULL; + for( ; argc; argc--, argv++ ) + append_to_strlist2( &sl, *argv, utf8_strings ); + rc = keyserver_fetch (ctrl, sl, opt.key_origin); + free_strlist (sl); + if(rc) + { + write_status_failure ("fetch-keys", rc); + log_error ("key fetch failed: %s\n",gpg_strerror (rc)); + if (gpg_err_code (rc) == GPG_ERR_NO_DATA) + g10_exit (1); /* In this case return 1 and not 2. */ + } + break; + + case aExportSecret: + sl = NULL; + for( ; argc; argc--, argv++ ) + add_to_strlist2( &sl, *argv, utf8_strings ); + { + export_stats_t stats = export_new_stats (); + export_seckeys (ctrl, sl, opt.export_options, stats); + export_print_stats (stats); + export_release_stats (stats); + } + free_strlist(sl); + break; + + case aExportSecretSub: + sl = NULL; + for( ; argc; argc--, argv++ ) + add_to_strlist2( &sl, *argv, utf8_strings ); + { + export_stats_t stats = export_new_stats (); + export_secsubkeys (ctrl, sl, opt.export_options, stats); + export_print_stats (stats); + export_release_stats (stats); + } + free_strlist(sl); + break; + + case aGenRevoke: + if( argc != 1 ) + wrong_args("--generate-revocation user-id"); + username = make_username(*argv); + gen_revoke (ctrl, username ); + xfree( username ); + break; + + case aDesigRevoke: + if (argc != 1) + wrong_args ("--generate-designated-revocation user-id"); + username = make_username (*argv); + gen_desig_revoke (ctrl, username, locusr); + xfree (username); + break; + + case aDeArmor: + if( argc > 1 ) + wrong_args("--dearmor [file]"); + rc = dearmor_file( argc? *argv: NULL ); + if( rc ) + { + write_status_failure ("dearmor", rc); + log_error (_("dearmoring failed: %s\n"), gpg_strerror (rc)); + } + break; + + case aEnArmor: + if( argc > 1 ) + wrong_args("--enarmor [file]"); + rc = enarmor_file( argc? *argv: NULL ); + if( rc ) + { + write_status_failure ("enarmor", rc); + log_error (_("enarmoring failed: %s\n"), gpg_strerror (rc)); + } + break; + + + case aPrimegen: +#if 0 /*FIXME*/ + { int mode = argc < 2 ? 0 : atoi(*argv); + + if( mode == 1 && argc == 2 ) { + mpi_print (es_stdout, + generate_public_prime( atoi(argv[1]) ), 1); + } + else if( mode == 2 && argc == 3 ) { + mpi_print (es_stdout, generate_elg_prime( + 0, atoi(argv[1]), + atoi(argv[2]), NULL,NULL ), 1); + } + else if( mode == 3 && argc == 3 ) { + MPI *factors; + mpi_print (es_stdout, generate_elg_prime( + 1, atoi(argv[1]), + atoi(argv[2]), NULL,&factors ), 1); + es_putc ('\n', es_stdout); + mpi_print (es_stdout, factors[0], 1 ); /* print q */ + } + else if( mode == 4 && argc == 3 ) { + MPI g = mpi_alloc(1); + mpi_print (es_stdout, generate_elg_prime( + 0, atoi(argv[1]), + atoi(argv[2]), g, NULL ), 1); + es_putc ('\n', es_stdout); + mpi_print (es_stdout, g, 1 ); + mpi_free (g); + } + else + wrong_args("--gen-prime mode bits [qbits] "); + es_putc ('\n', es_stdout); + } +#endif + wrong_args("--gen-prime not yet supported "); + 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 = gcry_random_bytes (n, level); +#ifdef HAVE_DOSISH_SYSTEM + setmode ( fileno(stdout), O_BINARY ); +#endif + if (opt.armor) { + char *tmp = make_radix64_string (p, n); + es_fputs (tmp, es_stdout); + xfree (tmp); + if (n%3 == 1) + es_putc ('=', es_stdout); + if (n%3) + es_putc ('=', es_stdout); + } else { + es_fwrite( p, n, 1, es_stdout ); + } + xfree(p); + if( !endless ) + count -= n; + } + if (opt.armor) + es_putc ('\n', es_stdout); + } + break; + + case aPrintMD: + if( argc < 1) + wrong_args("--print-md algo [files]"); + { + int all_algos = (**argv=='*' && !(*argv)[1]); + int algo = all_algos? 0 : gcry_md_map_name (*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; + +#ifndef NO_TRUST_MODELS + case aListTrustDB: + if( !argc ) + list_trustdb (ctrl, es_stdout, NULL); + else { + for( ; argc; argc--, argv++ ) + list_trustdb (ctrl, es_stdout, *argv ); + } + break; + + case aUpdateTrustDB: + if( argc ) + wrong_args("--update-trustdb"); + update_trustdb (ctrl); + break; + + case aCheckTrustDB: + /* Old versions allowed for arguments - ignore them */ + check_trustdb (ctrl); + break; + + case aFixTrustDB: + how_to_fix_the_trustdb (); + break; + + case aListTrustPath: + if( !argc ) + wrong_args("--list-trust-path "); + 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 (ctrl); + break; + + case aImportOwnerTrust: + if( argc > 1 ) + wrong_args("--import-ownertrust [file]"); + import_ownertrust (ctrl, argc? *argv:NULL ); + break; +#endif /*!NO_TRUST_MODELS*/ + + case aRebuildKeydbCaches: + if (argc) + wrong_args ("--rebuild-keydb-caches"); + keydb_rebuild_caches (ctrl, 1); + break; + +#ifdef ENABLE_CARD_SUPPORT + case aCardStatus: + if (argc == 0) + card_status (ctrl, es_stdout, NULL); + else if (argc == 1) + card_status (ctrl, es_stdout, *argv); + else + wrong_args ("--card-status [serialno]"); + break; + + case aCardEdit: + if (argc) { + sl = NULL; + for (argc--, argv++ ; argc; argc--, argv++) + append_to_strlist (&sl, *argv); + card_edit (ctrl, sl); + free_strlist (sl); + } + else + card_edit (ctrl, 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 aListGcryptConfig: + /* Fixme: It would be nice to integrate that with + --list-config but unfortunately there is no way yet to have + libgcrypt print it to an estream for further parsing. */ + gcry_control (GCRYCTL_PRINT_CONFIG, stdout); + break; + + case aTOFUPolicy: +#ifdef USE_TOFU + { + int policy; + int i; + KEYDB_HANDLE hd; + + if (argc < 2) + wrong_args ("--tofu-policy POLICY KEYID [KEYID...]"); + + policy = parse_tofu_policy (argv[0]); + + hd = keydb_new (); + if (! hd) + { + write_status_failure ("tofu-driver", gpg_error(GPG_ERR_GENERAL)); + g10_exit (1); + } + + tofu_begin_batch_update (ctrl); + + for (i = 1; i < argc; i ++) + { + KEYDB_SEARCH_DESC desc; + kbnode_t kb; + + rc = classify_user_id (argv[i], &desc, 0); + if (rc) + { + log_error (_("error parsing key specification '%s': %s\n"), + argv[i], gpg_strerror (rc)); + write_status_failure ("tofu-driver", rc); + g10_exit (1); + } + + 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 + || desc.mode == KEYDB_SEARCH_MODE_FPR + || desc.mode == KEYDB_SEARCH_MODE_KEYGRIP)) + { + log_error (_("'%s' does not appear to be a valid" + " key ID, fingerprint or keygrip\n"), + argv[i]); + write_status_failure ("tofu-driver", + gpg_error(GPG_ERR_GENERAL)); + g10_exit (1); + } + + rc = keydb_search_reset (hd); + if (rc) + { + /* This should not happen, thus no need to tranalate + the string. */ + log_error ("keydb_search_reset failed: %s\n", + gpg_strerror (rc)); + write_status_failure ("tofu-driver", rc); + g10_exit (1); + } + + rc = keydb_search (hd, &desc, 1, NULL); + if (rc) + { + log_error (_("key \"%s\" not found: %s\n"), argv[i], + gpg_strerror (rc)); + write_status_failure ("tofu-driver", rc); + g10_exit (1); + } + + rc = keydb_get_keyblock (hd, &kb); + if (rc) + { + log_error (_("error reading keyblock: %s\n"), + gpg_strerror (rc)); + write_status_failure ("tofu-driver", rc); + g10_exit (1); + } + + merge_keys_and_selfsig (ctrl, kb); + if (tofu_set_policy (ctrl, kb, policy)) + { + write_status_failure ("tofu-driver", rc); + g10_exit (1); + } + + release_kbnode (kb); + } + + tofu_end_batch_update (ctrl); + + keydb_release (hd); + } +#endif /*USE_TOFU*/ + break; + + default: + if (!opt.quiet) + log_info (_("WARNING: no command supplied." + " Trying to guess what you mean ...\n")); + /*FALLTHRU*/ + case aListPackets: + if( argc > 1 ) + wrong_args("[filename]"); + /* Issue some output for the unix newbie */ + if (!fname && !opt.outfile + && gnupg_isatty (fileno (stdin)) + && gnupg_isatty (fileno (stdout)) + && gnupg_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; + gpg_err_set_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 ) ) { + afx = new_armor_context (); + push_armor_filter (afx, a); + } + } + if( cmd == aListPackets ) { + opt.list_packets=1; + set_packet_list_mode(1); + } + rc = proc_packets (ctrl, NULL, a ); + if( rc ) + { + write_status_failure ("-", rc); + log_error ("processing message failed: %s\n", + gpg_strerror (rc)); + } + iobuf_close(a); + } + break; + } + + /* cleanup */ + gpg_deinit_default_ctrl (ctrl); + xfree (ctrl); + release_armor_context (afx); + FREE_STRLIST(remusr); + FREE_STRLIST(locusr); + g10_exit(0); + return 8; /*NEVER REACHED*/ +} + + +/* Note: This function is used by signal handlers!. */ +static void +emergency_cleanup (void) +{ + gcry_control (GCRYCTL_TERM_SECMEM ); +} + + +void +g10_exit( int rc ) +{ + /* If we had an error but not printed an error message, do it now. + * Note that write_status_failure will never print a second failure + * status line. */ + if (rc) + write_status_failure ("gpg-exit", gpg_error (GPG_ERR_GENERAL)); + + gcry_control (GCRYCTL_UPDATE_RANDOM_SEED_FILE); + if (DBG_CLOCK) + log_clock ("stop"); + + if ( (opt.debug & DBG_MEMSTAT_VALUE) ) + { + keydb_dump_stats (); + sig_check_dump_stats (); + gcry_control (GCRYCTL_DUMP_MEMORY_STATS); + gcry_control (GCRYCTL_DUMP_RANDOM_STATS); + } + if (opt.debug) + gcry_control (GCRYCTL_DUMP_SECMEM_STATS ); + + emergency_cleanup (); + + rc = rc? rc : log_get_errorcount(0)? 2 : 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 (gcry_md_hd_t md, int algo, const char *fname) +{ + int i,n,count,indent=0; + const byte *p; + + if (fname) + indent = es_printf("%s: ",fname); + + if (indent>40) + { + es_printf ("\n"); + indent=0; + } + + if (algo==DIGEST_ALGO_RMD160) + indent += es_printf("RMD160 = "); + else if (algo>0) + indent += es_printf("%6s = ", gcry_md_algo_name (algo)); + else + algo = abs(algo); + + count = indent; + + p = gcry_md_read (md, algo); + n = gcry_md_get_algo_dlen (algo); + + count += es_printf ("%02X",*p++); + + for(i=1;i79) + { + es_printf ("\n%*s",indent," "); + count = indent; + } + else + count += es_printf(" "); + + if (!(i%8)) + count += es_printf(" "); + } + else if (n==20) + { + if(!(i%2)) + { + if(count+4>79) + { + es_printf ("\n%*s",indent," "); + count=indent; + } + else + count += es_printf(" "); + } + + if (!(i%10)) + count += es_printf(" "); + } + else + { + if(!(i%4)) + { + if (count+8>79) + { + es_printf ("\n%*s",indent," "); + count=indent; + } + else + count += es_printf(" "); + } + } + + count += es_printf("%02X",*p); + } + + es_printf ("\n"); +} + +static void +print_hashline( gcry_md_hd_t 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 == '%' ) + es_printf ("%%%02X", *p ); + else + es_putc (*p, es_stdout); + } + } + es_putc (':', es_stdout); + es_printf ("%d:", algo); + p = gcry_md_read (md, algo); + n = gcry_md_get_algo_dlen (algo); + for(i=0; i < n ; i++, p++ ) + es_printf ("%02X", *p); + es_fputs (":\n", es_stdout); +} + + +static void +print_mds( const char *fname, int algo ) +{ + estream_t fp; + char buf[1024]; + size_t n; + gcry_md_hd_t md; + + if (!fname) + { + fp = es_stdin; + es_set_binary (fp); + } + else + { + fp = es_fopen (fname, "rb" ); + if (fp && is_secured_file (es_fileno (fp))) + { + es_fclose (fp); + fp = NULL; + gpg_err_set_errno (EPERM); + } + } + if (!fp) + { + log_error("%s: %s\n", fname?fname:"[stdin]", strerror(errno) ); + return; + } + + gcry_md_open (&md, 0, 0); + if (algo) + gcry_md_enable (md, algo); + else + { + if (!gcry_md_test_algo (GCRY_MD_MD5)) + gcry_md_enable (md, GCRY_MD_MD5); + gcry_md_enable (md, GCRY_MD_SHA1); + if (!gcry_md_test_algo (GCRY_MD_RMD160)) + gcry_md_enable (md, GCRY_MD_RMD160); + if (!gcry_md_test_algo (GCRY_MD_SHA224)) + gcry_md_enable (md, GCRY_MD_SHA224); + if (!gcry_md_test_algo (GCRY_MD_SHA256)) + gcry_md_enable (md, GCRY_MD_SHA256); + if (!gcry_md_test_algo (GCRY_MD_SHA384)) + gcry_md_enable (md, GCRY_MD_SHA384); + if (!gcry_md_test_algo (GCRY_MD_SHA512)) + gcry_md_enable (md, GCRY_MD_SHA512); + } + + while ((n=es_fread (buf, 1, DIM(buf), fp))) + gcry_md_write (md, buf, n); + + if (es_ferror(fp)) + log_error ("%s: %s\n", fname?fname:"[stdin]", strerror(errno)); + else + { + gcry_md_final (md); + if (opt.with_colons) + { + if ( algo ) + print_hashline (md, algo, fname); + else + { + if (!gcry_md_test_algo (GCRY_MD_MD5)) + print_hashline( md, GCRY_MD_MD5, fname ); + print_hashline( md, GCRY_MD_SHA1, fname ); + if (!gcry_md_test_algo (GCRY_MD_RMD160)) + print_hashline( md, GCRY_MD_RMD160, fname ); + if (!gcry_md_test_algo (GCRY_MD_SHA224)) + print_hashline (md, GCRY_MD_SHA224, fname); + if (!gcry_md_test_algo (GCRY_MD_SHA256)) + print_hashline( md, GCRY_MD_SHA256, fname ); + if (!gcry_md_test_algo (GCRY_MD_SHA384)) + print_hashline ( md, GCRY_MD_SHA384, fname ); + if (!gcry_md_test_algo (GCRY_MD_SHA512)) + print_hashline ( md, GCRY_MD_SHA512, fname ); + } + } + else + { + if (algo) + print_hex (md, -algo, fname); + else + { + if (!gcry_md_test_algo (GCRY_MD_MD5)) + print_hex (md, GCRY_MD_MD5, fname); + print_hex (md, GCRY_MD_SHA1, fname ); + if (!gcry_md_test_algo (GCRY_MD_RMD160)) + print_hex (md, GCRY_MD_RMD160, fname ); + if (!gcry_md_test_algo (GCRY_MD_SHA224)) + print_hex (md, GCRY_MD_SHA224, fname); + if (!gcry_md_test_algo (GCRY_MD_SHA256)) + print_hex (md, GCRY_MD_SHA256, fname ); + if (!gcry_md_test_algo (GCRY_MD_SHA384)) + print_hex (md, GCRY_MD_SHA384, fname ); + if (!gcry_md_test_algo (GCRY_MD_SHA512)) + print_hex (md, GCRY_MD_SHA512, fname ); + } + } + } + gcry_md_close (md); + + if (fp != es_stdin) + es_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_t sl; + + if(*string=='!') + { + string++; + critical=1; + } + + for(i=0;iflags |= 1; +} + +static void +add_keyserver_url( const char *string, int which ) +{ + unsigned int i,critical=0; + strlist_t sl; + + if(*string=='!') + { + string++; + critical=1; + } + + for(i=0;iflags |= 1; +} + + +static void +read_sessionkey_from_fd (int fd) +{ + int i, len; + char *line; + + if (! gnupg_fd_valid (fd)) + log_fatal ("override-session-key-fd is invalid: %s\n", strerror (errno)); + + for (line = NULL, i = len = 100; ; i++ ) + { + if (i >= len-1 ) + { + char *tmp = line; + len += 100; + line = xmalloc_secure (len); + if (tmp) + { + memcpy (line, tmp, i); + xfree (tmp); + } + else + i=0; + } + if (read (fd, line + i, 1) != 1 || line[i] == '\n') + break; + } + line[i] = 0; + log_debug ("seskey: %s\n", line); + gpgrt_annotate_leaked_object (line); + opt.override_session_key = line; +} diff --git a/g10/gpg.h b/g10/gpg.h new file mode 100644 index 0000000..1bad551 --- /dev/null +++ b/g10/gpg.h @@ -0,0 +1,110 @@ +/* gpg.h - top level include file for gpg etc. + * Copyright (C) 2003, 2006, 2010 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ +#ifndef GNUPG_G10_GPG_H +#define GNUPG_G10_GPG_H + +/* Note, that this file should be the first one after the system + header files. This is required to set the error source to the + correct value and may be of advantage if we ever have to do + special things. */ + +#ifdef HAVE_W32_SYSTEM +# define WIN32_LEAN_AND_MEAN 1 +#endif + +#ifdef GPG_ERR_SOURCE_DEFAULT +#error GPG_ERR_SOURCE_DEFAULT already defined +#endif +#define GPG_ERR_SOURCE_DEFAULT GPG_ERR_SOURCE_GPG +#define map_assuan_err(a) \ + map_assuan_err_with_source (GPG_ERR_SOURCE_DEFAULT, (a)) +#include +#include + + +/* Number of bits we accept when reading or writing MPIs. */ +#define MAX_EXTERN_MPI_BITS 16384 + +/* The maximum length of a binary fingerprints. This is used to + provide a static buffer and will be increased if we need to support + longer fingerprints. + Warning: At some places we still use 20 instead of this macro. */ +#define MAX_FINGERPRINT_LEN 20 + +/* The maximum length of a formatted fingerprint as returned by + format_hexfingerprint(). */ +#define MAX_FORMATTED_FINGERPRINT_LEN 50 + + +/* + Forward declarations. + */ + +/* Object used to keep state locally to server.c . */ +struct server_local_s; + +/* Object used to keep state locally to call-dirmngr.c . */ +struct dirmngr_local_s; +typedef struct dirmngr_local_s *dirmngr_local_t; + +/* Object used to describe a keyblock node. */ +typedef struct kbnode_struct *KBNODE; /* Deprecated use kbnode_t. */ +typedef struct kbnode_struct *kbnode_t; + +/* The handle for keydb operations. */ +typedef struct keydb_handle *KEYDB_HANDLE; + +/* TOFU database meta object. */ +struct tofu_dbs_s; +typedef struct tofu_dbs_s *tofu_dbs_t; + + +#if SIZEOF_UNSIGNED_LONG == 8 +# define SERVER_CONTROL_MAGIC 0x53616c696e676572 +#else +# define SERVER_CONTROL_MAGIC 0x53616c69 +#endif + +/* Session control object. This object is passed to most functions to + convey the status of a session. Note that the defaults are set by + gpg_init_default_ctrl(). */ +struct server_control_s +{ + /* Always has the value SERVER_CONTROL_MAGIC. */ + unsigned long magic; + + /* Local data for server.c */ + struct server_local_s *server_local; + + /* Local data for call-dirmngr.c */ + dirmngr_local_t dirmngr_local; + + /* Local data for tofu.c */ + struct { + tofu_dbs_t dbs; + int batch_updated_wanted; + } tofu; + + /* This is used to cache a key data base handle. */ + KEYDB_HANDLE cached_getkey_kdb; +}; + + + +#endif /*GNUPG_G10_GPG_H*/ diff --git a/g10/gpg.w32-manifest.in b/g10/gpg.w32-manifest.in new file mode 100644 index 0000000..24484db --- /dev/null +++ b/g10/gpg.w32-manifest.in @@ -0,0 +1,18 @@ + + +GNU Privacy Guard (OpenPGP tool) + + + + + + + + + + + diff --git a/g10/gpgcompose.c b/g10/gpgcompose.c new file mode 100644 index 0000000..d82995d --- /dev/null +++ b/g10/gpgcompose.c @@ -0,0 +1,3089 @@ +/* gpgcompose.c - Maintainer tool to create OpenPGP messages by hand. + * Copyright (C) 2016 g10 Code GmbH + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include +#include + +#define INCLUDED_BY_MAIN_MODULE 1 +#include "gpg.h" +#include "packet.h" +#include "keydb.h" +#include "main.h" +#include "options.h" + +static int do_debug; +#define debug(fmt, ...) \ + do { if (do_debug) log_debug (fmt, ##__VA_ARGS__); } while (0) + +/* --encryption, for instance, adds a filter in front of out. There + is an operator (--encryption-pop) to end this. We use the + following infrastructure to make it easy to pop the state. */ +struct filter +{ + void *func; + void *context; + int pkttype; + int partial_block_mode; + struct filter *next; +}; + + +/* Hack to ass CTRL to some functions. */ +static ctrl_t global_ctrl; + + +static struct filter *filters; + +static void +filter_push (iobuf_t out, void *func, void *context, + int type, int partial_block_mode) +{ + gpg_error_t err; + struct filter *f = xmalloc_clear (sizeof (*f)); + f->next = filters; + f->func = func; + f->context = context; + f->pkttype = type; + f->partial_block_mode = partial_block_mode; + + filters = f; + + err = iobuf_push_filter (out, func, context); + if (err) + log_fatal ("Adding filter: %s\n", gpg_strerror (err)); +} + +static void +filter_pop (iobuf_t out, int expected_type) +{ + gpg_error_t err; + struct filter *f = filters; + + log_assert (f); + + if (f->pkttype != expected_type) + log_fatal ("Attempted to pop a %s container, " + "but current container is a %s container.\n", + pkttype_str (f->pkttype), pkttype_str (expected_type)); + + if (f->pkttype == PKT_ENCRYPTED) + { + err = iobuf_pop_filter (out, f->func, f->context); + if (err) + log_fatal ("Popping encryption filter: %s\n", gpg_strerror (err)); + } + else + log_fatal ("FILTERS appears to be corrupted.\n"); + + if (f->partial_block_mode) + iobuf_set_partial_body_length_mode (out, 0); + + filters = f->next; + xfree (f); +} + +/* Return if CIPHER_ID is a valid cipher. */ +static int +valid_cipher (int cipher_id) +{ + return (cipher_id == CIPHER_ALGO_IDEA + || cipher_id == CIPHER_ALGO_3DES + || cipher_id == CIPHER_ALGO_CAST5 + || cipher_id == CIPHER_ALGO_BLOWFISH + || cipher_id == CIPHER_ALGO_AES + || cipher_id == CIPHER_ALGO_AES192 + || cipher_id == CIPHER_ALGO_AES256 + || cipher_id == CIPHER_ALGO_TWOFISH + || cipher_id == CIPHER_ALGO_CAMELLIA128 + || cipher_id == CIPHER_ALGO_CAMELLIA192 + || cipher_id == CIPHER_ALGO_CAMELLIA256); +} + +/* Parse a session key encoded as a string of the form x:HEXDIGITS + where x is the algorithm id. (This is the format emitted by gpg + --show-session-key.) */ +struct session_key +{ + int algo; + int keylen; + char *key; +}; + +static struct session_key +parse_session_key (const char *option, char *p, int require_algo) +{ + char *tail; + struct session_key sk; + + memset (&sk, 0, sizeof (sk)); + + /* Check for the optional "cipher-id:" at the start of the + string. */ + errno = 0; + sk.algo = strtol (p, &tail, 10); + if (! errno && tail && *tail == ':') + { + if (! valid_cipher (sk.algo)) + log_info ("%s: %d is not a known cipher (but using anyways)\n", + option, sk.algo); + p = tail + 1; + } + else if (require_algo) + log_fatal ("%s: Session key must have the form algo:HEXCHARACTERS.\n", + option); + else + sk.algo = 0; + + /* Ignore a leading 0x. */ + if (p[0] == '0' && p[1] == 'x') + p += 2; + + if (strlen (p) % 2 != 0) + log_fatal ("%s: session key must consist of an even number of hexadecimal characters.\n", + option); + + sk.keylen = strlen (p) / 2; + sk.key = xmalloc (sk.keylen); + + if (hex2bin (p, sk.key, sk.keylen) == -1) + log_fatal ("%s: Session key must only contain hexadecimal characters\n", + option); + + return sk; +} + +/* A callback. + + OPTION_STR is the option that was matched. ARGC is the number of + arguments following the option and ARGV are those arguments. + (Thus, argv[0] is the first string following the option and + argv[-1] is the option.) + + COOKIE is the opaque value passed to process_options. */ +typedef int (*option_prcessor_t) (const char *option_str, + int argc, char *argv[], + void *cookie); + +struct option +{ + /* The option that this matches. This must start with "--" or be + the empty string. The empty string matches bare arguments. */ + const char *option; + /* The function to call to process this option. */ + option_prcessor_t func; + /* Documentation. */ + const char *help; +}; + +/* Merge two lists of options. Note: this makes a shallow copy! The + caller must xfree() the result. */ +static struct option * +merge_options (struct option a[], struct option b[]) +{ + int i, j; + struct option *c; + + for (i = 0; a[i].option; i ++) + ; + for (j = 0; b[j].option; j ++) + ; + + c = xmalloc ((i + j + 1) * sizeof (struct option)); + memcpy (c, a, i * sizeof (struct option)); + memcpy (&c[i], b, j * sizeof (struct option)); + c[i + j].option = NULL; + + if (a[i].help && b[j].help) + c[i + j].help = xasprintf ("%s\n\n%s", a[i].help, b[j].help); + else if (a[i].help) + c[i + j].help = a[i].help; + else if (b[j].help) + c[i + j].help = b[j].help; + + return c; +} + +/* Returns whether ARG is an option. All options start with --. */ +static int +is_option (const char *arg) +{ + return arg[0] == '-' && arg[1] == '-'; +} + +/* OPTIONS is a NULL terminated array of struct option:s. Finds the + entry that is the same as ARG. Returns -1 if no entry is found. + The empty string option matches bare arguments. */ +static int +match_option (const struct option options[], const char *arg) +{ + int i; + int bare_arg = ! is_option (arg); + + for (i = 0; options[i].option; i ++) + if ((! bare_arg && strcmp (options[i].option, arg) == 0) + /* Non-options match the empty string. */ + || (bare_arg && options[i].option[0] == '\0')) + return i; + + return -1; +} + +static void +show_help (struct option options[]) +{ + int i; + int max_length = 0; + int space; + + for (i = 0; options[i].option; i ++) + { + const char *option = options[i].option[0] ? options[i].option : "ARG"; + int l = strlen (option); + if (l > max_length) + max_length = l; + } + + space = 72 - (max_length + 2); + if (space < 40) + space = 40; + + for (i = 0; ; i ++) + { + const char *option = options[i].option; + const char *help = options[i].help; + + int l; + int j; + char *tmp; + char *formatted; + char *p; + char *newline; + + if (! option && ! help) + break; + + if (option) + { + const char *o = option[0] ? option : "ARG"; + l = strlen (o); + fprintf (stdout, "%s", o); + } + + if (! help) + { + fputc ('\n', stdout); + continue; + } + + if (option) + for (j = l; j < max_length + 2; j ++) + fputc (' ', stdout); + +#define BOLD_START "\033[1m" +#define NORMAL_RESTORE "\033[0m" +#define BOLD(x) BOLD_START x NORMAL_RESTORE + + if (! option || options[i].func) + tmp = (char *) help; + else + tmp = xasprintf ("%s " BOLD("(Unimplemented.)"), help); + + if (! option) + space = 72; + formatted = format_text (tmp, space, space + 4); + if (!formatted) + abort (); + + if (tmp != help) + xfree (tmp); + + if (! option) + { + printf ("\n%s\n", formatted); + break; + } + + for (p = formatted; + p && *p; + p = (*newline == '\0') ? newline : newline + 1) + { + newline = strchr (p, '\n'); + if (! newline) + newline = &p[strlen (p)]; + + l = (size_t) newline - (size_t) p; + + if (p != formatted) + for (j = 0; j < max_length + 2; j ++) + fputc (' ', stdout); + + fwrite (p, l, 1, stdout); + fputc ('\n', stdout); + } + + xfree (formatted); + } +} + +/* Return value is number of consumed argv elements. */ +static int +process_options (const char *parent_option, + struct option break_options[], + struct option local_options[], void *lcookie, + struct option global_options[], void *gcookie, + int argc, char *argv[]) +{ + int i; + for (i = 0; i < argc; i ++) + { + int j; + struct option *option; + void *cookie; + int bare_arg; + option_prcessor_t func; + int consumed; + + if (break_options) + { + j = match_option (break_options, argv[i]); + if (j != -1) + /* Match. Break out. */ + return i; + } + + j = match_option (local_options, argv[i]); + if (j == -1) + { + if (global_options) + j = match_option (global_options, argv[i]); + if (j == -1) + { + if (strcmp (argv[i], "--help") == 0) + { + if (! global_options) + show_help (local_options); + else + { + struct option *combined + = merge_options (local_options, global_options); + show_help (combined); + xfree (combined); + } + g10_exit (0); + } + + if (parent_option) + log_fatal ("%s: Unknown option: %s\n", parent_option, argv[i]); + else + log_fatal ("Unknown option: %s\n", argv[i]); + } + + option = &global_options[j]; + cookie = gcookie; + } + else + { + option = &local_options[j]; + cookie = lcookie; + } + + bare_arg = strcmp (option->option, "") == 0; + + func = option->func; + if (! func) + { + if (bare_arg) + log_fatal ("Bare arguments unimplemented.\n"); + else + log_fatal ("Unimplemented option: %s\n", + option->option); + } + + consumed = func (bare_arg ? parent_option : argv[i], + argc - i - !bare_arg, &argv[i + !bare_arg], + cookie); + i += consumed; + if (bare_arg) + i --; + } + + return i; +} + +/* The keys, subkeys, user ids and user attributes in the order that + they were added. */ +PACKET components[20]; +/* The number of components. */ +int ncomponents; + +static int +add_component (int pkttype, void *component) +{ + int i = ncomponents ++; + + log_assert (i < sizeof (components) / sizeof (components[0])); + log_assert (pkttype == PKT_PUBLIC_KEY + || pkttype == PKT_PUBLIC_SUBKEY + || pkttype == PKT_SECRET_KEY + || pkttype == PKT_SECRET_SUBKEY + || pkttype == PKT_USER_ID + || pkttype == PKT_ATTRIBUTE); + + components[i].pkttype = pkttype; + components[i].pkt.generic = component; + + return i; +} + +static void +dump_component (PACKET *pkt) +{ + struct kbnode_struct kbnode; + + if (! do_debug) + return; + + memset (&kbnode, 0, sizeof (kbnode)); + kbnode.pkt = pkt; + dump_kbnode (&kbnode); +} + +/* Returns the first primary key in COMPONENTS or NULL if there is + none. */ +static PKT_public_key * +primary_key (void) +{ + int i; + for (i = 0; i < ncomponents; i ++) + if (components[i].pkttype == PKT_PUBLIC_KEY) + return components[i].pkt.public_key; + return NULL; +} + +/* The last session key (updated when adding a SK-ESK, PK-ESK or SED + packet. */ +static DEK session_key; + +static int user_id (const char *option, int argc, char *argv[], + void *cookie); +static int public_key (const char *option, int argc, char *argv[], + void *cookie); +static int sk_esk (const char *option, int argc, char *argv[], + void *cookie); +static int pk_esk (const char *option, int argc, char *argv[], + void *cookie); +static int encrypted (const char *option, int argc, char *argv[], + void *cookie); +static int encrypted_pop (const char *option, int argc, char *argv[], + void *cookie); +static int literal (const char *option, int argc, char *argv[], + void *cookie); +static int signature (const char *option, int argc, char *argv[], + void *cookie); +static int copy (const char *option, int argc, char *argv[], + void *cookie); + +static struct option major_options[] = { + { "--user-id", user_id, "Create a user id packet." }, + { "--public-key", public_key, "Create a public key packet." }, + { "--private-key", NULL, "Create a private key packet." }, + { "--public-subkey", public_key, "Create a subkey packet." }, + { "--private-subkey", NULL, "Create a private subkey packet." }, + { "--sk-esk", sk_esk, + "Create a symmetric-key encrypted session key packet." }, + { "--pk-esk", pk_esk, + "Create a public-key encrypted session key packet." }, + { "--encrypted", encrypted, "Create a symmetrically encrypted data packet." }, + { "--encrypted-mdc", encrypted, + "Create a symmetrically encrypted and integrity protected data packet." }, + { "--encrypted-pop", encrypted_pop, + "Pop the most recent encryption container started by either" + " --encrypted or --encrypted-mdc." }, + { "--compressed", NULL, "Create a compressed data packet." }, + { "--literal", literal, "Create a literal (plaintext) data packet." }, + { "--signature", signature, "Create a signature packet." }, + { "--onepass-sig", NULL, "Create a one-pass signature packet." }, + { "--copy", copy, "Copy the specified file." }, + { NULL, NULL, + "To get more information about a given command, use:\n\n" + " $ gpgcompose --command --help to list a command's options."}, +}; + +static struct option global_options[] = { + { NULL, NULL, NULL }, +}; + +/* Make our lives easier and use a static limit for the user name. + 10k is way more than enough anyways... */ +const int user_id_max_len = 10 * 1024; + +static int +user_id_name (const char *option, int argc, char *argv[], void *cookie) +{ + PKT_user_id *uid = cookie; + int l; + + if (argc == 0) + log_fatal ("Usage: %s USER_ID\n", option); + + if (uid->len) + log_fatal ("Attempt to set user id multiple times.\n"); + + l = strlen (argv[0]); + if (l > user_id_max_len) + log_fatal ("user id too long (max: %d)\n", user_id_max_len); + + memcpy (uid->name, argv[0], l); + uid->name[l] = 0; + uid->len = l; + + return 1; +} + +static struct option user_id_options[] = { + { "", user_id_name, + "Set the user id. This is usually in the format " + "\"Name (comment) \"" }, + { NULL, NULL, + "Example:\n\n" + " $ gpgcompose --user-id \"USERID\" | " GPG_NAME " --list-packets" } +}; + +static int +user_id (const char *option, int argc, char *argv[], void *cookie) +{ + iobuf_t out = cookie; + gpg_error_t err; + PKT_user_id *uid = xmalloc_clear (sizeof (*uid) + user_id_max_len); + int c = add_component (PKT_USER_ID, uid); + int processed; + + processed = process_options (option, + major_options, + user_id_options, uid, + global_options, NULL, + argc, argv); + + if (! uid->len) + log_fatal ("%s: user id not given", option); + + err = build_packet (out, &components[c]); + if (err) + log_fatal ("Serializing user id packet: %s\n", gpg_strerror (err)); + + debug ("Wrote user id packet:\n"); + dump_component (&components[c]); + + return processed; +} + +static int +pk_search_terms (const char *option, int argc, char *argv[], void *cookie) +{ + gpg_error_t err; + KEYDB_HANDLE hd; + KEYDB_SEARCH_DESC desc; + kbnode_t kb; + PKT_public_key *pk = cookie; + PKT_public_key *pk_ref; + int i; + + if (argc == 0) + log_fatal ("Usage: %s KEYID\n", option); + + if (pk->pubkey_algo) + log_fatal ("%s: multiple keys provided\n", option); + + err = classify_user_id (argv[0], &desc, 0); + if (err) + log_fatal ("search terms '%s': %s\n", argv[0], gpg_strerror (err)); + + hd = keydb_new (); + + err = keydb_search (hd, &desc, 1, NULL); + if (err) + log_fatal ("looking up '%s': %s\n", argv[0], gpg_strerror (err)); + + err = keydb_get_keyblock (hd, &kb); + if (err) + log_fatal ("retrieving keyblock for '%s': %s\n", + argv[0], gpg_strerror (err)); + + keydb_release (hd); + + pk_ref = kb->pkt->pkt.public_key; + + /* Copy the timestamp (if not already set), algo and public key + parameters. */ + if (! pk->timestamp) + pk->timestamp = pk_ref->timestamp; + pk->pubkey_algo = pk_ref->pubkey_algo; + for (i = 0; i < pubkey_get_npkey (pk->pubkey_algo); i ++) + pk->pkey[i] = gcry_mpi_copy (pk_ref->pkey[i]); + + release_kbnode (kb); + + return 1; +} + +static int +pk_timestamp (const char *option, int argc, char *argv[], void *cookie) +{ + PKT_public_key *pk = cookie; + char *tail = NULL; + + if (argc == 0) + log_fatal ("Usage: %s TIMESTAMP\n", option); + + errno = 0; + pk->timestamp = parse_timestamp (argv[0], &tail); + if (errno || (tail && *tail)) + log_fatal ("Invalid value passed to %s (%s)\n", option, argv[0]); + + return 1; +} + +#define TIMESTAMP_HELP \ + "Either as seconds since the epoch or as an ISO 8601 formatted " \ + "string (yyyymmddThhmmss, where the T is a literal)." + +static struct option pk_options[] = { + { "--timestamp", pk_timestamp, + "The creation time. " TIMESTAMP_HELP }, + { "", pk_search_terms, + "The key to copy the creation time and public key parameters from." }, + { NULL, NULL, + "Example:\n\n" + " $ gpgcompose --public-key $KEYID --user-id \"USERID\" \\\n" + " | " GPG_NAME " --list-packets" } +}; + +static int +public_key (const char *option, int argc, char *argv[], void *cookie) +{ + gpg_error_t err; + iobuf_t out = cookie; + PKT_public_key *pk; + int c; + int processed; + int t = (strcmp (option, "--public-key") == 0 + ? PKT_PUBLIC_KEY : PKT_PUBLIC_SUBKEY); + + (void) option; + + pk = xmalloc_clear (sizeof (*pk)); + pk->version = 4; + + c = add_component (t, pk); + + processed = process_options (option, + major_options, + pk_options, pk, + global_options, NULL, + argc, argv); + + if (! pk->pubkey_algo) + log_fatal ("%s: key to extract public key parameters from not given", + option); + + /* Clear the keyid in case we updated one of the relevant fields + after accessing it. */ + pk->keyid[0] = pk->keyid[1] = 0; + + err = build_packet (out, &components[c]); + if (err) + log_fatal ("serializing %s packet: %s\n", + t == PKT_PUBLIC_KEY ? "public key" : "subkey", + gpg_strerror (err)); + + debug ("Wrote %s packet:\n", + t == PKT_PUBLIC_KEY ? "public key" : "subkey"); + dump_component (&components[c]); + + return processed; +} + +struct signinfo +{ + /* Key with which to sign. */ + kbnode_t issuer_kb; + PKT_public_key *issuer_pk; + + /* Overrides the issuer's key id. */ + u32 issuer_keyid[2]; + /* Sets the issuer's keyid to the primary key's key id. */ + int issuer_keyid_self; + + /* Key to sign. */ + PKT_public_key *pk; + /* Subkey to sign. */ + PKT_public_key *sk; + /* User id to sign. */ + PKT_user_id *uid; + + int class; + int digest_algo; + u32 timestamp; + u32 key_expiration; + + byte *cipher_algorithms; + int cipher_algorithms_len; + byte *digest_algorithms; + int digest_algorithms_len; + byte *compress_algorithms; + int compress_algorithms_len; + + u32 expiration; + + int exportable_set; + int exportable; + + int revocable_set; + int revocable; + + int trust_level_set; + byte trust_args[2]; + + char *trust_scope; + + struct revocation_key *revocation_key; + int nrevocation_keys; + + struct notation *notations; + + byte *key_server_preferences; + int key_server_preferences_len; + + char *key_server; + + int primary_user_id_set; + int primary_user_id; + + char *policy_uri; + + byte *key_flags; + int key_flags_len; + + char *signers_user_id; + + byte reason_for_revocation_code; + char *reason_for_revocation; + + byte *features; + int features_len; + + /* Whether to corrupt the signature. */ + int corrupt; +}; + +static int +sig_issuer (const char *option, int argc, char *argv[], void *cookie) +{ + gpg_error_t err; + KEYDB_HANDLE hd; + KEYDB_SEARCH_DESC desc; + struct signinfo *si = cookie; + + if (argc == 0) + log_fatal ("Usage: %s KEYID\n", option); + + if (si->issuer_pk) + log_fatal ("%s: multiple keys provided\n", option); + + err = classify_user_id (argv[0], &desc, 0); + if (err) + log_fatal ("search terms '%s': %s\n", argv[0], gpg_strerror (err)); + + hd = keydb_new (); + + err = keydb_search (hd, &desc, 1, NULL); + if (err) + log_fatal ("looking up '%s': %s\n", argv[0], gpg_strerror (err)); + + err = keydb_get_keyblock (hd, &si->issuer_kb); + if (err) + log_fatal ("retrieving keyblock for '%s': %s\n", + argv[0], gpg_strerror (err)); + + keydb_release (hd); + + si->issuer_pk = si->issuer_kb->pkt->pkt.public_key; + + return 1; +} + +static int +sig_issuer_keyid (const char *option, int argc, char *argv[], void *cookie) +{ + gpg_error_t err; + KEYDB_SEARCH_DESC desc; + struct signinfo *si = cookie; + + if (argc == 0) + log_fatal ("Usage: %s KEYID|self\n", option); + + if (si->issuer_keyid[0] || si->issuer_keyid[1] || si->issuer_keyid_self) + log_fatal ("%s given multiple times.\n", option); + + if (strcasecmp (argv[0], "self") == 0) + { + si->issuer_keyid_self = 1; + return 1; + } + + err = classify_user_id (argv[0], &desc, 0); + if (err) + log_fatal ("search terms '%s': %s\n", argv[0], gpg_strerror (err)); + + if (desc.mode != KEYDB_SEARCH_MODE_LONG_KID) + log_fatal ("%s is not a valid long key id.\n", argv[0]); + + keyid_copy (si->issuer_keyid, desc.u.kid); + + return 1; +} + +static int +sig_pk (const char *option, int argc, char *argv[], void *cookie) +{ + struct signinfo *si = cookie; + int i; + char *tail = NULL; + + if (argc == 0) + log_fatal ("Usage: %s COMPONENT_INDEX\n", option); + + errno = 0; + i = strtoul (argv[0], &tail, 10); + if (errno || (tail && *tail)) + log_fatal ("Invalid value passed to %s (%s)\n", option, argv[0]); + + if (i >= ncomponents) + log_fatal ("%d: No such component (have %d components so far)\n", + i, ncomponents); + if (! (components[i].pkttype == PKT_PUBLIC_KEY + || components[i].pkttype == PKT_PUBLIC_SUBKEY)) + log_fatal ("Component %d is not a public key or a subkey.", i); + + if (strcmp (option, "--pk") == 0) + { + if (si->pk) + log_fatal ("%s already given.\n", option); + si->pk = components[i].pkt.public_key; + } + else if (strcmp (option, "--sk") == 0) + { + if (si->sk) + log_fatal ("%s already given.\n", option); + si->sk = components[i].pkt.public_key; + } + else + log_fatal ("Cannot handle %s\n", option); + + return 1; +} + +static int +sig_user_id (const char *option, int argc, char *argv[], void *cookie) +{ + struct signinfo *si = cookie; + int i; + char *tail = NULL; + + if (argc == 0) + log_fatal ("Usage: %s COMPONENT_INDEX\n", option); + if (si->uid) + log_fatal ("%s already given.\n", option); + + errno = 0; + i = strtoul (argv[0], &tail, 10); + if (errno || (tail && *tail)) + log_fatal ("Invalid value passed to %s (%s)\n", option, argv[0]); + + if (i >= ncomponents) + log_fatal ("%d: No such component (have %d components so far)\n", + i, ncomponents); + if (! (components[i].pkttype != PKT_USER_ID + || components[i].pkttype == PKT_ATTRIBUTE)) + log_fatal ("Component %d is not a public key or a subkey.", i); + + si->uid = components[i].pkt.user_id; + + return 1; +} + +static int +sig_class (const char *option, int argc, char *argv[], void *cookie) +{ + struct signinfo *si = cookie; + int i; + char *tail = NULL; + + if (argc == 0) + log_fatal ("Usage: %s CLASS\n", option); + + errno = 0; + i = strtoul (argv[0], &tail, 0); + if (errno || (tail && *tail)) + log_fatal ("Invalid value passed to %s (%s)\n", option, argv[0]); + + si->class = i; + + return 1; +} + +static int +sig_digest (const char *option, int argc, char *argv[], void *cookie) +{ + struct signinfo *si = cookie; + int i; + char *tail = NULL; + + if (argc == 0) + log_fatal ("Usage: %s DIGEST_ALGO\n", option); + + errno = 0; + i = strtoul (argv[0], &tail, 10); + if (errno || (tail && *tail)) + log_fatal ("Invalid value passed to %s (%s)\n", option, argv[0]); + + si->digest_algo = i; + + return 1; +} + +static int +sig_timestamp (const char *option, int argc, char *argv[], void *cookie) +{ + struct signinfo *si = cookie; + char *tail = NULL; + + if (argc == 0) + log_fatal ("Usage: %s TIMESTAMP\n", option); + + errno = 0; + si->timestamp = parse_timestamp (argv[0], &tail); + if (errno || (tail && *tail)) + log_fatal ("Invalid value passed to %s (%s)\n", option, argv[0]); + + return 1; +} + +static int +sig_expiration (const char *option, int argc, char *argv[], void *cookie) +{ + struct signinfo *si = cookie; + int is_expiration = strcmp (option, "--expiration") == 0; + u32 *i = is_expiration ? &si->expiration : &si->key_expiration; + + if (! is_expiration) + log_assert (strcmp (option, "--key-expiration") == 0); + + if (argc == 0) + log_fatal ("Usage: %s DURATION\n", option); + + *i = parse_expire_string (argv[0]); + if (*i == (u32)-1) + log_fatal ("Invalid value passed to %s (%s)\n", option, argv[0]); + + return 1; +} + +static int +sig_int_list (const char *option, int argc, char *argv[], void *cookie) +{ + struct signinfo *si = cookie; + int nvalues = 1; + char *values = xmalloc (nvalues * sizeof (values[0])); + char *tail = argv[0]; + int i; + byte **a; + int *n; + + if (argc == 0) + log_fatal ("Usage: %s VALUE[,VALUE...]\n", option); + + for (i = 0; tail && *tail; i ++) + { + int v; + char *old_tail = tail; + + errno = 0; + v = strtol (tail, &tail, 0); + if (errno || old_tail == tail || (tail && !(*tail == ',' || *tail == 0))) + log_fatal ("Invalid value passed to %s (%s). " + "Expected a list of comma separated numbers\n", + option, argv[0]); + + if (! (0 <= v && v <= 255)) + log_fatal ("%s: %d is out of range (Expected: 0-255)\n", option, v); + + if (i == nvalues) + { + nvalues *= 2; + values = xrealloc (values, nvalues * sizeof (values[0])); + } + + values[i] = v; + + if (*tail == ',') + tail ++; + else + log_assert (*tail == 0); + } + + if (strcmp ("--cipher-algos", option) == 0) + { + a = &si->cipher_algorithms; + n = &si->cipher_algorithms_len; + } + else if (strcmp ("--digest-algos", option) == 0) + { + a = &si->digest_algorithms; + n = &si->digest_algorithms_len; + } + else if (strcmp ("--compress-algos", option) == 0) + { + a = &si->compress_algorithms; + n = &si->compress_algorithms_len; + } + else + log_fatal ("Cannot handle %s\n", option); + + if (*a) + log_fatal ("Option %s given multiple times.\n", option); + + *a = values; + *n = i; + + return 1; +} + +static int +sig_flag (const char *option, int argc, char *argv[], void *cookie) +{ + struct signinfo *si = cookie; + int range[2] = {0, 255}; + char *tail; + int v; + + if (strcmp (option, "--primary-user-id") == 0) + range[1] = 1; + + if (argc <= 1) + { + if (range[0] == 0 && range[1] == 1) + log_fatal ("Usage: %s 0|1\n", option); + else + log_fatal ("Usage: %s %d-%d\n", option, range[0], range[1]); + } + + errno = 0; + v = strtol (argv[0], &tail, 0); + if (errno || (tail && *tail) || !(range[0] <= v && v <= range[1])) + log_fatal ("Invalid value passed to %s (%s). Expected %d-%d\n", + option, argv[0], range[0], range[1]); + + if (strcmp (option, "--exportable") == 0) + { + si->exportable_set = 1; + si->exportable = v; + } + else if (strcmp (option, "--revocable") == 0) + { + si->revocable_set = 1; + si->revocable = v; + } + else if (strcmp (option, "--primary-user-id") == 0) + { + si->primary_user_id_set = 1; + si->primary_user_id = v; + } + else + log_fatal ("Cannot handle %s\n", option); + + return 1; +} + +static int +sig_trust_level (const char *option, int argc, char *argv[], void *cookie) +{ + struct signinfo *si = cookie; + int i; + char *tail; + + if (argc <= 1) + log_fatal ("Usage: %s DEPTH TRUST_AMOUNT\n", option); + + for (i = 0; i < sizeof (si->trust_args) / sizeof (si->trust_args[0]); i ++) + { + int v; + + errno = 0; + v = strtol (argv[i], &tail, 0); + if (errno || (tail && *tail) || !(0 <= v && v <= 255)) + log_fatal ("Invalid value passed to %s (%s). Expected 0-255\n", + option, argv[i]); + + si->trust_args[i] = v; + } + + si->trust_level_set = 1; + + return 2; +} + +static int +sig_string_arg (const char *option, int argc, char *argv[], void *cookie) +{ + struct signinfo *si = cookie; + char *p = argv[0]; + char **s; + + if (argc == 0) + log_fatal ("Usage: %s STRING\n", option); + + if (strcmp (option, "--trust-scope") == 0) + s = &si->trust_scope; + else if (strcmp (option, "--key-server") == 0) + s = &si->key_server; + else if (strcmp (option, "--signers-user-id") == 0) + s = &si->signers_user_id; + else if (strcmp (option, "--policy-uri") == 0) + s = &si->policy_uri; + else + log_fatal ("Cannot handle %s\n", option); + + if (*s) + log_fatal ("%s already given.\n", option); + + *s = xstrdup (p); + + return 1; +} + +static int +sig_revocation_key (const char *option, int argc, char *argv[], void *cookie) +{ + gpg_error_t err; + struct signinfo *si = cookie; + int v; + char *tail; + PKT_public_key pk; + struct revocation_key *revkey; + + if (argc < 2) + log_fatal ("Usage: %s CLASS KEYID\n", option); + + memset (&pk, 0, sizeof (pk)); + + errno = 0; + v = strtol (argv[0], &tail, 16); + if (errno || (tail && *tail) || !(0 <= v && v <= 255)) + log_fatal ("%s: Invalid class value (%s). Expected 0-255\n", + option, argv[0]); + + pk.req_usage = PUBKEY_USAGE_SIG; + err = get_pubkey_byname (NULL, GET_PUBKEY_NO_AKL, + NULL, &pk, argv[1], NULL, NULL, 1); + if (err) + log_fatal ("looking up key %s: %s\n", argv[1], gpg_strerror (err)); + + si->nrevocation_keys ++; + si->revocation_key = xrealloc (si->revocation_key, + si->nrevocation_keys + * sizeof (*si->revocation_key)); + revkey = &si->revocation_key[si->nrevocation_keys - 1]; + + revkey->class = v; + revkey->algid = pk.pubkey_algo; + fingerprint_from_pk (&pk, revkey->fpr, NULL); + + release_public_key_parts (&pk); + + return 2; +} + +static int +sig_notation (const char *option, int argc, char *argv[], void *cookie) +{ + struct signinfo *si = cookie; + int is_blob = strcmp (option, "--notation") != 0; + struct notation *notation; + char *p = argv[0]; + int p_free = 0; + char *data; + int data_size; + int data_len; + + if (argc == 0) + log_fatal ("Usage: %s [!<]name=value\n", option); + + if ((p[0] == '!' && p[1] == '<') || p[0] == '<') + /* Read from a file. */ + { + char *filename = NULL; + iobuf_t in; + int prefix; + + if (p[0] == '<') + p ++; + else + { + /* Remove the '<', which string_to_notation does not + understand, and preserve the '!'. */ + p = xstrdup (&p[1]); + p_free = 1; + p[0] = '!'; + } + + filename = strchr (p, '='); + if (! filename) + log_fatal ("No value specified. Usage: %s [!<]name=value\n", + option); + filename ++; + + prefix = (size_t) filename - (size_t) p; + + errno = 0; + in = iobuf_open (filename); + if (! in) + log_fatal ("Opening '%s': %s\n", + filename, errno ? strerror (errno): "unknown error"); + + /* A notation can be at most about a few dozen bytes short of + 64k. Since this is relatively small, we just allocate that + much instead of trying to dynamically size a buffer. */ + data_size = 64 * 1024; + data = xmalloc (data_size); + log_assert (prefix <= data_size); + memcpy (data, p, prefix); + + data_len = iobuf_read (in, &data[prefix], data_size - prefix - 1); + if (data_len == -1) + /* EOF => 0 bytes read. */ + data_len = 0; + + if (data_len == data_size - prefix - 1) + /* Technically, we should do another read and check for EOF, + but what's one byte more or less? */ + log_fatal ("Notation data doesn't fit in the packet.\n"); + + iobuf_close (in); + + /* NUL terminate it. */ + data[prefix + data_len] = 0; + + if (p_free) + xfree (p); + p = data; + p_free = 1; + data = &p[prefix]; + + if (is_blob) + p[prefix - 1] = 0; + } + else if (is_blob) + { + data = strchr (p, '='); + if (! data) + { + data = p; + data_len = 0; + } + else + { + p = xstrdup (p); + p_free = 1; + + data = strchr (p, '='); + log_assert (data); + + /* NUL terminate the name. */ + *data = 0; + data ++; + data_len = strlen (data); + } + } + + if (is_blob) + notation = blob_to_notation (p, data, data_len); + else + notation = string_to_notation (p, 1); + if (! notation) + log_fatal ("creating notation: an unknown error occurred.\n"); + notation->next = si->notations; + si->notations = notation; + + if (p_free) + xfree (p); + + return 1; +} + +static int +sig_big_endian_arg (const char *option, int argc, char *argv[], void *cookie) +{ + struct signinfo *si = cookie; + char *p = argv[0]; + int i; + int l; + char *bytes; + + if (argc == 0) + log_fatal ("Usage: %s HEXDIGITS\n", option); + + /* Skip a leading "0x". */ + if (p[0] == '0' && p[1] == 'x') + p += 2; + + for (i = 0; i < strlen (p); i ++) + if (!hexdigitp (&p[i])) + log_fatal ("%s: argument ('%s') must consist of hex digits.\n", + option, p); + if (strlen (p) % 2 != 0) + log_fatal ("%s: argument ('%s') must contain an even number of hex digits.\n", + option, p); + + l = strlen (p) / 2; + bytes = xmalloc (l); + hex2bin (p, bytes, l); + + if (strcmp (option, "--key-server-preferences") == 0) + { + if (si->key_server_preferences) + log_fatal ("%s given multiple times.\n", option); + si->key_server_preferences = bytes; + si->key_server_preferences_len = l; + } + else if (strcmp (option, "--key-flags") == 0) + { + if (si->key_flags) + log_fatal ("%s given multiple times.\n", option); + si->key_flags = bytes; + si->key_flags_len = l; + } + else if (strcmp (option, "--features") == 0) + { + if (si->features) + log_fatal ("%s given multiple times.\n", option); + si->features = bytes; + si->features_len = l; + } + else + log_fatal ("Cannot handle %s\n", option); + + return 1; +} + +static int +sig_reason_for_revocation (const char *option, int argc, char *argv[], void *cookie) +{ + struct signinfo *si = cookie; + int v; + char *tail; + + if (argc < 2) + log_fatal ("Usage: %s REASON_CODE REASON_STRING\n", option); + + errno = 0; + v = strtol (argv[0], &tail, 16); + if (errno || (tail && *tail) || !(0 <= v && v <= 255)) + log_fatal ("%s: Invalid reason code (%s). Expected 0-255\n", + option, argv[0]); + + if (si->reason_for_revocation) + log_fatal ("%s given multiple times.\n", option); + + si->reason_for_revocation_code = v; + si->reason_for_revocation = xstrdup (argv[1]); + + return 2; +} + +static int +sig_corrupt (const char *option, int argc, char *argv[], void *cookie) +{ + struct signinfo *si = cookie; + + (void) option; + (void) argc; + (void) argv; + (void) cookie; + + si->corrupt = 1; + + return 0; +} + +static struct option sig_options[] = { + { "--issuer", sig_issuer, + "The key to use to generate the signature."}, + { "--issuer-keyid", sig_issuer_keyid, + "Set the issuer's key id. This is useful for creating a " + "self-signature. As a special case, the value \"self\" refers " + "to the primary key's key id. " + "(RFC 4880, Section 5.2.3.5)" }, + { "--pk", sig_pk, + "The primary keyas an index into the components (keys and uids) " + "created so far where the first component has the index 0." }, + { "--sk", sig_pk, + "The subkey as an index into the components (keys and uids) created " + "so far where the first component has the index 0. Only needed for " + "0x18, 0x19, and 0x28 signatures." }, + { "--user-id", sig_user_id, + "The user id as an index into the components (keys and uids) created " + "so far where the first component has the index 0. Only needed for " + "0x10-0x13 and 0x30 signatures." }, + { "--class", sig_class, + "The signature's class. Valid values are " + "0x10-0x13 (user id and primary-key certification), " + "0x18 (subkey binding), " + "0x19 (primary key binding), " + "0x1f (direct primary key signature), " + "0x20 (key revocation), " + "0x28 (subkey revocation), and " + "0x30 (certification revocation)." + }, + { "--digest", sig_digest, "The digest algorithm" }, + { "--timestamp", sig_timestamp, + "The signature's creation time. " TIMESTAMP_HELP " 0 means now. " + "(RFC 4880, Section 5.2.3.4)" }, + { "--key-expiration", sig_expiration, + "The number of days until the associated key expires. To specify " + "seconds, prefix the value with \"seconds=\". It is also possible " + "to use 'y', 'm' and 'w' as simple multipliers. For instance, 2y " + "means 2 years, etc. " + "(RFC 4880, Section 5.2.3.6)" }, + { "--cipher-algos", sig_int_list, + "A comma separated list of the preferred cipher algorithms (identified by " + "their number, see RFC 4880, Section 9). " + "(RFC 4880, Section 5.2.3.7)" }, + { "--digest-algos", sig_int_list, + "A comma separated list of the preferred algorithms (identified by " + "their number, see RFC 4880, Section 9). " + "(RFC 4880, Section 5.2.3.8)" }, + { "--compress-algos", sig_int_list, + "A comma separated list of the preferred algorithms (identified by " + "their number, see RFC 4880, Section 9)." + "(RFC 4880, Section 5.2.3.9)" }, + { "--expiration", sig_expiration, + "The number of days until the signature expires. To specify seconds, " + "prefix the value with \"seconds=\". It is also possible to use 'y', " + "'m' and 'w' as simple multipliers. For instance, 2y means 2 years, " + "etc. " + "(RFC 4880, Section 5.2.3.10)" }, + { "--exportable", sig_flag, + "Mark this signature as exportable (1) or local (0). " + "(RFC 4880, Section 5.2.3.11)" }, + { "--revocable", sig_flag, + "Mark this signature as revocable (1, revocations are ignored) " + "or non-revocable (0). " + "(RFC 4880, Section 5.2.3.12)" }, + { "--trust-level", sig_trust_level, + "Set the trust level. This takes two integer arguments (0-255): " + "the trusted-introducer level and the degree of trust. " + "(RFC 4880, Section 5.2.3.13.)" }, + { "--trust-scope", sig_string_arg, + "A regular expression that limits the scope of --trust-level. " + "(RFC 4880, Section 5.2.3.14.)" }, + { "--revocation-key", sig_revocation_key, + "Specify a designated revoker. Takes two arguments: the class " + "(normally 0x80 or 0xC0 (sensitive)) and the key id of the " + "designatured revoker. May be given multiple times. " + "(RFC 4880, Section 5.2.3.15)" }, + { "--notation", sig_notation, + "Add a human-readable notation of the form \"[!<]name=value\" where " + "\"!\" means that the critical flag should be set and \"<\" means " + "that VALUE is a file to read the data from. " + "(RFC 4880, Section 5.2.3.16)" }, + { "--notation-binary", sig_notation, + "Add a binary notation of the form \"[!<]name=value\" where " + "\"!\" means that the critical flag should be set and \"<\" means " + "that VALUE is a file to read the data from. " + "(RFC 4880, Section 5.2.3.16)" }, + { "--key-server-preferences", sig_big_endian_arg, + "Big-endian number encoding the keyserver preferences. " + "(RFC 4880, Section 5.2.3.17)" }, + { "--key-server", sig_string_arg, + "The preferred keyserver. (RFC 4880, Section 5.2.3.18)" }, + { "--primary-user-id", sig_flag, + "Sets the primary user id flag. (RFC 4880, Section 5.2.3.19)" }, + { "--policy-uri", sig_string_arg, + "URI of a document that describes the issuer's signing policy. " + "(RFC 4880, Section 5.2.3.20)" }, + { "--key-flags", sig_big_endian_arg, + "Big-endian number encoding the key flags. " + "(RFC 4880, Section 5.2.3.21)" }, + { "--signers-user-id", sig_string_arg, + "The user id (as a string) responsible for the signing. " + "(RFC 4880, Section 5.2.3.22)" }, + { "--reason-for-revocation", sig_reason_for_revocation, + "Takes two arguments: a reason for revocation code and a " + "user-provided string. " + "(RFC 4880, Section 5.2.3.23)" }, + { "--features", sig_big_endian_arg, + "Big-endian number encoding the feature flags. " + "(RFC 4880, Section 5.2.3.24)" }, + { "--signature-target", NULL, + "Takes three arguments: the target signature's public key algorithm " + " (as an integer), the hash algorithm (as an integer) and the hash " + " (as a hexadecimal string). " + "(RFC 4880, Section 5.2.3.25)" }, + { "--embedded-signature", NULL, + "An embedded signature. This must be immediately followed by a " + "signature packet (created using --signature ...) or a filename " + "containing the packet." + "(RFC 4880, Section 5.2.3.26)" }, + { "--hashed", NULL, + "The following attributes will be placed in the hashed area of " + "the signature. (This is the default and it reset at the end of" + "each signature.)" }, + { "--unhashed", NULL, + "The following attributes will be placed in the unhashed area of " + "the signature (and thus not integrity protected)." }, + { "--corrupt", sig_corrupt, + "Corrupt the signature." }, + { NULL, NULL, + "Example:\n\n" + " $ gpgcompose --public-key $KEYID --user-id USERID \\\n" + " --signature --class 0x10 --issuer $KEYID --issuer-keyid self \\\n" + " | " GPG_NAME " --list-packets"} +}; + +static int +mksubpkt_callback (PKT_signature *sig, void *cookie) +{ + struct signinfo *si = cookie; + int i; + + if (si->key_expiration) + { + char buf[4]; + buf[0] = (si->key_expiration >> 24) & 0xff; + buf[1] = (si->key_expiration >> 16) & 0xff; + buf[2] = (si->key_expiration >> 8) & 0xff; + buf[3] = si->key_expiration & 0xff; + build_sig_subpkt (sig, SIGSUBPKT_KEY_EXPIRE, buf, 4); + } + + if (si->cipher_algorithms) + build_sig_subpkt (sig, SIGSUBPKT_PREF_SYM, + si->cipher_algorithms, + si->cipher_algorithms_len); + + if (si->digest_algorithms) + build_sig_subpkt (sig, SIGSUBPKT_PREF_HASH, + si->digest_algorithms, + si->digest_algorithms_len); + + if (si->compress_algorithms) + build_sig_subpkt (sig, SIGSUBPKT_PREF_COMPR, + si->compress_algorithms, + si->compress_algorithms_len); + + if (si->exportable_set) + { + char buf = si->exportable; + build_sig_subpkt (sig, SIGSUBPKT_EXPORTABLE, &buf, 1); + } + + if (si->trust_level_set) + build_sig_subpkt (sig, SIGSUBPKT_TRUST, + si->trust_args, sizeof (si->trust_args)); + + if (si->trust_scope) + build_sig_subpkt (sig, SIGSUBPKT_REGEXP, + si->trust_scope, strlen (si->trust_scope)); + + for (i = 0; i < si->nrevocation_keys; i ++) + { + struct revocation_key *revkey = &si->revocation_key[i]; + gpg_error_t err = keygen_add_revkey (sig, revkey); + if (err) + { + u32 keyid[2]; + keyid_from_fingerprint (global_ctrl, revkey->fpr, 20, keyid); + log_fatal ("adding revocation key %s: %s\n", + keystr (keyid), gpg_strerror (err)); + } + } + + /* keygen_add_revkey sets revocable=0 so be sure to do this after + adding the rev keys. */ + if (si->revocable_set) + { + char buf = si->revocable; + build_sig_subpkt (sig, SIGSUBPKT_REVOCABLE, &buf, 1); + } + + keygen_add_notations (sig, si->notations); + + if (si->key_server_preferences) + build_sig_subpkt (sig, SIGSUBPKT_KS_FLAGS, + si->key_server_preferences, + si->key_server_preferences_len); + + if (si->key_server) + build_sig_subpkt (sig, SIGSUBPKT_PREF_KS, + si->key_server, strlen (si->key_server)); + + if (si->primary_user_id_set) + { + char buf = si->primary_user_id; + build_sig_subpkt (sig, SIGSUBPKT_PRIMARY_UID, &buf, 1); + } + + if (si->policy_uri) + build_sig_subpkt (sig, SIGSUBPKT_POLICY, + si->policy_uri, strlen (si->policy_uri)); + + if (si->key_flags) + build_sig_subpkt (sig, SIGSUBPKT_KEY_FLAGS, + si->key_flags, si->key_flags_len); + + if (si->signers_user_id) + build_sig_subpkt (sig, SIGSUBPKT_SIGNERS_UID, + si->signers_user_id, strlen (si->signers_user_id)); + + if (si->reason_for_revocation) + { + int len = 1 + strlen (si->reason_for_revocation); + char *buf; + + buf = xmalloc (len); + + buf[0] = si->reason_for_revocation_code; + memcpy (&buf[1], si->reason_for_revocation, len - 1); + + build_sig_subpkt (sig, SIGSUBPKT_REVOC_REASON, buf, len); + + xfree (buf); + } + + if (si->features) + build_sig_subpkt (sig, SIGSUBPKT_FEATURES, + si->features, si->features_len); + + return 0; +} + +static int +signature (const char *option, int argc, char *argv[], void *cookie) +{ + gpg_error_t err; + iobuf_t out = cookie; + struct signinfo si; + int processed; + PKT_public_key *pk; + PKT_signature *sig; + PACKET pkt; + u32 keyid_orig[2], keyid[2]; + + (void) option; + + memset (&si, 0, sizeof (si)); + memset (&pkt, 0, sizeof (pkt)); + + processed = process_options (option, + major_options, + sig_options, &si, + global_options, NULL, + argc, argv); + + if (ncomponents) + { + int pkttype = components[ncomponents - 1].pkttype; + + if (pkttype == PKT_PUBLIC_KEY) + { + if (! si.class) + /* Direct key sig. */ + si.class = 0x1F; + } + else if (pkttype == PKT_PUBLIC_SUBKEY) + { + if (! si.sk) + si.sk = components[ncomponents - 1].pkt.public_key; + if (! si.class) + /* Subkey binding sig. */ + si.class = 0x18; + } + else if (pkttype == PKT_USER_ID) + { + if (! si.uid) + si.uid = components[ncomponents - 1].pkt.user_id; + if (! si.class) + /* Certification of a user id and public key packet. */ + si.class = 0x10; + } + } + + pk = NULL; + if (! si.pk || ! si.issuer_pk) + /* No primary key specified. Default to the first one that we + find. */ + { + int i; + for (i = 0; i < ncomponents; i ++) + if (components[i].pkttype == PKT_PUBLIC_KEY) + { + pk = components[i].pkt.public_key; + break; + } + } + + if (! si.pk) + { + if (! pk) + log_fatal ("%s: no primary key given and no primary key available", + "--pk"); + si.pk = pk; + } + if (! si.issuer_pk) + { + if (! pk) + log_fatal ("%s: no issuer key given and no primary key available", + "--issuer"); + si.issuer_pk = pk; + } + + if (si.class == 0x18 || si.class == 0x19 || si.class == 0x28) + /* Requires the primary key and a subkey. */ + { + if (! si.sk) + log_fatal ("sig class 0x%x requires a subkey (--sk)\n", si.class); + } + else if (si.class == 0x10 + || si.class == 0x11 + || si.class == 0x12 + || si.class == 0x13 + || si.class == 0x30) + /* Requires the primary key and a user id. */ + { + if (! si.uid) + log_fatal ("sig class 0x%x requires a uid (--uid)\n", si.class); + } + else if (si.class == 0x1F || si.class == 0x20) + /* Just requires the primary key. */ + ; + else + log_fatal ("Unsupported signature class: 0x%x\n", si.class); + + sig = xmalloc_clear (sizeof (*sig)); + + /* Save SI.ISSUER_PK->KEYID. */ + keyid_copy (keyid_orig, pk_keyid (si.issuer_pk)); + if (si.issuer_keyid[0] || si.issuer_keyid[1]) + keyid_copy (si.issuer_pk->keyid, si.issuer_keyid); + else if (si.issuer_keyid_self) + { + PKT_public_key *pripk = primary_key(); + if (! pripk) + log_fatal ("--issuer-keyid self given, but no primary key available.\n"); + keyid_copy (si.issuer_pk->keyid, pk_keyid (pripk)); + } + + /* Changing the issuer's key id is fragile. Check to make sure + make_keysig_packet didn't recompute the keyid. */ + keyid_copy (keyid, si.issuer_pk->keyid); + err = make_keysig_packet (global_ctrl, + &sig, si.pk, si.uid, si.sk, si.issuer_pk, + si.class, si.digest_algo, + si.timestamp, si.expiration, + mksubpkt_callback, &si, NULL); + log_assert (keyid_cmp (keyid, si.issuer_pk->keyid) == 0); + if (err) + log_fatal ("Generating signature: %s\n", gpg_strerror (err)); + + /* Restore SI.PK->KEYID. */ + keyid_copy (si.issuer_pk->keyid, keyid_orig); + + if (si.corrupt) + { + /* Set the top 32-bits to 0xBAD0DEAD. */ + int bits = gcry_mpi_get_nbits (sig->data[0]); + gcry_mpi_t x = gcry_mpi_new (0); + gcry_mpi_add_ui (x, x, 0xBAD0DEAD); + gcry_mpi_lshift (x, x, bits > 32 ? bits - 32 : bits); + gcry_mpi_clear_highbit (sig->data[0], bits > 32 ? bits - 32 : 0); + gcry_mpi_add (sig->data[0], sig->data[0], x); + gcry_mpi_release (x); + } + + pkt.pkttype = PKT_SIGNATURE; + pkt.pkt.signature = sig; + + err = build_packet (out, &pkt); + if (err) + log_fatal ("serializing public key packet: %s\n", gpg_strerror (err)); + + debug ("Wrote signature packet:\n"); + dump_component (&pkt); + + free_seckey_enc (sig); + release_kbnode (si.issuer_kb); + xfree (si.revocation_key); + + return processed; +} + +struct sk_esk_info +{ + /* The cipher used for encrypting the session key (when a session + key is used). */ + int cipher; + /* The cipher used for encryping the SED packet. */ + int sed_cipher; + + /* S2K related data. */ + int hash; + int mode; + int mode_set; + byte salt[8]; + int salt_set; + int iterations; + + /* If applying the S2K function to the passphrase is the session key + or if it is the decryption key for the session key. */ + int s2k_is_session_key; + /* Generate a new, random session key. */ + int new_session_key; + + /* The unencrypted session key. */ + int session_key_len; + char *session_key; + + char *password; +}; + +static int +sk_esk_cipher (const char *option, int argc, char *argv[], void *cookie) +{ + struct sk_esk_info *si = cookie; + char *usage = "integer|IDEA|3DES|CAST5|BLOWFISH|AES|AES192|AES256|CAMELLIA128|CAMELLIA192|CAMELLIA256"; + int cipher; + + if (argc == 0) + log_fatal ("Usage: %s %s\n", option, usage); + + if (strcasecmp (argv[0], "IDEA") == 0) + cipher = CIPHER_ALGO_IDEA; + else if (strcasecmp (argv[0], "3DES") == 0) + cipher = CIPHER_ALGO_3DES; + else if (strcasecmp (argv[0], "CAST5") == 0) + cipher = CIPHER_ALGO_CAST5; + else if (strcasecmp (argv[0], "BLOWFISH") == 0) + cipher = CIPHER_ALGO_BLOWFISH; + else if (strcasecmp (argv[0], "AES") == 0) + cipher = CIPHER_ALGO_AES; + else if (strcasecmp (argv[0], "AES192") == 0) + cipher = CIPHER_ALGO_AES192; + else if (strcasecmp (argv[0], "TWOFISH") == 0) + cipher = CIPHER_ALGO_TWOFISH; + else if (strcasecmp (argv[0], "CAMELLIA128") == 0) + cipher = CIPHER_ALGO_CAMELLIA128; + else if (strcasecmp (argv[0], "CAMELLIA192") == 0) + cipher = CIPHER_ALGO_CAMELLIA192; + else if (strcasecmp (argv[0], "CAMELLIA256") == 0) + cipher = CIPHER_ALGO_CAMELLIA256; + else + { + char *tail; + int v; + + errno = 0; + v = strtol (argv[0], &tail, 0); + if (errno || (tail && *tail) || ! valid_cipher (v)) + log_fatal ("Invalid or unsupported value. Usage: %s %s\n", + option, usage); + + cipher = v; + } + + if (strcmp (option, "--cipher") == 0) + { + if (si->cipher) + log_fatal ("%s given multiple times.", option); + si->cipher = cipher; + } + else if (strcmp (option, "--sed-cipher") == 0) + { + if (si->sed_cipher) + log_fatal ("%s given multiple times.", option); + si->sed_cipher = cipher; + } + + return 1; +} + +static int +sk_esk_mode (const char *option, int argc, char *argv[], void *cookie) +{ + struct sk_esk_info *si = cookie; + char *usage = "integer|simple|salted|iterated"; + + if (argc == 0) + log_fatal ("Usage: %s %s\n", option, usage); + + if (si->mode) + log_fatal ("%s given multiple times.", option); + + if (strcasecmp (argv[0], "simple") == 0) + si->mode = 0; + else if (strcasecmp (argv[0], "salted") == 0) + si->mode = 1; + else if (strcasecmp (argv[0], "iterated") == 0) + si->mode = 3; + else + { + char *tail; + int v; + + errno = 0; + v = strtol (argv[0], &tail, 0); + if (errno || (tail && *tail) || ! (v == 0 || v == 1 || v == 3)) + log_fatal ("Invalid or unsupported value. Usage: %s %s\n", + option, usage); + + si->mode = v; + } + + si->mode_set = 1; + + return 1; +} + +static int +sk_esk_hash_algorithm (const char *option, int argc, char *argv[], void *cookie) +{ + struct sk_esk_info *si = cookie; + char *usage = "integer|MD5|SHA1|RMD160|SHA256|SHA384|SHA512|SHA224"; + + if (argc == 0) + log_fatal ("Usage: %s %s\n", option, usage); + + if (si->hash) + log_fatal ("%s given multiple times.", option); + + if (strcasecmp (argv[0], "MD5") == 0) + si->hash = DIGEST_ALGO_MD5; + else if (strcasecmp (argv[0], "SHA1") == 0) + si->hash = DIGEST_ALGO_SHA1; + else if (strcasecmp (argv[0], "RMD160") == 0) + si->hash = DIGEST_ALGO_RMD160; + else if (strcasecmp (argv[0], "SHA256") == 0) + si->hash = DIGEST_ALGO_SHA256; + else if (strcasecmp (argv[0], "SHA384") == 0) + si->hash = DIGEST_ALGO_SHA384; + else if (strcasecmp (argv[0], "SHA512") == 0) + si->hash = DIGEST_ALGO_SHA512; + else if (strcasecmp (argv[0], "SHA224") == 0) + si->hash = DIGEST_ALGO_SHA224; + else + { + char *tail; + int v; + + errno = 0; + v = strtol (argv[0], &tail, 0); + if (errno || (tail && *tail) + || ! (v == DIGEST_ALGO_MD5 + || v == DIGEST_ALGO_SHA1 + || v == DIGEST_ALGO_RMD160 + || v == DIGEST_ALGO_SHA256 + || v == DIGEST_ALGO_SHA384 + || v == DIGEST_ALGO_SHA512 + || v == DIGEST_ALGO_SHA224)) + log_fatal ("Invalid or unsupported value. Usage: %s %s\n", + option, usage); + + si->hash = v; + } + + return 1; +} + +static int +sk_esk_salt (const char *option, int argc, char *argv[], void *cookie) +{ + struct sk_esk_info *si = cookie; + char *usage = "16-HEX-CHARACTERS"; + char *p = argv[0]; + + if (argc == 0) + log_fatal ("Usage: %s %s\n", option, usage); + + if (si->salt_set) + log_fatal ("%s given multiple times.", option); + + if (p[0] == '0' && p[1] == 'x') + p += 2; + + if (strlen (p) != 16) + log_fatal ("%s: Salt must be exactly 16 hexadecimal characters (have: %zd)\n", + option, strlen (p)); + + if (hex2bin (p, si->salt, sizeof (si->salt)) == -1) + log_fatal ("%s: Salt must only contain hexadecimal characters\n", + option); + + si->salt_set = 1; + + return 1; +} + +static int +sk_esk_iterations (const char *option, int argc, char *argv[], void *cookie) +{ + struct sk_esk_info *si = cookie; + char *usage = "ITERATION-COUNT"; + char *tail; + int v; + + if (argc == 0) + log_fatal ("Usage: %s %s\n", option, usage); + + errno = 0; + v = strtol (argv[0], &tail, 0); + if (errno || (tail && *tail) || v < 0) + log_fatal ("%s: Non-negative integer expected.\n", option); + + si->iterations = v; + + return 1; +} + +static int +sk_esk_session_key (const char *option, int argc, char *argv[], void *cookie) +{ + struct sk_esk_info *si = cookie; + char *usage = "HEX-CHARACTERS|auto|none"; + char *p = argv[0]; + struct session_key sk; + + if (argc == 0) + log_fatal ("Usage: %s %s\n", option, usage); + + if (si->session_key || si->s2k_is_session_key + || si->new_session_key) + log_fatal ("%s given multiple times.", option); + + if (strcasecmp (p, "none") == 0) + { + si->s2k_is_session_key = 1; + return 1; + } + if (strcasecmp (p, "new") == 0) + { + si->new_session_key = 1; + return 1; + } + if (strcasecmp (p, "auto") == 0) + return 1; + + sk = parse_session_key (option, p, 0); + + if (si->session_key) + log_fatal ("%s given multiple times.", option); + + if (sk.algo) + si->sed_cipher = sk.algo; + + si->session_key_len = sk.keylen; + si->session_key = sk.key; + + return 1; +} + +static int +sk_esk_password (const char *option, int argc, char *argv[], void *cookie) +{ + struct sk_esk_info *si = cookie; + char *usage = "PASSWORD"; + + if (argc == 0) + log_fatal ("Usage: --sk-esk %s\n", usage); + + if (si->password) + log_fatal ("%s given multiple times.", option); + + si->password = xstrdup (argv[0]); + + return 1; +} + +static struct option sk_esk_options[] = { + { "--cipher", sk_esk_cipher, + "The encryption algorithm for encrypting the session key. " + "One of IDEA, 3DES, CAST5, BLOWFISH, AES (default), AES192, " + "AES256, TWOFISH, CAMELLIA128, CAMELLIA192, or CAMELLIA256." }, + { "--sed-cipher", sk_esk_cipher, + "The encryption algorithm for encrypting the SED packet. " + "One of IDEA, 3DES, CAST5, BLOWFISH, AES, AES192, " + "AES256 (default), TWOFISH, CAMELLIA128, CAMELLIA192, or CAMELLIA256." }, + { "--mode", sk_esk_mode, + "The S2K mode. Either one of the strings \"simple\", \"salted\" " + "or \"iterated\" or an integer." }, + { "--hash", sk_esk_hash_algorithm, + "The hash algorithm to used to derive the key. One of " + "MD5, SHA1 (default), RMD160, SHA256, SHA384, SHA512, or SHA224." }, + { "--salt", sk_esk_salt, + "The S2K salt encoded as 16 hexadecimal characters. One needed " + "if the S2K function is in salted or iterated mode." }, + { "--iterations", sk_esk_iterations, + "The iteration count. If not provided, a reasonable value is chosen. " + "Note: due to the encoding scheme, not every value is valid. For " + "convenience, the provided value will be rounded appropriately. " + "Only needed if the S2K function is in iterated mode." }, + { "--session-key", sk_esk_session_key, + "The session key to be encrypted by the S2K function as a hexadecimal " + "string. If this is \"new\", then a new session key is generated." + "If this is \"auto\", then either the last session key is " + "used, if the was none, one is generated. If this is \"none\", then " + "the session key is the result of applying the S2K algorithms to the " + "password. The session key may be prefaced with an integer and a colon " + "to indicate the cipher to use for the SED packet (making --sed-cipher " + "unnecessary and allowing the direct use of the result of " + "\"" GPG_NAME " --show-session-key\")." }, + { "", sk_esk_password, "The password." }, + { NULL, NULL, + "Example:\n\n" + " $ gpgcompose --sk-esk foobar --encrypted \\\n" + " --literal --value foo | " GPG_NAME " --list-packets" } +}; + +static int +sk_esk (const char *option, int argc, char *argv[], void *cookie) +{ + iobuf_t out = cookie; + gpg_error_t err; + int processed; + struct sk_esk_info si; + DEK sesdek; + DEK s2kdek; + PKT_symkey_enc *ske; + PACKET pkt; + + memset (&si, 0, sizeof (si)); + + processed = process_options (option, + major_options, + sk_esk_options, &si, + global_options, NULL, + argc, argv); + + if (! si.password) + log_fatal ("%s: missing password. Usage: %s PASSWORD", option, option); + + /* Fill in defaults, if appropriate. */ + if (! si.cipher) + si.cipher = CIPHER_ALGO_AES; + + if (! si.sed_cipher) + si.sed_cipher = CIPHER_ALGO_AES256; + + if (! si.hash) + si.hash = DIGEST_ALGO_SHA1; + + if (! si.mode_set) + /* Salted and iterated. */ + si.mode = 3; + + if (si.mode != 0 && ! si.salt_set) + /* Generate a salt. */ + gcry_randomize (si.salt, 8, GCRY_STRONG_RANDOM); + + if (si.mode == 0) + { + if (si.iterations) + log_info ("%s: --iterations provided, but not used for mode=0\n", + option); + si.iterations = 0; + } + else if (! si.iterations) + si.iterations = 10000; + + memset (&sesdek, 0, sizeof (sesdek)); + /* The session key is used to encrypt the SED packet. */ + sesdek.algo = si.sed_cipher; + if (si.session_key) + /* Copy the unencrypted session key into SESDEK. */ + { + sesdek.keylen = openpgp_cipher_get_algo_keylen (sesdek.algo); + if (sesdek.keylen != si.session_key_len) + log_fatal ("%s: Cipher algorithm requires a %d byte session key, but provided session key is %d bytes.", + option, sesdek.keylen, si.session_key_len); + + log_assert (sesdek.keylen <= sizeof (sesdek.key)); + memcpy (sesdek.key, si.session_key, sesdek.keylen); + } + else if (! si.s2k_is_session_key || si.new_session_key) + /* We need a session key, but one wasn't provided. Generate it. */ + make_session_key (&sesdek); + + /* The encrypted session key needs 1 + SESDEK.KEYLEN bytes of + space. */ + ske = xmalloc_clear (sizeof (*ske) + sesdek.keylen); + + ske->version = 4; + ske->cipher_algo = si.cipher; + + ske->s2k.mode = si.mode; + ske->s2k.hash_algo = si.hash; + log_assert (sizeof (si.salt) == sizeof (ske->s2k.salt)); + memcpy (ske->s2k.salt, si.salt, sizeof (ske->s2k.salt)); + if (! si.s2k_is_session_key) + /* 0 means get the default. */ + ske->s2k.count = encode_s2k_iterations (si.iterations); + + + /* Derive the symmetric key that is either the session key or the + key used to encrypt the session key. */ + memset (&s2kdek, 0, sizeof (s2kdek)); + + s2kdek.algo = si.cipher; + s2kdek.keylen = openpgp_cipher_get_algo_keylen (s2kdek.algo); + + err = gcry_kdf_derive (si.password, strlen (si.password), + ske->s2k.mode == 3 ? GCRY_KDF_ITERSALTED_S2K + : ske->s2k.mode == 1 ? GCRY_KDF_SALTED_S2K + : GCRY_KDF_SIMPLE_S2K, + ske->s2k.hash_algo, ske->s2k.salt, 8, + S2K_DECODE_COUNT (ske->s2k.count), + /* The size of the desired key and its + buffer. */ + s2kdek.keylen, s2kdek.key); + if (err) + log_fatal ("gcry_kdf_derive failed: %s", gpg_strerror (err)); + + + if (si.s2k_is_session_key) + { + ske->seskeylen = 0; + session_key = s2kdek; + } + else + /* Encrypt the session key using the s2k specifier. */ + { + DEK *sesdekp = &sesdek; + + /* Now encrypt the session key (or rather, the algorithm used to + encrypt the SED plus the session key) using ENCKEY. */ + ske->seskeylen = 1 + sesdek.keylen; + encrypt_seskey (&s2kdek, &sesdekp, ske->seskey); + + /* Save the session key for later. */ + session_key = sesdek; + } + + pkt.pkttype = PKT_SYMKEY_ENC; + pkt.pkt.symkey_enc = ske; + + err = build_packet (out, &pkt); + if (err) + log_fatal ("Serializing sym-key encrypted packet: %s\n", + gpg_strerror (err)); + + debug ("Wrote sym-key encrypted packet:\n"); + dump_component (&pkt); + + xfree (si.session_key); + xfree (si.password); + xfree (ske); + + return processed; +} + +struct pk_esk_info +{ + int session_key_set; + + int new_session_key; + + int sed_cipher; + int session_key_len; + char *session_key; + + int throw_keyid; + + char *keyid; +}; + +static int +pk_esk_session_key (const char *option, int argc, char *argv[], void *cookie) +{ + struct pk_esk_info *pi = cookie; + char *usage = "HEX-CHARACTERS|auto|none"; + char *p = argv[0]; + struct session_key sk; + + if (argc == 0) + log_fatal ("Usage: %s %s\n", option, usage); + + if (pi->session_key_set) + log_fatal ("%s given multiple times.", option); + pi->session_key_set = 1; + + if (strcasecmp (p, "new") == 0) + { + pi->new_session_key = 1; + return 1; + } + + if (strcasecmp (p, "auto") == 0) + return 1; + + sk = parse_session_key (option, p, 0); + + if (pi->session_key) + log_fatal ("%s given multiple times.", option); + + if (sk.algo) + pi->sed_cipher = sk.algo; + + pi->session_key_len = sk.keylen; + pi->session_key = sk.key; + + return 1; +} + +static int +pk_esk_throw_keyid (const char *option, int argc, char *argv[], void *cookie) +{ + struct pk_esk_info *pi = cookie; + + (void) option; + (void) argc; + (void) argv; + + pi->throw_keyid = 1; + + return 0; +} + +static int +pk_esk_keyid (const char *option, int argc, char *argv[], void *cookie) +{ + struct pk_esk_info *pi = cookie; + char *usage = "KEYID"; + + if (argc == 0) + log_fatal ("Usage: %s %s\n", option, usage); + + if (pi->keyid) + log_fatal ("Multiple key ids given, but only one is allowed."); + + pi->keyid = xstrdup (argv[0]); + + return 1; +} + +static struct option pk_esk_options[] = { + { "--session-key", pk_esk_session_key, + "The session key to be encrypted by the S2K function as a hexadecimal " + "string. If this is not given or is \"auto\", then the current " + "session key is used. If there is no session key or this is \"new\", " + "then a new session key is generated. The session key may be " + "prefaced with an integer and a colon to indicate the cipher to use " + "for the SED packet (making --sed-cipher unnecessary and allowing the " + "direct use of the result of \"" GPG_NAME " --show-session-key\")." }, + { "--throw-keyid", pk_esk_throw_keyid, + "Throw the keyid." }, + { "", pk_esk_keyid, "The key id." }, + { NULL, NULL, + "Example:\n\n" + " $ gpgcompose --pk-esk $KEYID --encrypted --literal --value foo \\\n" + " | " GPG_NAME " --list-packets"} +}; + +static int +pk_esk (const char *option, int argc, char *argv[], void *cookie) +{ + iobuf_t out = cookie; + gpg_error_t err; + int processed; + struct pk_esk_info pi; + PKT_public_key pk; + + memset (&pi, 0, sizeof (pi)); + + processed = process_options (option, + major_options, + pk_esk_options, &pi, + global_options, NULL, + argc, argv); + + if (! pi.keyid) + log_fatal ("%s: missing keyid. Usage: %s KEYID", option, option); + + memset (&pk, 0, sizeof (pk)); + pk.req_usage = PUBKEY_USAGE_ENC; + err = get_pubkey_byname (NULL, GET_PUBKEY_NO_AKL, + NULL, &pk, pi.keyid, NULL, NULL, 1); + if (err) + log_fatal ("%s: looking up key %s: %s\n", + option, pi.keyid, gpg_strerror (err)); + + if (pi.sed_cipher) + /* Have a session key. */ + { + session_key.algo = pi.sed_cipher; + session_key.keylen = pi.session_key_len; + log_assert (session_key.keylen <= sizeof (session_key.key)); + memcpy (session_key.key, pi.session_key, session_key.keylen); + } + + if (pi.new_session_key || ! session_key.algo) + { + if (! pi.new_session_key) + /* Default to AES256. */ + session_key.algo = CIPHER_ALGO_AES256; + make_session_key (&session_key); + } + + err = write_pubkey_enc (global_ctrl, &pk, pi.throw_keyid, &session_key, out); + if (err) + log_fatal ("%s: writing pk_esk packet for %s: %s\n", + option, pi.keyid, gpg_strerror (err)); + + debug ("Wrote pk_esk packet for %s\n", pi.keyid); + + xfree (pi.keyid); + xfree (pi.session_key); + + return processed; +} + +struct encinfo +{ + int saw_session_key; +}; + +static int +encrypted_session_key (const char *option, int argc, char *argv[], void *cookie) +{ + struct encinfo *ei = cookie; + char *usage = "HEX-CHARACTERS|auto"; + char *p = argv[0]; + struct session_key sk; + + if (argc == 0) + log_fatal ("Usage: %s %s\n", option, usage); + + if (ei->saw_session_key) + log_fatal ("%s given multiple times.", option); + ei->saw_session_key = 1; + + if (strcasecmp (p, "auto") == 0) + return 1; + + sk = parse_session_key (option, p, 1); + + session_key.algo = sk.algo; + log_assert (sk.keylen <= sizeof (session_key.key)); + memcpy (session_key.key, sk.key, sk.keylen); + xfree (sk.key); + + return 1; +} + +static struct option encrypted_options[] = { + { "--session-key", encrypted_session_key, + "The session key to be encrypted by the S2K function as a hexadecimal " + "string. If this is not given or is \"auto\", then the last session key " + "is used. If there was none, then an error is raised. The session key " + "must be prefaced with an integer and a colon to indicate the cipher " + "to use (this is format used by \"" GPG_NAME " --show-session-key\")." }, + { NULL, NULL, + "After creating the packet, this command clears the current " + "session key.\n\n" + "Example: nested encryption packets:\n\n" + " $ gpgcompose --sk-esk foo --encrypted-mdc \\\n" + " --sk-esk bar --encrypted-mdc \\\n" + " --literal --value 123 --encrypted-pop --encrypted-pop | " GPG_NAME" -d" } +}; + +static int +encrypted (const char *option, int argc, char *argv[], void *cookie) +{ + iobuf_t out = cookie; + int processed; + struct encinfo ei; + PKT_encrypted e; + cipher_filter_context_t *cfx; + + memset (&ei, 0, sizeof (ei)); + + processed = process_options (option, + major_options, + encrypted_options, &ei, + global_options, NULL, + argc, argv); + + if (! session_key.algo) + log_fatal ("%s: no session key configured\n" + " (use e.g. --sk-esk PASSWORD or --pk-esk KEYID).\n", + option); + + memset (&e, 0, sizeof (e)); + /* We only need to set E->LEN, E->EXTRALEN (if E->LEN is not + 0), and E->NEW_CTB. */ + e.len = 0; + e.new_ctb = 1; + + /* Register the cipher filter. */ + + cfx = xmalloc_clear (sizeof (*cfx)); + + /* Copy the session key. */ + cfx->dek = xmalloc (sizeof (*cfx->dek)); + *cfx->dek = session_key; + + if (do_debug) + { + char *buf; + + buf = xmalloc (2 * session_key.keylen + 1); + debug ("session key: algo: %d; keylen: %d; key: %s\n", + session_key.algo, session_key.keylen, + bin2hex (session_key.key, session_key.keylen, buf)); + xfree (buf); + } + + if (strcmp (option, "--encrypted-mdc") == 0) + cfx->dek->use_mdc = 1; + else if (strcmp (option, "--encrypted") == 0) + cfx->dek->use_mdc = 0; + else + log_fatal ("%s: option not handled by this function!\n", option); + + cfx->datalen = 0; + + filter_push (out, cipher_filter_cfb, cfx, PKT_ENCRYPTED, cfx->datalen == 0); + + debug ("Wrote encrypted packet:\n"); + + /* Clear the current session key. */ + memset (&session_key, 0, sizeof (session_key)); + + return processed; +} + +static struct option encrypted_pop_options[] = { + { NULL, NULL, + "Example:\n\n" + " $ gpgcompose --sk-esk PASSWORD \\\n" + " --encrypted-mdc \\\n" + " --literal --value foo \\\n" + " --encrypted-pop | " GPG_NAME " --list-packets" } +}; + +static int +encrypted_pop (const char *option, int argc, char *argv[], void *cookie) +{ + iobuf_t out = cookie; + int processed; + + processed = process_options (option, + major_options, + encrypted_pop_options, + NULL, + global_options, NULL, + argc, argv); + /* We only support a single option, --help, which causes the program + * to exit. */ + log_assert (processed == 0); + + filter_pop (out, PKT_ENCRYPTED); + + debug ("Popped encryption container.\n"); + + return processed; +} + +struct data +{ + int file; + union + { + char *data; + char *filename; + }; + struct data *next; +}; + +/* This must be the first member of the struct to be able to use + add_value! */ +struct datahead +{ + struct data *head; + struct data **last_next; +}; + +static int +add_value (const char *option, int argc, char *argv[], void *cookie) +{ + struct datahead *dh = cookie; + struct data *d = xmalloc_clear (sizeof (struct data)); + + d->file = strcmp ("--file", option) == 0; + if (! d->file) + log_assert (strcmp ("--value", option) == 0); + + if (argc == 0) + { + if (d->file) + log_fatal ("Usage: %s FILENAME\n", option); + else + log_fatal ("Usage: %s STRING\n", option); + } + + if (! dh->last_next) + /* First time through. Initialize DH->LAST_NEXT. */ + { + log_assert (! dh->head); + dh->last_next = &dh->head; + } + + if (d->file) + d->filename = argv[0]; + else + d->data = argv[0]; + + /* Append it. */ + *dh->last_next = d; + dh->last_next = &d->next; + + return 1; +} + +struct litinfo +{ + /* This must be the first element for add_value to work! */ + struct datahead data; + + int timestamp_set; + u32 timestamp; + char mode; + int partial_body_length_encoding; + char *name; +}; + +static int +literal_timestamp (const char *option, int argc, char *argv[], void *cookie) +{ + struct litinfo *li = cookie; + + char *tail = NULL; + + if (argc == 0) + log_fatal ("Usage: %s TIMESTAMP\n", option); + + errno = 0; + li->timestamp = parse_timestamp (argv[0], &tail); + if (errno || (tail && *tail)) + log_fatal ("Invalid value passed to %s (%s)\n", option, argv[0]); + li->timestamp_set = 1; + + return 1; +} + +static int +literal_mode (const char *option, int argc, char *argv[], void *cookie) +{ + struct litinfo *li = cookie; + + if (argc == 0 + || ! (strcmp (argv[0], "b") == 0 + || strcmp (argv[0], "t") == 0 + || strcmp (argv[0], "u") == 0)) + log_fatal ("Usage: %s [btu]\n", option); + + li->mode = argv[0][0]; + + return 1; +} + +static int +literal_partial_body_length (const char *option, int argc, char *argv[], + void *cookie) +{ + struct litinfo *li = cookie; + char *tail; + int v; + int range[2] = {0, 1}; + + if (argc <= 1) + log_fatal ("Usage: %s [0|1]\n", option); + + errno = 0; + v = strtol (argv[0], &tail, 0); + if (errno || (tail && *tail) || !(range[0] <= v && v <= range[1])) + log_fatal ("Invalid value passed to %s (%s). Expected %d-%d\n", + option, argv[0], range[0], range[1]); + + li->partial_body_length_encoding = v; + + return 1; +} + +static int +literal_name (const char *option, int argc, char *argv[], void *cookie) +{ + struct litinfo *li = cookie; + + if (argc <= 0) + log_fatal ("Usage: %s NAME\n", option); + + if (strlen (argv[0]) > 255) + log_fatal ("%s: name is too long (%zd > 255 characters).\n", + option, strlen (argv[0])); + + li->name = argv[0]; + + return 1; +} + +static struct option literal_options[] = { + { "--value", add_value, + "A string to store in the literal packet." }, + { "--file", add_value, + "A file to copy into the literal packet." }, + { "--timestamp", literal_timestamp, + "The literal packet's time stamp. This defaults to the current time." }, + { "--mode", literal_mode, + "The content's mode (normally 'b' (default), 't' or 'u')." }, + { "--partial-body-length", literal_partial_body_length, + "Force partial body length encoding." }, + { "--name", literal_name, + "The literal's name." }, + { NULL, NULL, + "Example:\n\n" + " $ gpgcompose --literal --value foobar | " GPG_NAME " -d"} +}; + +static int +literal (const char *option, int argc, char *argv[], void *cookie) +{ + iobuf_t out = cookie; + gpg_error_t err; + int processed; + struct litinfo li; + PKT_plaintext *pt; + PACKET pkt; + struct data *data; + + memset (&li, 0, sizeof (li)); + + processed = process_options (option, + major_options, + literal_options, &li, + global_options, NULL, + argc, argv); + + if (! li.data.head) + log_fatal ("%s: no data provided (use --value or --file)", option); + + pt = xmalloc_clear (sizeof (*pt) + (li.name ? strlen (li.name) : 0)); + pt->new_ctb = 1; + + if (li.timestamp_set) + pt->timestamp = li.timestamp; + else + /* Default to the current time. */ + pt->timestamp = make_timestamp (); + + pt->mode = li.mode; + if (! pt->mode) + /* Default to binary. */ + pt->mode = 'b'; + + if (li.name) + { + strcpy (pt->name, li.name); + pt->namelen = strlen (pt->name); + } + + pkt.pkttype = PKT_PLAINTEXT; + pkt.pkt.plaintext = pt; + + if (! li.partial_body_length_encoding) + /* Compute the amount of data. */ + { + pt->len = 0; + for (data = li.data.head; data; data = data->next) + { + if (data->file) + { + iobuf_t in; + int overflow; + off_t off; + + in = iobuf_open (data->filename); + if (! in) + /* An error opening the file. We do error handling + below so just break here. */ + { + pt->len = 0; + break; + } + + off = iobuf_get_filelength (in, &overflow); + iobuf_close (in); + + if (overflow || off == 0) + /* Length is unknown or there was an error + (unfortunately, iobuf_get_filelength doesn't + distinguish between 0 length files and an error!). + Fall back to partial body mode. */ + { + pt->len = 0; + break; + } + + pt->len += off; + } + else + pt->len += strlen (data->data); + } + } + + err = build_packet (out, &pkt); + if (err) + log_fatal ("Serializing literal packet: %s\n", gpg_strerror (err)); + + /* Write out the data. */ + for (data = li.data.head; data; data = data->next) + { + if (data->file) + { + iobuf_t in; + errno = 0; + in = iobuf_open (data->filename); + if (! in) + log_fatal ("Opening '%s': %s\n", + data->filename, + errno ? strerror (errno): "unknown error"); + + iobuf_copy (out, in); + if (iobuf_error (in)) + log_fatal ("Reading from %s: %s\n", + data->filename, + gpg_strerror (iobuf_error (in))); + if (iobuf_error (out)) + log_fatal ("Writing literal data from %s: %s\n", + data->filename, + gpg_strerror (iobuf_error (out))); + + iobuf_close (in); + } + else + { + err = iobuf_write (out, data->data, strlen (data->data)); + if (err) + log_fatal ("Writing literal data: %s\n", gpg_strerror (err)); + } + } + + if (! pt->len) + { + /* Disable partial body length mode. */ + log_assert (pt->new_ctb == 1); + iobuf_set_partial_body_length_mode (out, 0); + } + + debug ("Wrote literal packet:\n"); + dump_component (&pkt); + + while (li.data.head) + { + data = li.data.head->next; + xfree (li.data.head); + li.data.head = data; + } + xfree (pt); + + return processed; +} + +static int +copy_file (const char *option, int argc, char *argv[], void *cookie) +{ + char **filep = cookie; + + if (argc == 0) + log_fatal ("Usage: %s FILENAME\n", option); + + *filep = argv[0]; + + return 1; +} + +static struct option copy_options[] = { + { "", copy_file, "Copy the specified file to stdout." }, + { NULL, NULL, + "Example:\n\n" + " $ gpgcompose --copy /etc/hostname\n\n" + "This is particularly useful when combined with gpgsplit." } +}; + +static int +copy (const char *option, int argc, char *argv[], void *cookie) +{ + iobuf_t out = cookie; + char *file = NULL; + iobuf_t in; + + int processed; + + processed = process_options (option, + major_options, + copy_options, &file, + global_options, NULL, + argc, argv); + if (! file) + log_fatal ("Usage: %s FILE\n", option); + + errno = 0; + in = iobuf_open (file); + if (! in) + log_fatal ("Error opening %s: %s.\n", + file, errno ? strerror (errno): "unknown error"); + + iobuf_copy (out, in); + if (iobuf_error (out)) + log_fatal ("Copying data to destination: %s\n", + gpg_strerror (iobuf_error (out))); + if (iobuf_error (in)) + log_fatal ("Reading data from %s: %s\n", + argv[0], gpg_strerror (iobuf_error (in))); + + iobuf_close (in); + + return processed; +} + +int +main (int argc, char *argv[]) +{ + const char *filename = "-"; + iobuf_t out; + int preprocessed = 1; + int processed; + ctrl_t ctrl; + + opt.ignore_time_conflict = 1; + /* Allow notations in the IETF space, for instance. */ + opt.expert = 1; + + global_ctrl = ctrl = xcalloc (1, sizeof *ctrl); + + keydb_add_resource ("pubring" EXTSEP_S GPGEXT_GPG, + KEYDB_RESOURCE_FLAG_DEFAULT); + + if (argc == 1) + /* Nothing to do. */ + return 0; + + if (strcmp (argv[1], "--output") == 0 + || strcmp (argv[1], "-o") == 0) + { + filename = argv[2]; + log_info ("Writing to %s\n", filename); + preprocessed += 2; + } + + out = iobuf_create (filename, 0); + if (! out) + log_fatal ("Failed to open stdout for writing\n"); + + processed = process_options (NULL, NULL, + major_options, out, + global_options, NULL, + argc - preprocessed, &argv[preprocessed]); + if (processed != argc - preprocessed) + log_fatal ("Didn't process %d options.\n", argc - preprocessed - processed); + + iobuf_close (out); + + return 0; +} + +/* Stubs duplicated from gpg.c. */ + +int g10_errors_seen = 0; + +/* Note: This function is used by signal handlers!. */ +static void +emergency_cleanup (void) +{ + gcry_control (GCRYCTL_TERM_SECMEM ); +} + +void +g10_exit( int rc ) +{ + gcry_control (GCRYCTL_UPDATE_RANDOM_SEED_FILE); + + emergency_cleanup (); + + rc = rc? rc : log_get_errorcount(0)? 2 : g10_errors_seen? 1 : 0; + exit (rc); +} + +void +keyedit_menu (ctrl_t ctrl, const char *username, strlist_t locusr, + strlist_t commands, int quiet, int seckey_check) +{ + (void) ctrl; + (void) username; + (void) locusr; + (void) commands; + (void) quiet; + (void) seckey_check; +} + +void +show_basic_key_info (ctrl_t ctrl, KBNODE keyblock, int made_from_sec) +{ + (void)ctrl; + (void)keyblock; + (void)made_from_sec; +} + +int +keyedit_print_one_sig (ctrl_t ctrl, estream_t fp, + int rc, kbnode_t keyblock, kbnode_t node, + int *inv_sigs, int *no_key, int *oth_err, + int is_selfsig, int print_without_key, int extended) +{ + (void) ctrl; + (void) fp; + (void) rc; + (void) keyblock; + (void) node; + (void) inv_sigs; + (void) no_key; + (void) oth_err; + (void) is_selfsig; + (void) print_without_key; + (void) extended; + return 0; +} diff --git a/g10/gpgsql.c b/g10/gpgsql.c new file mode 100644 index 0000000..5b75569 --- /dev/null +++ b/g10/gpgsql.c @@ -0,0 +1,251 @@ +/* gpgsql.c - SQLite helper functions. + * Copyright (C) 2015 g10 Code GmbH + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include +#include +#include +#include + +#include "gpg.h" +#include "../common/util.h" +#include "../common/logging.h" + +#include "gpgsql.h" + +/* This is a convenience function that combines sqlite3_mprintf and + sqlite3_exec. */ +int +gpgsql_exec_printf (sqlite3 *db, + int (*callback)(void*,int,char**,char**), void *cookie, + char **errmsg, + const char *sql, ...) +{ + va_list ap; + int rc; + char *sql2; + + va_start (ap, sql); + sql2 = sqlite3_vmprintf (sql, ap); + va_end (ap); + +#if 0 + log_debug ("tofo db: executing: '%s'\n", sql2); +#endif + + rc = sqlite3_exec (db, sql2, callback, cookie, errmsg); + + sqlite3_free (sql2); + + return rc; +} + +int +gpgsql_stepx (sqlite3 *db, + sqlite3_stmt **stmtp, + gpgsql_stepx_callback callback, + void *cookie, + char **errmsg, + const char *sql, ...) +{ + int rc; + int err = 0; + sqlite3_stmt *stmt = NULL; + + va_list va; + int args; + enum gpgsql_arg_type t; + int i; + + int cols; + /* Names of the columns. We initialize this lazily to avoid the + overhead in case the query doesn't return any results. */ + const char **azColName = 0; + int callback_initialized = 0; + + const char **azVals = 0; + + callback_initialized = 0; + + if (stmtp && *stmtp) + { + stmt = *stmtp; + + /* Make sure this statement is associated with the supplied db. */ + log_assert (db == sqlite3_db_handle (stmt)); + +#if DEBUG_TOFU_CACHE + prepares_saved ++; +#endif + } + else + { + const char *tail = NULL; + + rc = sqlite3_prepare_v2 (db, sql, -1, &stmt, &tail); + if (rc) + log_fatal ("failed to prepare SQL: %s", sql); + + /* We can only process a single statement. */ + if (tail) + { + while (*tail == ' ' || *tail == ';' || *tail == '\n') + tail ++; + + if (*tail) + log_fatal + ("sqlite3_stepx can only process a single SQL statement." + " Second statement starts with: '%s'\n", + tail); + } + + if (stmtp) + *stmtp = stmt; + } + +#if DEBUG_TOFU_CACHE + queries ++; +#endif + + args = sqlite3_bind_parameter_count (stmt); + va_start (va, sql); + if (args) + { + for (i = 1; i <= args; i ++) + { + t = va_arg (va, enum gpgsql_arg_type); + switch (t) + { + case GPGSQL_ARG_INT: + { + int value = va_arg (va, int); + err = sqlite3_bind_int (stmt, i, value); + break; + } + case GPGSQL_ARG_LONG_LONG: + { + long long value = va_arg (va, long long); + err = sqlite3_bind_int64 (stmt, i, value); + break; + } + case GPGSQL_ARG_STRING: + { + char *text = va_arg (va, char *); + err = sqlite3_bind_text (stmt, i, text, -1, SQLITE_STATIC); + break; + } + case GPGSQL_ARG_BLOB: + { + char *blob = va_arg (va, void *); + long long length = va_arg (va, long long); + err = sqlite3_bind_blob (stmt, i, blob, length, SQLITE_STATIC); + break; + } + default: + /* Internal error. Likely corruption. */ + log_fatal ("Bad value for parameter type %d.\n", t); + } + + if (err) + { + log_fatal ("Error binding parameter %d\n", i); + goto out; + } + } + + } + t = va_arg (va, enum gpgsql_arg_type); + log_assert (t == GPGSQL_ARG_END); + va_end (va); + + for (;;) + { + rc = sqlite3_step (stmt); + + if (rc != SQLITE_ROW) + /* No more data (SQLITE_DONE) or an error occurred. */ + break; + + if (! callback) + continue; + + if (! callback_initialized) + { + cols = sqlite3_column_count (stmt); + azColName = xmalloc (2 * cols * sizeof (const char *) + 1); + + for (i = 0; i < cols; i ++) + azColName[i] = sqlite3_column_name (stmt, i); + + callback_initialized = 1; + } + + azVals = &azColName[cols]; + for (i = 0; i < cols; i ++) + { + azVals[i] = sqlite3_column_text (stmt, i); + if (! azVals[i] && sqlite3_column_type (stmt, i) != SQLITE_NULL) + /* Out of memory. */ + { + err = SQLITE_NOMEM; + break; + } + } + + if (callback (cookie, cols, (char **) azVals, (char **) azColName, stmt)) + /* A non-zero result means to abort. */ + { + err = SQLITE_ABORT; + break; + } + } + + out: + xfree (azColName); + + if (stmtp) + rc = sqlite3_reset (stmt); + else + rc = sqlite3_finalize (stmt); + if (rc == SQLITE_OK && err) + /* Local error. */ + { + rc = err; + if (errmsg) + { + const char *e = sqlite3_errstr (err); + size_t l = strlen (e) + 1; + *errmsg = sqlite3_malloc (l); + if (! *errmsg) + log_fatal ("Out of memory.\n"); + memcpy (*errmsg, e, l); + } + } + else if (rc != SQLITE_OK && errmsg) + /* Error reported by sqlite. */ + { + const char * e = sqlite3_errmsg (db); + size_t l = strlen (e) + 1; + *errmsg = sqlite3_malloc (l); + if (! *errmsg) + log_fatal ("Out of memory.\n"); + memcpy (*errmsg, e, l); + } + + return rc; +} diff --git a/g10/gpgsql.h b/g10/gpgsql.h new file mode 100644 index 0000000..0d69039 --- /dev/null +++ b/g10/gpgsql.h @@ -0,0 +1,61 @@ +/* gpgsql.h - SQLite helper functions. + * Copyright (C) 2015 g10 Code GmbH + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#ifndef GNUPG_GPGSQL_H +#define GNUPG_GPGSQL_H + +#include + +enum gpgsql_arg_type + { + GPGSQL_ARG_END = 0xdead001, + GPGSQL_ARG_INT, + GPGSQL_ARG_LONG_LONG, + GPGSQL_ARG_STRING, + /* This takes two arguments: the blob as a void * and the length + of the blob as a long long. */ + GPGSQL_ARG_BLOB + }; + +int gpgsql_exec_printf (sqlite3 *db, + int (*callback)(void*,int,char**,char**), void *cookie, + char **errmsg, + const char *sql, ...); + +typedef int (*gpgsql_stepx_callback) (void *cookie, + /* number of columns. */ + int cols, + /* columns as text. */ + char **values, + /* column names. */ + char **names, + /* The prepared statement so + * that it is possible to use + * something like + * sqlite3_column_blob(). */ + sqlite3_stmt *statement); + +int gpgsql_stepx (sqlite3 *db, + sqlite3_stmt **stmtp, + gpgsql_stepx_callback callback, + void *cookie, + char **errmsg, + const char *sql, ...); + +#endif /*GNUPG_GPGSQL_H*/ diff --git a/g10/gpgv-w32info.rc b/g10/gpgv-w32info.rc new file mode 100644 index 0000000..9182fa4 --- /dev/null +++ b/g10/gpgv-w32info.rc @@ -0,0 +1,52 @@ +/* gpgv-w32info.rc -*- c -*- + * Copyright (C) 2020 g10 Code GmbH + * + * This file is free software; as a special exception the author gives + * unlimited permission to copy and/or distribute it, with or without + * modifications, as long as this notice is preserved. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY, to the extent permitted by law; without even the + * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + */ + +#include "afxres.h" +#include "../common/w32info-rc.h" + +1 ICON "../common/gnupg.ico" + +1 VERSIONINFO + FILEVERSION W32INFO_VI_FILEVERSION + PRODUCTVERSION W32INFO_VI_PRODUCTVERSION + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x01L /* VS_FF_DEBUG (0x1)*/ +#else + FILEFLAGS 0x00L +#endif + FILEOS 0x40004L /* VOS_NT (0x40000) | VOS__WINDOWS32 (0x4) */ + FILETYPE 0x1L /* VFT_APP (0x1) */ + FILESUBTYPE 0x0L /* VFT2_UNKNOWN */ + BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" /* US English (0409), Unicode (04b0) */ + BEGIN + VALUE "FileDescription", L"GnuPG\x2019s OpenPGP verify tool\0" + VALUE "InternalName", "gpgv\0" + VALUE "OriginalFilename", "gpgv.exe\0" + VALUE "ProductName", W32INFO_PRODUCTNAME + VALUE "ProductVersion", W32INFO_PRODUCTVERSION + VALUE "CompanyName", W32INFO_COMPANYNAME + VALUE "FileVersion", W32INFO_FILEVERSION + VALUE "LegalCopyright", W32INFO_LEGALCOPYRIGHT + VALUE "Comments", W32INFO_COMMENTS + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 0x4b0 + END + END + +1 RT_MANIFEST "gpgv.w32-manifest" diff --git a/g10/gpgv.c b/g10/gpgv.c new file mode 100644 index 0000000..4e9c35d --- /dev/null +++ b/g10/gpgv.c @@ -0,0 +1,835 @@ +/* gpgv.c - The GnuPG signature verify utility + * Copyright (C) 1998-2020 Free Software Foundation, Inc. + * Copyright (C) 1998-2019 Werner Koch + * Copyright (C) 2015-2020 g10 Code GmbH + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include +#include +#include +#include +#include +#include +#include +#ifdef HAVE_DOSISH_SYSTEM +#include /* for setmode() */ +#endif +#ifdef HAVE_LIBREADLINE +#define GNUPG_LIBREADLINE_H_INCLUDED +#include +#endif + +#define INCLUDED_BY_MAIN_MODULE 1 +#include "gpg.h" +#include "../common/util.h" +#include "packet.h" +#include "../common/iobuf.h" +#include "main.h" +#include "options.h" +#include "keydb.h" +#include "trustdb.h" +#include "filter.h" +#include "../common/ttyio.h" +#include "../common/i18n.h" +#include "../common/sysutils.h" +#include "../common/status.h" +#include "call-agent.h" +#include "../common/init.h" + + +enum cmd_and_opt_values { + aNull = 0, + oQuiet = 'q', + oVerbose = 'v', + oOutput = 'o', + oBatch = 500, + oKeyring, + oIgnoreTimeConflict, + oStatusFD, + oLoggerFD, + oLoggerFile, + oHomedir, + oWeakDigest, + oEnableSpecialFilenames, + oDebug, + aTest +}; + + +static ARGPARSE_OPTS opts[] = { + ARGPARSE_group (300, N_("@\nOptions:\n ")), + + ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")), + ARGPARSE_s_n (oQuiet, "quiet", N_("be somewhat more quiet")), + ARGPARSE_s_s (oKeyring, "keyring", + N_("|FILE|take the keys from the keyring FILE")), + ARGPARSE_s_s (oOutput, "output", N_("|FILE|write output to FILE")), + ARGPARSE_s_n (oIgnoreTimeConflict, "ignore-time-conflict", + N_("make timestamp conflicts only a warning")), + ARGPARSE_s_i (oStatusFD, "status-fd", + N_("|FD|write status info to this FD")), + ARGPARSE_s_i (oLoggerFD, "logger-fd", "@"), + ARGPARSE_s_s (oLoggerFile, "log-file", "@"), + ARGPARSE_s_s (oHomedir, "homedir", "@"), + ARGPARSE_s_s (oWeakDigest, "weak-digest", + N_("|ALGO|reject signatures made with ALGO")), + ARGPARSE_s_n (oEnableSpecialFilenames, "enable-special-filenames", "@"), + ARGPARSE_s_s (oDebug, "debug", "@"), + + ARGPARSE_end () +}; + + +/* The list of supported debug flags. */ +static struct debug_flags_s debug_flags [] = + { + { DBG_PACKET_VALUE , "packet" }, + { DBG_MPI_VALUE , "mpi" }, + { DBG_CRYPTO_VALUE , "crypto" }, + { DBG_FILTER_VALUE , "filter" }, + { DBG_IOBUF_VALUE , "iobuf" }, + { DBG_MEMORY_VALUE , "memory" }, + { DBG_CACHE_VALUE , "cache" }, + { DBG_MEMSTAT_VALUE, "memstat" }, + { DBG_TRUST_VALUE , "trust" }, + { DBG_HASHING_VALUE, "hashing" }, + { DBG_IPC_VALUE , "ipc" }, + { DBG_CLOCK_VALUE , "clock" }, + { DBG_LOOKUP_VALUE , "lookup" }, + { DBG_EXTPROG_VALUE, "extprog" }, + { 0, NULL } + }; + + +int g10_errors_seen = 0; + + +static char * +make_libversion (const char *libname, const char *(*getfnc)(const char*)) +{ + const char *s; + char *result; + + s = getfnc (NULL); + result = xmalloc (strlen (libname) + 1 + strlen (s) + 1); + strcpy (stpcpy (stpcpy (result, libname), " "), s); + return result; +} + +static const char * +my_strusage( int level ) +{ + static char *ver_gcry; + const char *p; + + switch (level) + { + case 9: p = "GPL-3.0-or-later"; break; + case 11: p = "@GPG@v (GnuPG)"; + break; + case 13: p = VERSION; break; + case 14: p = GNUPG_DEF_COPYRIGHT_LINE; break; + case 17: p = PRINTABLE_OS_NAME; break; + case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break; + + case 1: + case 40: p = _("Usage: gpgv [options] [files] (-h for help)"); + break; + case 41: p = _("Syntax: gpgv [options] [files]\n" + "Check signatures against known trusted keys\n"); + break; + + case 20: + if (!ver_gcry) + ver_gcry = make_libversion ("libgcrypt", gcry_check_version); + p = ver_gcry; + break; + + + default: p = NULL; + } + return p; +} + + + +int +main( int argc, char **argv ) +{ + ARGPARSE_ARGS pargs; + int rc=0; + strlist_t sl; + strlist_t nrings = NULL; + ctrl_t ctrl; + + early_system_init (); + set_strusage (my_strusage); + log_set_prefix ("gpgv", GPGRT_LOG_WITH_PREFIX); + + /* Make sure that our subsystems are ready. */ + i18n_init(); + init_common_subsystems (&argc, &argv); + + gcry_control (GCRYCTL_DISABLE_SECMEM, 0); + + gnupg_init_signals (0, NULL); + + opt.command_fd = -1; /* no command fd */ + opt.keyserver_options.options |= KEYSERVER_AUTO_KEY_RETRIEVE; + opt.trust_model = TM_ALWAYS; + opt.no_sig_cache = 1; + opt.flags.require_cross_cert = 1; + opt.batch = 1; + opt.answer_yes = 1; + + opt.weak_digests = NULL; + + tty_no_terminal(1); + tty_batchmode(1); + dotlock_disable (); + gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0); + additional_weak_digest("MD5"); + gnupg_initialize_compliance (GNUPG_MODULE_NAME_GPG); + + pargs.argc = &argc; + pargs.argv = &argv; + pargs.flags= ARGPARSE_FLAG_KEEP; + while (gnupg_argparser (&pargs, opts, NULL)) + { + switch (pargs.r_opt) + { + case ARGPARSE_CONFFILE: break; + + case oQuiet: opt.quiet = 1; break; + case oVerbose: + opt.verbose++; + opt.list_sigs=1; + gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose); + break; + case oDebug: + if (parse_debug_flag (pargs.r.ret_str, &opt.debug, debug_flags)) + { + pargs.r_opt = ARGPARSE_INVALID_ARG; + pargs.err = ARGPARSE_PRINT_ERROR; + } + break; + case oKeyring: append_to_strlist( &nrings, pargs.r.ret_str); break; + case oOutput: opt.outfile = pargs.r.ret_str; break; + case oStatusFD: + set_status_fd (translate_sys2libc_fd_int (pargs.r.ret_int, 1)); + break; + case oLoggerFD: + log_set_fd (translate_sys2libc_fd_int (pargs.r.ret_int, 1)); + break; + case oLoggerFile: + log_set_file (pargs.r.ret_str); + log_set_prefix (NULL, (GPGRT_LOG_WITH_PREFIX + | GPGRT_LOG_WITH_TIME + | GPGRT_LOG_WITH_PID) ); + break; + case oHomedir: gnupg_set_homedir (pargs.r.ret_str); break; + case oWeakDigest: + additional_weak_digest(pargs.r.ret_str); + break; + case oIgnoreTimeConflict: opt.ignore_time_conflict = 1; break; + case oEnableSpecialFilenames: + enable_special_filenames (); + break; + default : pargs.err = ARGPARSE_PRINT_ERROR; break; + } + } + + gnupg_argparse (NULL, &pargs, NULL); /* Release internal state. */ + + if (log_get_errorcount (0)) + g10_exit(2); + + if (opt.verbose > 1) + set_packet_list_mode(1); + + /* Note: We open all keyrings in read-only mode. */ + if (!nrings) /* No keyring given: use default one. */ + keydb_add_resource ("trustedkeys" EXTSEP_S "kbx", + (KEYDB_RESOURCE_FLAG_READONLY + |KEYDB_RESOURCE_FLAG_GPGVDEF)); + for (sl = nrings; sl; sl = sl->next) + keydb_add_resource (sl->d, KEYDB_RESOURCE_FLAG_READONLY); + + FREE_STRLIST (nrings); + + ctrl = xcalloc (1, sizeof *ctrl); + + if ((rc = verify_signatures (ctrl, argc, argv))) + log_error("verify signatures failed: %s\n", gpg_strerror (rc) ); + + keydb_release (ctrl->cached_getkey_kdb); + xfree (ctrl); + + /* cleanup */ + g10_exit (0); + return 8; /*NOTREACHED*/ +} + + +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 because + * this utility assumes that all keys in the keyring are trustworthy + */ +int +check_signatures_trust (ctrl_t ctrl, PKT_signature *sig) +{ + (void)ctrl; + (void)sig; + return 0; +} + +void +read_trust_options (ctrl_t ctrl, + byte *trust_model, ulong *created, ulong *nextcheck, + byte *marginals, byte *completes, byte *cert_depth, + byte *min_cert_level) +{ + (void)ctrl; + (void)trust_model; + (void)created; + (void)nextcheck; + (void)marginals; + (void)completes; + (void)cert_depth; + (void)min_cert_level; +} + +/* Stub: + * We don't have the trustdb , so we have to provide some stub functions + * instead + */ + +int +cache_disabled_value (ctrl_t ctrl, PKT_public_key *pk) +{ + (void)ctrl; + (void)pk; + return 0; +} + +void +check_trustdb_stale (ctrl_t ctrl) +{ + (void)ctrl; +} + +int +get_validity_info (ctrl_t ctrl, kbnode_t kb, PKT_public_key *pk, + PKT_user_id *uid) +{ + (void)ctrl; + (void)kb; + (void)pk; + (void)uid; + return '?'; +} + +unsigned int +get_validity (ctrl_t ctrl, kbnode_t kb, PKT_public_key *pk, PKT_user_id *uid, + PKT_signature *sig, int may_ask) +{ + (void)ctrl; + (void)kb; + (void)pk; + (void)uid; + (void)sig; + (void)may_ask; + return 0; +} + +const char * +trust_value_to_string (unsigned int value) +{ + (void)value; + return "err"; +} + +const char * +uid_trust_string_fixed (ctrl_t ctrl, PKT_public_key *key, PKT_user_id *uid) +{ + (void)ctrl; + (void)key; + (void)uid; + return "err"; +} + +int +get_ownertrust_info (ctrl_t ctrl, PKT_public_key *pk, int no_create) +{ + (void)ctrl; + (void)pk; + (void)no_create; + return '?'; +} + +unsigned int +get_ownertrust (ctrl_t ctrl, PKT_public_key *pk) +{ + (void)ctrl; + (void)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) +{ + (void)spec; + return NULL; +} + +int +keyserver_any_configured (ctrl_t ctrl) +{ + (void)ctrl; + return 0; +} + +int +keyserver_import_keyid (u32 *keyid, void *dummy, unsigned int flags) +{ + (void)keyid; + (void)dummy; + (void)flags; + return -1; +} + +int +keyserver_import_fprint (ctrl_t ctrl, const byte *fprint,size_t fprint_len, + struct keyserver_spec *keyserver, unsigned int flags) +{ + (void)ctrl; + (void)fprint; + (void)fprint_len; + (void)keyserver; + (void)flags; + return -1; +} + +int +keyserver_import_fprint_ntds (ctrl_t ctrl, + const byte *fprint, size_t fprint_len) +{ + (void)ctrl; + (void)fprint; + (void)fprint_len; + return -1; +} + +int +keyserver_import_cert (const char *name) +{ + (void)name; + return -1; +} + +int +keyserver_import_pka (const char *name,unsigned char *fpr) +{ + (void)name; + (void)fpr; + return -1; +} + +gpg_error_t +keyserver_import_wkd (ctrl_t ctrl, const char *name, unsigned int flags, + unsigned char **fpr, size_t *fpr_len) +{ + (void)ctrl; + (void)name; + (void)flags; + (void)fpr; + (void)fpr_len; + return GPG_ERR_BUG; +} + +int +keyserver_import_mbox (const char *name,struct keyserver_spec *spec) +{ + (void)name; + (void)spec; + return -1; +} + +int +keyserver_import_ntds (ctrl_t ctrl, const char *mbox, + unsigned char **fpr, size_t *fprlen) +{ + (void)ctrl; + (void)mbox; + (void)fpr; + (void)fprlen; + return -1; +} + +int +keyserver_import_ldap (const char *name) +{ + (void)name; + return -1; +} + + +gpg_error_t +read_key_from_file_or_buffer (ctrl_t ctrl, const char *fname, + const void *buffer, size_t buflen, + kbnode_t *r_keyblock) +{ + (void)ctrl; + (void)fname; + (void)buffer; + (void)buflen; + (void)r_keyblock; + return -1; +} + +gpg_error_t +import_included_key_block (ctrl_t ctrl, kbnode_t keyblock) +{ + (void)ctrl; + (void)keyblock; + return -1; +} + + +/* Stub: + * No encryption here but mainproc links to these functions. + */ +gpg_error_t +get_session_key (ctrl_t ctrl, PKT_pubkey_enc *k, DEK *dek) +{ + (void)ctrl; + (void)k; + (void)dek; + return GPG_ERR_GENERAL; +} + +/* Stub: */ +gpg_error_t +get_override_session_key (DEK *dek, const char *string) +{ + (void)dek; + (void)string; + return GPG_ERR_GENERAL; +} + +/* Stub: */ +int +decrypt_data (ctrl_t ctrl, void *procctx, PKT_encrypted *ed, DEK *dek, + int *compliance_error) +{ + (void)ctrl; + (void)procctx; + (void)ed; + (void)dek; + (void)compliance_error; + return GPG_ERR_GENERAL; +} + + +/* Stub: + * No interactive commands, so we don't need the helptexts + */ +void +display_online_help (const char *keyword) +{ + (void)keyword; +} + +/* Stub: + * We don't use secret keys, but getkey.c links to this + */ +int +check_secret_key (PKT_public_key *pk, int n) +{ + (void)pk; + (void)n; + return GPG_ERR_GENERAL; +} + +/* Stub: + * No secret key, so no passphrase needed + */ +DEK * +passphrase_to_dek (int cipher_algo, STRING2KEY *s2k, int create, int nocache, + const char *tmp, unsigned int flags, int *canceled) +{ + (void)cipher_algo; + (void)s2k; + (void)create; + (void)nocache; + (void)tmp; + (void)flags; + + if (canceled) + *canceled = 0; + return NULL; +} + +void +passphrase_clear_cache (const char *cacheid) +{ + (void)cacheid; +} + +struct keyserver_spec * +parse_preferred_keyserver(PKT_signature *sig) +{ + (void)sig; + return NULL; +} + +struct keyserver_spec * +parse_keyserver_uri (const char *uri, int require_scheme, + const char *configname, unsigned int configlineno) +{ + (void)uri; + (void)require_scheme; + (void)configname; + (void)configlineno; + return NULL; +} + +void +free_keyserver_spec (struct keyserver_spec *keyserver) +{ + (void)keyserver; +} + +/* Stubs to avoid linking to photoid.c */ +void +show_photos (const struct user_attribute *attrs, int count, PKT_public_key *pk) +{ + (void)attrs; + (void)count; + (void)pk; +} + +int +parse_image_header (const struct user_attribute *attr, byte *type, u32 *len) +{ + (void)attr; + (void)type; + (void)len; + return 0; +} + +char * +image_type_to_string (byte type, int string) +{ + (void)type; + (void)string; + return NULL; +} + +#ifdef ENABLE_CARD_SUPPORT +int +agent_scd_getattr (const char *name, struct agent_card_info_s *info) +{ + (void)name; + (void)info; + return 0; +} +#endif /* ENABLE_CARD_SUPPORT */ + +/* We do not do any locking, so use these stubs here */ +void +dotlock_disable (void) +{ +} + +dotlock_t +dotlock_create (const char *file_to_lock, unsigned int flags) +{ + (void)file_to_lock; + (void)flags; + return NULL; +} + +void +dotlock_destroy (dotlock_t h) +{ + (void)h; +} + +int +dotlock_take (dotlock_t h, long timeout) +{ + (void)h; + (void)timeout; + return 0; +} + +int +dotlock_release (dotlock_t h) +{ + (void)h; + return 0; +} + +void +dotlock_remove_lockfiles (void) +{ +} + +gpg_error_t +agent_probe_secret_key (ctrl_t ctrl, PKT_public_key *pk) +{ + (void)ctrl; + (void)pk; + return gpg_error (GPG_ERR_NO_SECKEY); +} + +gpg_error_t +agent_probe_any_secret_key (ctrl_t ctrl, kbnode_t keyblock) +{ + (void)ctrl; + (void)keyblock; + return gpg_error (GPG_ERR_NO_SECKEY); +} + +gpg_error_t +agent_get_keyinfo (ctrl_t ctrl, const char *hexkeygrip, + char **r_serialno, int *r_cleartext) +{ + (void)ctrl; + (void)hexkeygrip; + (void)r_cleartext; + *r_serialno = NULL; + return gpg_error (GPG_ERR_NO_SECKEY); +} + +gpg_error_t +gpg_dirmngr_get_pka (ctrl_t ctrl, const char *userid, + unsigned char **r_fpr, size_t *r_fprlen, + char **r_url) +{ + (void)ctrl; + (void)userid; + if (r_fpr) + *r_fpr = NULL; + if (r_fprlen) + *r_fprlen = 0; + if (r_url) + *r_url = NULL; + return gpg_error (GPG_ERR_NOT_FOUND); +} + +gpg_error_t +export_pubkey_buffer (ctrl_t ctrl, const char *keyspec, unsigned int options, + const void *prefix, size_t prefixlen, + export_stats_t stats, + kbnode_t *r_keyblock, void **r_data, size_t *r_datalen) +{ + (void)ctrl; + (void)keyspec; + (void)options; + (void)prefix; + (void)prefixlen; + (void)stats; + + *r_keyblock = NULL; + *r_data = NULL; + *r_datalen = 0; + return gpg_error (GPG_ERR_NOT_IMPLEMENTED); +} + +gpg_error_t +tofu_write_tfs_record (ctrl_t ctrl, estream_t fp, + PKT_public_key *pk, const char *user_id) +{ + (void)ctrl; + (void)fp; + (void)pk; + (void)user_id; + return gpg_error (GPG_ERR_GENERAL); +} + +gpg_error_t +tofu_get_policy (ctrl_t ctrl, PKT_public_key *pk, PKT_user_id *user_id, + enum tofu_policy *policy) +{ + (void)ctrl; + (void)pk; + (void)user_id; + (void)policy; + return gpg_error (GPG_ERR_GENERAL); +} + +const char * +tofu_policy_str (enum tofu_policy policy) +{ + (void)policy; + + return "unknown"; +} + +void +tofu_begin_batch_update (ctrl_t ctrl) +{ + (void)ctrl; +} + +void +tofu_end_batch_update (ctrl_t ctrl) +{ + (void)ctrl; +} + +gpg_error_t +tofu_notice_key_changed (ctrl_t ctrl, kbnode_t kb) +{ + (void) ctrl; + (void) kb; + + return 0; +} + + +int +get_revocation_reason (PKT_signature *sig, char **r_reason, + char **r_comment, size_t *r_commentlen) +{ + (void)sig; + (void)r_commentlen; + + if (r_reason) + *r_reason = NULL; + if (r_comment) + *r_comment = NULL; + return 0; +} diff --git a/g10/gpgv.w32-manifest.in b/g10/gpgv.w32-manifest.in new file mode 100644 index 0000000..b7a2120 --- /dev/null +++ b/g10/gpgv.w32-manifest.in @@ -0,0 +1,18 @@ + + +GNU Privacy Guard (OpenPGP verify tool) + + + + + + + + + + + diff --git a/g10/helptext.c b/g10/helptext.c new file mode 100644 index 0000000..8b85101 --- /dev/null +++ b/g10/helptext.c @@ -0,0 +1,86 @@ +/* helptext.c - English help texts + * Copyright (C) 1998, 1999, 2000, 2001, 2002, + * 2004, 2007 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include +#include +#include +#include + +#include "gpg.h" +#include "../common/util.h" +#include "../common/ttyio.h" +#include "main.h" +#include "../common/i18n.h" + + + + +/* Helper to get the help through the configurable GnuPG help + system. */ +static char * +get_help_from_file (const char *keyword) +{ + char *key, *result; + + key = xtrymalloc (4 + strlen (keyword) + 1); + if (key) + { + strcpy (stpcpy (key, "gpg."), keyword); + result = gnupg_get_help_string (key, 0); + xfree (key); + if (result && !is_native_utf8 ()) + { + char *tmp = utf8_to_native (result, strlen (result), -1); + if (tmp) + { + xfree (result); + result = tmp; + } + } + } + else + result = NULL; + return result; +} + + +void +display_online_help( const char *keyword ) +{ + char *result; + int need_final_lf = 1; + + tty_kill_prompt(); + if ( !keyword ) + tty_printf (_("No help available") ); + else if ( (result = get_help_from_file (keyword)) ) + { + tty_printf ("%s", result); + if (*result && result[strlen (result)-1] == '\n') + need_final_lf = 0; + xfree (result); + } + else + { + tty_printf (_("No help available for '%s'"), keyword ); + } + if (need_final_lf) + tty_printf("\n"); +} diff --git a/g10/import.c b/g10/import.c new file mode 100644 index 0000000..b2d5c1d --- /dev/null +++ b/g10/import.c @@ -0,0 +1,4528 @@ +/* import.c - import a key into our key storage. + * Copyright (C) 1998-2007, 2010-2011 Free Software Foundation, Inc. + * Copyright (C) 2014, 2016, 2017, 2019 Werner Koch + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include +#include +#include +#include +#include + +#include "gpg.h" +#include "options.h" +#include "packet.h" +#include "../common/status.h" +#include "keydb.h" +#include "../common/util.h" +#include "trustdb.h" +#include "main.h" +#include "../common/i18n.h" +#include "../common/ttyio.h" +#include "../common/recsel.h" +#include "keyserver-internal.h" +#include "call-agent.h" +#include "../common/membuf.h" +#include "../common/init.h" +#include "../common/mbox-util.h" +#include "key-check.h" +#include "key-clean.h" + + +struct import_stats_s +{ + ulong count; + ulong no_user_id; + ulong imported; + 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; + ulong v3keys; /* Number of V3 keys seen. */ +}; + + +/* Node flag to indicate that a user ID or a subkey has a + * valid self-signature. */ +#define NODE_GOOD_SELFSIG 1 +/* Node flag to indicate that a user ID or subkey has + * an invalid self-signature. */ +#define NODE_BAD_SELFSIG 2 +/* Node flag to indicate that the node shall be deleted. */ +#define NODE_DELETION_MARK 4 +/* A node flag used to temporary mark a node. */ +#define NODE_FLAG_A 8 +/* A flag used by transfer_secret_keys. */ +#define NODE_TRANSFER_SECKEY 16 + + +/* An object and a global instance to store selectors created from + * --import-filter keep-uid=EXPR. + * --import-filter drop-sig=EXPR. + * + * FIXME: We should put this into the CTRL object but that requires a + * lot more changes right now. For now we use save and restore + * function to temporary change them. + */ +/* Definition of the import filters. */ +struct import_filter_s +{ + recsel_expr_t keep_uid; + recsel_expr_t drop_sig; +}; +/* The current instance. */ +struct import_filter_s import_filter; + + +static int import (ctrl_t ctrl, + IOBUF inp, const char* fname, struct import_stats_s *stats, + unsigned char **fpr, size_t *fpr_len, unsigned int options, + import_screener_t screener, void *screener_arg, + int origin, const char *url); +static int read_block (IOBUF a, unsigned int options, + PACKET **pending_pkt, kbnode_t *ret_root, int *r_v3keys); +static void revocation_present (ctrl_t ctrl, kbnode_t keyblock); +static gpg_error_t import_one (ctrl_t ctrl, + kbnode_t keyblock, + struct import_stats_s *stats, + unsigned char **fpr, size_t *fpr_len, + unsigned int options, int from_sk, int silent, + import_screener_t screener, void *screener_arg, + int origin, const char *url, int *r_valid); +static gpg_error_t import_matching_seckeys ( + ctrl_t ctrl, kbnode_t seckeys, + const byte *mainfpr, size_t mainfprlen, + struct import_stats_s *stats, int batch); +static gpg_error_t import_secret_one (ctrl_t ctrl, kbnode_t keyblock, + struct import_stats_s *stats, int batch, + unsigned int options, int for_migration, + import_screener_t screener, void *screener_arg, + kbnode_t *r_secattic); +static int import_revoke_cert (ctrl_t ctrl, kbnode_t node, unsigned int options, + struct import_stats_s *stats); +static int chk_self_sigs (ctrl_t ctrl, kbnode_t keyblock, u32 *keyid, + int *non_self); +static int delete_inv_parts (ctrl_t ctrl, kbnode_t keyblock, + u32 *keyid, unsigned int options); +static int any_uid_left (kbnode_t keyblock); +static void remove_all_non_self_sigs (kbnode_t *keyblock, u32 *keyid); +static int merge_blocks (ctrl_t ctrl, unsigned int options, + kbnode_t keyblock_orig, + kbnode_t keyblock, u32 *keyid, + u32 curtime, int origin, const char *url, + int *n_uids, int *n_sigs, int *n_subk ); +static gpg_error_t append_new_uid (unsigned int options, + kbnode_t keyblock, kbnode_t node, + u32 curtime, int origin, const char *url, + int *n_sigs); +static int append_key (kbnode_t keyblock, kbnode_t node, int *n_sigs); +static int merge_sigs (kbnode_t dst, kbnode_t src, int *n_sigs); +static int merge_keysigs (kbnode_t dst, kbnode_t src, int *n_sigs); + + + +static void +release_import_filter (import_filter_t filt) +{ + recsel_release (filt->keep_uid); + filt->keep_uid = NULL; + recsel_release (filt->drop_sig); + filt->drop_sig = NULL; +} + +static void +cleanup_import_globals (void) +{ + release_import_filter (&import_filter); +} + + +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")}, + + {"keep-ownertrust", IMPORT_KEEP_OWNERTTRUST, NULL, + N_("do not clear the ownertrust values during import")}, + + {"fast-import",IMPORT_FAST,NULL, + N_("do not update the trustdb after import")}, + + {"import-show",IMPORT_SHOW,NULL, + N_("show key during import")}, + + {"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")}, + + {"self-sigs-only", IMPORT_SELF_SIGS_ONLY, NULL, + N_("ignore key-signatures which are not self-signatures")}, + + {"import-export", IMPORT_EXPORT, NULL, + N_("run import filters and export key immediately")}, + + {"restore", IMPORT_RESTORE, NULL, + N_("assume the GnuPG key backup format")}, + {"import-restore", IMPORT_RESTORE, NULL, NULL}, + + {"repair-keys", IMPORT_REPAIR_KEYS, NULL, + N_("repair keys on import")}, + + /* No description to avoid string change: Fixme for 2.3 */ + {"show-only", (IMPORT_SHOW | IMPORT_DRY_RUN), NULL, + NULL}, + + /* 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}, + {"convert-sk-to-pk",0, NULL,NULL}, /* Not anymore needed due to + the new design. */ + {NULL,0,NULL,NULL} + }; + int rc; + int saved_self_sigs_only; + + /* We need to set a flag indicating wether the user has set + * IMPORT_SELF_SIGS_ONLY or it came from the default. */ + saved_self_sigs_only = (*options & IMPORT_SELF_SIGS_ONLY); + saved_self_sigs_only &= ~IMPORT_SELF_SIGS_ONLY; + + rc = parse_options (str, options, import_opts, noisy); + + if (rc && (*options & IMPORT_SELF_SIGS_ONLY)) + opt.flags.expl_import_self_sigs_only = 1; + else + *options |= saved_self_sigs_only; + + if (rc && (*options & IMPORT_RESTORE)) + { + /* Alter other options we want or don't want for restore. */ + *options |= (IMPORT_LOCAL_SIGS | IMPORT_KEEP_OWNERTTRUST); + *options &= ~(IMPORT_MINIMAL | IMPORT_CLEAN + | IMPORT_REPAIR_PKS_SUBKEY_BUG + | IMPORT_MERGE_ONLY); + } + return rc; +} + + +/* Parse and set an import filter from string. STRING has the format + * "NAME=EXPR" with NAME being the name of the filter. Spaces before + * and after NAME are not allowed. If this function is all called + * several times all expressions for the same NAME are concatenated. + * Supported filter names are: + * + * - keep-uid :: If the expression evaluates to true for a certain + * user ID packet, that packet and all it dependencies + * will be imported. The expression may use these + * variables: + * + * - uid :: The entire user ID. + * - mbox :: The mail box part of the user ID. + * - primary :: Evaluate to true for the primary user ID. + */ +gpg_error_t +parse_and_set_import_filter (const char *string) +{ + gpg_error_t err; + + /* Auto register the cleanup function. */ + register_mem_cleanup_func (cleanup_import_globals); + + if (!strncmp (string, "keep-uid=", 9)) + err = recsel_parse_expr (&import_filter.keep_uid, string+9); + else if (!strncmp (string, "drop-sig=", 9)) + err = recsel_parse_expr (&import_filter.drop_sig, string+9); + else + err = gpg_error (GPG_ERR_INV_NAME); + + return err; +} + + +/* Save the current import filters, return them, and clear the current + * filters. Returns NULL on error and sets ERRNO. */ +import_filter_t +save_and_clear_import_filter (void) +{ + import_filter_t filt; + + filt = xtrycalloc (1, sizeof *filt); + if (!filt) + return NULL; + *filt = import_filter; + memset (&import_filter, 0, sizeof import_filter); + + return filt; +} + + +/* Release the current import filters and restore them from NEWFILT. + * Ownership of NEWFILT is moved to this function. */ +void +restore_import_filter (import_filter_t filt) +{ + if (filt) + { + release_import_filter (&import_filter); + import_filter = *filt; + xfree (filt); + } +} + + +import_stats_t +import_new_stats_handle (void) +{ + return xmalloc_clear ( sizeof (struct import_stats_s) ); +} + + +void +import_release_stats_handle (import_stats_t p) +{ + xfree (p); +} + + +/* Read a key from a file. Only the first key in the file is + * considered and stored at R_KEYBLOCK. FNAME is the name of the + * file. + */ +gpg_error_t +read_key_from_file_or_buffer (ctrl_t ctrl, const char *fname, + const void *buffer, size_t buflen, + kbnode_t *r_keyblock) +{ + gpg_error_t err; + iobuf_t inp; + PACKET *pending_pkt = NULL; + kbnode_t keyblock = NULL; + u32 keyid[2]; + int v3keys; /* Dummy */ + int non_self; /* Dummy */ + + (void)ctrl; + + *r_keyblock = NULL; + + log_assert (!!fname ^ !!buffer); + + if (fname) + { + inp = iobuf_open (fname); + if (!inp) + err = gpg_error_from_syserror (); + else if (is_secured_file (iobuf_get_fd (inp))) + { + iobuf_close (inp); + inp = NULL; + err = gpg_error (GPG_ERR_EPERM); + } + else + err = 0; + if (err) + { + log_error (_("can't open '%s': %s\n"), + iobuf_is_pipe_filename (fname)? "[stdin]": fname, + gpg_strerror (err)); + if (gpg_err_code (err) == GPG_ERR_ENOENT) + err = gpg_error (GPG_ERR_NO_PUBKEY); + goto leave; + } + + /* Push the armor filter. */ + { + armor_filter_context_t *afx; + afx = new_armor_context (); + afx->only_keyblocks = 1; + push_armor_filter (afx, inp); + release_armor_context (afx); + } + + } + else /* Read from buffer (No armor expected). */ + { + inp = iobuf_temp_with_content (buffer, buflen); + } + + /* Read the first non-v3 keyblock. */ + while (!(err = read_block (inp, 0, &pending_pkt, &keyblock, &v3keys))) + { + if (keyblock->pkt->pkttype == PKT_PUBLIC_KEY) + break; + log_info (_("skipping block of type %d\n"), keyblock->pkt->pkttype); + release_kbnode (keyblock); + keyblock = NULL; + } + if (err) + { + if (gpg_err_code (err) != GPG_ERR_INV_KEYRING) + log_error (_("error reading '%s': %s\n"), + fname? (iobuf_is_pipe_filename (fname)? "[stdin]": fname) + /* */ : "[buffer]", + gpg_strerror (err)); + goto leave; + } + + keyid_from_pk (keyblock->pkt->pkt.public_key, keyid); + + if (!find_next_kbnode (keyblock, PKT_USER_ID)) + { + err = gpg_error (GPG_ERR_NO_USER_ID); + goto leave; + } + + collapse_uids (&keyblock); + + clear_kbnode_flags (keyblock); + if (chk_self_sigs (ctrl, keyblock, keyid, &non_self)) + { + err = gpg_error (GPG_ERR_INV_KEYRING); + goto leave; + } + + if (!delete_inv_parts (ctrl, keyblock, keyid, 0) ) + { + err = gpg_error (GPG_ERR_NO_USER_ID); + goto leave; + } + + *r_keyblock = keyblock; + keyblock = NULL; + + leave: + if (inp) + { + iobuf_close (inp); + /* Must invalidate that ugly cache to actually close the file. */ + if (fname) + iobuf_ioctl (NULL, IOBUF_IOCTL_INVALIDATE_CACHE, 0, (char*)fname); + } + release_kbnode (keyblock); + /* FIXME: Do we need to free PENDING_PKT ? */ + return err; +} + + +/* Import an already checked public key which was included in a + * signature and the signature verified out using this key. */ +gpg_error_t +import_included_key_block (ctrl_t ctrl, kbnode_t keyblock) +{ + gpg_error_t err; + struct import_stats_s *stats; + import_filter_t save_filt; + int save_armor = opt.armor; + + opt.armor = 0; + stats = import_new_stats_handle (); + save_filt = save_and_clear_import_filter (); + if (!save_filt) + { + err = gpg_error_from_syserror (); + goto leave; + } + + /* FIXME: Should we introduce a dedicated KEYORG ? */ + err = import_one (ctrl, keyblock, + stats, NULL, 0, 0, 0, 0, + NULL, NULL, KEYORG_UNKNOWN, NULL, NULL); + + leave: + restore_import_filter (save_filt); + import_release_stats_handle (stats); + opt.armor = save_armor; + return err; +} + + + +/* + * 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 gpg_error_t +import_keys_internal (ctrl_t ctrl, iobuf_t inp, char **fnames, int nnames, + import_stats_t stats_handle, + unsigned char **fpr, size_t *fpr_len, + unsigned int options, + import_screener_t screener, void *screener_arg, + int origin, const char *url) +{ + int i; + gpg_error_t err = 0; + struct import_stats_s *stats = stats_handle; + + if (!stats) + stats = import_new_stats_handle (); + + if (inp) + { + err = import (ctrl, inp, "[stream]", stats, fpr, fpr_len, options, + screener, screener_arg, origin, url); + } + 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; + gpg_err_set_errno (EPERM); + } + if (!inp2) + log_error (_("can't open '%s': %s\n"), fname, strerror (errno)); + else + { + err = import (ctrl, inp2, fname, stats, fpr, fpr_len, options, + screener, screener_arg, origin, url); + iobuf_close (inp2); + /* Must invalidate that ugly cache to actually close it. */ + iobuf_ioctl (NULL, IOBUF_IOCTL_INVALIDATE_CACHE, 0, (char*)fname); + if (err) + log_error ("import from '%s' failed: %s\n", + fname, gpg_strerror (err) ); + } + if (!fname) + break; + } + } + + if (!stats_handle) + { + if ((options & (IMPORT_SHOW | IMPORT_DRY_RUN)) + != (IMPORT_SHOW | IMPORT_DRY_RUN)) + 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)) + check_or_update_trustdb (ctrl); + + return err; +} + + +void +import_keys (ctrl_t ctrl, char **fnames, int nnames, + import_stats_t stats_handle, unsigned int options, + int origin, const char *url) +{ + import_keys_internal (ctrl, NULL, fnames, nnames, stats_handle, + NULL, NULL, options, NULL, NULL, origin, url); +} + + +gpg_error_t +import_keys_es_stream (ctrl_t ctrl, estream_t fp, + import_stats_t stats_handle, + unsigned char **fpr, size_t *fpr_len, + unsigned int options, + import_screener_t screener, void *screener_arg, + int origin, const char *url) +{ + gpg_error_t err; + iobuf_t inp; + + inp = iobuf_esopen (fp, "rb", 1); + if (!inp) + { + err = gpg_error_from_syserror (); + log_error ("iobuf_esopen failed: %s\n", gpg_strerror (err)); + return err; + } + + err = import_keys_internal (ctrl, inp, NULL, 0, stats_handle, + fpr, fpr_len, options, + screener, screener_arg, origin, url); + + iobuf_close (inp); + return err; +} + + +static int +import (ctrl_t ctrl, IOBUF inp, const char* fname,struct import_stats_s *stats, + unsigned char **fpr,size_t *fpr_len, unsigned int options, + import_screener_t screener, void *screener_arg, + int origin, const char *url) +{ + PACKET *pending_pkt = NULL; + kbnode_t keyblock = NULL; /* Need to initialize because gcc can't + grasp the return semantics of + read_block. */ + kbnode_t secattic = NULL; /* Kludge for PGP desktop percularity */ + int rc = 0; + int v3keys; + + getkey_disable_caches (); + + if (!opt.no_armor) /* Armored reading is not disabled. */ + { + armor_filter_context_t *afx; + + afx = new_armor_context (); + afx->only_keyblocks = 1; + push_armor_filter (afx, inp); + release_armor_context (afx); + } + + while (!(rc = read_block (inp, options, &pending_pkt, &keyblock, &v3keys))) + { + stats->v3keys += v3keys; + if (keyblock->pkt->pkttype == PKT_PUBLIC_KEY) + { + rc = import_one (ctrl, keyblock, + stats, fpr, fpr_len, options, 0, 0, + screener, screener_arg, origin, url, NULL); + if (secattic) + { + byte tmpfpr[MAX_FINGERPRINT_LEN]; + size_t tmpfprlen; + + if (!rc && !(opt.dry_run || (options & IMPORT_DRY_RUN))) + { + /* Kudge for PGP desktop - see below. */ + fingerprint_from_pk (keyblock->pkt->pkt.public_key, + tmpfpr, &tmpfprlen); + rc = import_matching_seckeys (ctrl, secattic, + tmpfpr, tmpfprlen, + stats, opt.batch); + } + release_kbnode (secattic); + secattic = NULL; + } + } + else if (keyblock->pkt->pkttype == PKT_SECRET_KEY) + { + release_kbnode (secattic); + secattic = NULL; + rc = import_secret_one (ctrl, keyblock, stats, + opt.batch, options, 0, + screener, screener_arg, &secattic); + keyblock = NULL; /* Ownership was transferred. */ + if (secattic) + { + if (gpg_err_code (rc) == GPG_ERR_NO_PUBKEY) + rc = 0; /* Try import after the next pubkey. */ + + /* The attic is a workaround for the peculiar PGP + * Desktop method of exporting a secret key: The + * exported file is the concatenation of two armored + * keyblocks; first the private one and then the public + * one. The strange thing is that the secret one has no + * binding signatures at all and thus we have not + * imported it. The attic stores that secret keys and + * we try to import it once after the very next public + * keyblock. */ + } + } + else if (keyblock->pkt->pkttype == PKT_SIGNATURE + && IS_KEY_REV (keyblock->pkt->pkt.signature) ) + { + release_kbnode (secattic); + secattic = NULL; + rc = import_revoke_cert (ctrl, keyblock, options, stats); + } + else + { + release_kbnode (secattic); + secattic = NULL; + 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. For now we do this only if the imported key is too + large. */ + if (gpg_err_code (rc) == GPG_ERR_TOO_LARGE + && gpg_err_source (rc) == GPG_ERR_SOURCE_KEYBOX) + { + stats->not_imported++; + } + else if (rc) + break; + + if (!(++stats->count % 100) && !opt.quiet) + log_info (_("%lu keys processed so far\n"), stats->count ); + + if (origin == KEYORG_WKD && stats->count >= 5) + { + /* We limit the number of keys _received_ from the WKD to 5. + * In fact there should be only one key but some sites want + * to store a few expired keys there also. gpg's key + * selection will later figure out which key to use. Note + * that for WKD we always return the fingerprint of the + * first imported key. */ + log_info ("import from WKD stopped after %d keys\n", 5); + break; + } + } + stats->v3keys += v3keys; + if (rc == -1) + rc = 0; + else if (rc && gpg_err_code (rc) != GPG_ERR_INV_KEYRING) + log_error (_("error reading '%s': %s\n"), fname, gpg_strerror (rc)); + + release_kbnode (secattic); + return rc; +} + + +/* Helper to migrate secring.gpg to GnuPG 2.1. */ +gpg_error_t +import_old_secring (ctrl_t ctrl, const char *fname) +{ + gpg_error_t err; + iobuf_t inp; + PACKET *pending_pkt = NULL; + kbnode_t keyblock = NULL; /* Need to initialize because gcc can't + grasp the return semantics of + read_block. */ + struct import_stats_s *stats; + int v3keys; + + inp = iobuf_open (fname); + if (inp && is_secured_file (iobuf_get_fd (inp))) + { + iobuf_close (inp); + inp = NULL; + gpg_err_set_errno (EPERM); + } + if (!inp) + { + err = gpg_error_from_syserror (); + log_error (_("can't open '%s': %s\n"), fname, gpg_strerror (err)); + return err; + } + + getkey_disable_caches(); + stats = import_new_stats_handle (); + while (!(err = read_block (inp, 0, &pending_pkt, &keyblock, &v3keys))) + { + if (keyblock->pkt->pkttype == PKT_SECRET_KEY) + { + err = import_secret_one (ctrl, keyblock, stats, 1, 0, 1, + NULL, NULL, NULL); + keyblock = NULL; /* Ownership was transferred. */ + } + release_kbnode (keyblock); + if (err) + break; + } + import_release_stats_handle (stats); + if (err == -1) + err = 0; + else if (err && gpg_err_code (err) != GPG_ERR_INV_KEYRING) + log_error (_("error reading '%s': %s\n"), fname, gpg_strerror (err)); + else if (err) + log_error ("import from '%s' failed: %s\n", fname, gpg_strerror (err)); + + iobuf_close (inp); + iobuf_ioctl (NULL, IOBUF_IOCTL_INVALIDATE_CACHE, 0, (char*)fname); + + return err; +} + + +void +import_print_stats (import_stats_t stats) +{ + if (!opt.quiet) + { + log_info(_("Total number processed: %lu\n"), + stats->count + stats->v3keys); + if (stats->v3keys) + log_info(_(" skipped PGP-2 keys: %lu\n"), stats->v3keys); + 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) + { + log_info(_(" imported: %lu"), stats->imported ); + log_printf ("\n"); + } + 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[15*20]; + + snprintf (buf, sizeof buf, + "%lu %lu %lu 0 %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu", + stats->count + stats->v3keys, + stats->no_user_id, + stats->imported, + 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, + stats->v3keys ); + write_status_text (STATUS_IMPORT_RES, buf); + } +} + + +/* Return true if PKTTYPE is valid in a keyblock. */ +static int +valid_keyblock_packet (int pkttype) +{ + switch (pkttype) + { + case PKT_PUBLIC_KEY: + case PKT_PUBLIC_SUBKEY: + case PKT_SECRET_KEY: + case PKT_SECRET_SUBKEY: + case PKT_SIGNATURE: + case PKT_USER_ID: + case PKT_ATTRIBUTE: + case PKT_RING_TRUST: + return 1; + default: + return 0; + } +} + + +/* Read the next keyblock from stream A. Meta data (ring trust + * packets) are only considered if OPTIONS has the IMPORT_RESTORE flag + * set. PENDING_PKT should be initialized to NULL and not changed by + * the caller. + * + * Returns 0 for okay, -1 no more blocks, or any other errorcode. The + * integer at R_V3KEY counts the number of unsupported v3 keyblocks. + */ +static int +read_block( IOBUF a, unsigned int options, + PACKET **pending_pkt, kbnode_t *ret_root, int *r_v3keys) +{ + int rc; + struct parse_packet_ctx_s parsectx; + PACKET *pkt; + kbnode_t root = NULL; + kbnode_t lastnode = NULL; + int in_cert, in_v3key, skip_sigs; + u32 keyid[2]; + int got_keyid = 0; + unsigned int dropped_nonselfsigs = 0; + + *r_v3keys = 0; + + if (*pending_pkt) + { + root = lastnode = new_kbnode( *pending_pkt ); + *pending_pkt = NULL; + log_assert (root->pkt->pkttype == PKT_PUBLIC_KEY + || root->pkt->pkttype == PKT_SECRET_KEY); + in_cert = 1; + keyid_from_pk (root->pkt->pkt.public_key, keyid); + got_keyid = 1; + } + else + in_cert = 0; + + pkt = xmalloc (sizeof *pkt); + init_packet (pkt); + init_parse_packet (&parsectx, a); + if (!(options & IMPORT_RESTORE)) + parsectx.skip_meta = 1; + in_v3key = 0; + skip_sigs = 0; + while ((rc=parse_packet (&parsectx, pkt)) != -1) + { + if (rc && ((gpg_err_code (rc) == GPG_ERR_LEGACY_KEY + || gpg_err_code (rc) == GPG_ERR_UNKNOWN_VERSION) + && (pkt->pkttype == PKT_PUBLIC_KEY + || pkt->pkttype == PKT_SECRET_KEY))) + { + in_v3key = 1; + if (gpg_err_code (rc) != GPG_ERR_UNKNOWN_VERSION) + ++*r_v3keys; + free_packet (pkt, &parsectx); + init_packet (pkt); + continue; + } + else if (rc ) /* (ignore errors) */ + { + skip_sigs = 0; + if (gpg_err_code (rc) == GPG_ERR_UNKNOWN_PACKET) + ; /* Do not show a diagnostic. */ + else if (gpg_err_code (rc) == GPG_ERR_INV_PACKET + && (pkt->pkttype == PKT_USER_ID + || pkt->pkttype == PKT_ATTRIBUTE)) + { + /* This indicates a too large user id or attribute + * packet. We skip this packet and all following + * signatures. Sure, this won't allow to repair a + * garbled keyring in case one of the signatures belong + * to another user id. However, this better mitigates + * DoS using inserted user ids. */ + skip_sigs = 1; + } + else if (gpg_err_code (rc) == GPG_ERR_INV_PACKET + && (pkt->pkttype == PKT_OLD_COMMENT + || pkt->pkttype == PKT_COMMENT)) + ; /* Ignore too large comment packets. */ + else + { + log_error("read_block: read error: %s\n", gpg_strerror (rc) ); + rc = GPG_ERR_INV_KEYRING; + goto ready; + } + free_packet (pkt, &parsectx); + init_packet(pkt); + continue; + } + + if (skip_sigs) + { + if (pkt->pkttype == PKT_SIGNATURE) + { + free_packet (pkt, &parsectx); + init_packet (pkt); + continue; + } + skip_sigs = 0; + } + + if (in_v3key && !(pkt->pkttype == PKT_PUBLIC_KEY + || pkt->pkttype == PKT_SECRET_KEY)) + { + free_packet (pkt, &parsectx); + init_packet(pkt); + continue; + } + in_v3key = 0; + + if (!root && pkt->pkttype == PKT_SIGNATURE + && IS_KEY_REV (pkt->pkt.signature) ) + { + /* 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 = GPG_ERR_COMPR_ALGO; + goto ready; + } + else + { + compress_filter_context_t *cfx = xmalloc_clear( sizeof *cfx ); + pkt->pkt.compressed->buf = NULL; + if (push_compress_filter2 (a, cfx, + pkt->pkt.compressed->algorithm, 1)) + xfree (cfx); /* e.g. in case of compression_algo NONE. */ + } + free_packet (pkt, &parsectx); + init_packet(pkt); + break; + + case PKT_RING_TRUST: + /* Skip those packets unless we are in restore mode. */ + if ((opt.import_options & IMPORT_RESTORE)) + goto x_default; + free_packet (pkt, &parsectx); + init_packet(pkt); + break; + + case PKT_SIGNATURE: + if (!in_cert) + goto x_default; + if (!(options & IMPORT_SELF_SIGS_ONLY)) + goto x_default; + log_assert (got_keyid); + if (pkt->pkt.signature->keyid[0] == keyid[0] + && pkt->pkt.signature->keyid[1] == keyid[1]) + { /* This is likely a self-signature. We import this one. + * Eventually we should use the ISSUER_FPR to compare + * self-signatures, but that will work only for v5 keys + * which are currently not even deployed. + * Note that we do not do any crypto verify here because + * that would defeat this very mitigation of DoS by + * importing a key with a huge amount of faked + * key-signatures. A verification will be done later in + * the processing anyway. Here we want a cheap an early + * way to drop non-self-signatures. */ + goto x_default; + } + /* Skip this signature. */ + dropped_nonselfsigs++; + free_packet (pkt, &parsectx); + init_packet(pkt); + break; + + case PKT_PUBLIC_KEY: + case PKT_SECRET_KEY: + if (!got_keyid) + { + keyid_from_pk (pkt->pkt.public_key, keyid); + got_keyid = 1; + } + if (in_cert) /* Store this packet. */ + { + *pending_pkt = pkt; + pkt = NULL; + goto ready; + } + in_cert = 1; + goto x_default; + + default: + x_default: + if (in_cert && valid_keyblock_packet (pkt->pkttype)) + { + if (!root ) + root = lastnode = new_kbnode (pkt); + else + { + lastnode->next = new_kbnode (pkt); + lastnode = lastnode->next; + } + pkt = xmalloc (sizeof *pkt); + } + else + free_packet (pkt, &parsectx); + init_packet(pkt); + break; + } + } + + ready: + if (rc == -1 && root ) + rc = 0; + + if (rc ) + release_kbnode( root ); + else + *ret_root = root; + free_packet (pkt, &parsectx); + deinit_parse_packet (&parsectx); + xfree( pkt ); + if (!rc && dropped_nonselfsigs && opt.verbose) + log_info ("key %s: number of dropped non-self-signatures: %u\n", + keystr (keyid), dropped_nonselfsigs); + + 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 (ctrl_t ctrl, kbnode_t keyblock) +{ + int changed = 0; + int keycount = 0; + kbnode_t node; + kbnode_t last = NULL; + kbnode_t 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 + && IS_SUBKEY_SIG (node->pkt->pkt.signature) + && keycount >= 2 + && !node->next) + { + /* We might have the problem, as this key has two subkeys in + a row without any intervening packets. */ + + /* Sanity check */ + if (!last) + 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 (ctrl, keyblock,node,NULL)) + { + /* Not a match, so undo the changes. */ + sknode->next = node->next; + last->next = node; + node->next = NULL; + break; + } + else + { + /* Mark it good so we don't need to check it again */ + sknode->flag |= NODE_GOOD_SELFSIG; + changed = 1; + break; + } + } + else + keycount = 0; + } + + return changed; +} + + +/* Versions of GnuPG before 1.4.11 and 2.0.16 allowed to import bogus + direct key signatures. A side effect of this was that a later + import of the same good direct key signatures was not possible + because the cmp_signature check in merge_blocks considered them + equal. Although direct key signatures are now checked during + import, there might still be bogus signatures sitting in a keyring. + We need to detect and delete them before doing a merge. This + function returns the number of removed sigs. */ +static int +fix_bad_direct_key_sigs (ctrl_t ctrl, kbnode_t keyblock, u32 *keyid) +{ + gpg_error_t err; + kbnode_t node; + int count = 0; + + for (node = keyblock->next; node; node=node->next) + { + if (node->pkt->pkttype == PKT_USER_ID) + break; + if (node->pkt->pkttype == PKT_SIGNATURE + && IS_KEY_SIG (node->pkt->pkt.signature)) + { + err = check_key_signature (ctrl, keyblock, node, NULL); + if (err && gpg_err_code (err) != GPG_ERR_PUBKEY_ALGO ) + { + /* If we don't know the error, we can't decide; this is + not a problem because cmp_signature can't compare the + signature either. */ + log_info ("key %s: invalid direct key signature removed\n", + keystr (keyid)); + delete_kbnode (node); + count++; + } + } + } + + return count; +} + + +static void +print_import_ok (PKT_public_key *pk, unsigned int reason) +{ + byte array[MAX_FINGERPRINT_LEN], *s; + char buf[MAX_FINGERPRINT_LEN*2+30], *p; + size_t i, n; + + snprintf (buf, sizeof buf, "%u ", reason); + p = buf + strlen (buf); + + fingerprint_from_pk (pk, 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) +{ + byte hexfpr[2*MAX_FINGERPRINT_LEN+1]; + u32 keyid[2]; + + keyid_from_pk (pk, keyid); + hexfingerprint (pk, hexfpr, sizeof hexfpr); + write_status_printf (STATUS_IMPORT_CHECK, "%08X%08X %s %s", + keyid[0], keyid[1], hexfpr, id->name); + +} + + +static void +check_prefs_warning(PKT_public_key *pk) +{ + log_info(_("WARNING: key %s contains preferences for unavailable\n" + "algorithms on these user IDs:\n"), keystr_from_pk(pk)); +} + + +static void +check_prefs (ctrl_t ctrl, kbnode_t keyblock) +{ + kbnode_t node; + PKT_public_key *pk; + int problem=0; + + merge_keys_and_selfsig (ctrl, 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 (openpgp_cipher_test_algo (prefs->value)) + { + const char *algo = + (openpgp_cipher_test_algo (prefs->value) + ? num + : openpgp_cipher_algo_name (prefs->value)); + if(!problem) + check_prefs_warning(pk); + log_info(_(" \"%s\": preference for cipher" + " algorithm %s\n"), user, algo); + problem=1; + } + } + else if(prefs->type==PREFTYPE_HASH) + { + if(openpgp_md_test_algo(prefs->value)) + { + const char *algo = + (gcry_md_test_algo (prefs->value) + ? num + : gcry_md_algo_name (prefs->value)); + if(!problem) + check_prefs_warning(pk); + log_info(_(" \"%s\": preference for digest" + " algorithm %s\n"), user, algo); + 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_t sl = NULL; + strlist_t 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;ictrl; + kbnode_t node = parm->node; + static char numbuf[20]; + const char *result; + + log_assert (ctrl && ctrl->magic == SERVER_CONTROL_MAGIC); + + if (node->pkt->pkttype == PKT_USER_ID + || node->pkt->pkttype == PKT_ATTRIBUTE) + { + PKT_user_id *uid = node->pkt->pkt.user_id; + + if (!strcmp (propname, "uid")) + result = uid->name; + else if (!strcmp (propname, "mbox")) + { + if (!uid->mbox) + { + uid->mbox = mailbox_from_userid (uid->name); + } + result = uid->mbox; + } + else if (!strcmp (propname, "primary")) + { + result = uid->flags.primary? "1":"0"; + } + else if (!strcmp (propname, "expired")) + { + result = uid->flags.expired? "1":"0"; + } + else if (!strcmp (propname, "revoked")) + { + result = uid->flags.revoked? "1":"0"; + } + else + result = NULL; + } + else if (node->pkt->pkttype == PKT_SIGNATURE) + { + PKT_signature *sig = node->pkt->pkt.signature; + + if (!strcmp (propname, "sig_created")) + { + snprintf (numbuf, sizeof numbuf, "%lu", (ulong)sig->timestamp); + result = numbuf; + } + else if (!strcmp (propname, "sig_created_d")) + { + result = datestr_from_sig (sig); + } + else if (!strcmp (propname, "sig_algo")) + { + snprintf (numbuf, sizeof numbuf, "%d", sig->pubkey_algo); + result = numbuf; + } + else if (!strcmp (propname, "sig_digest_algo")) + { + snprintf (numbuf, sizeof numbuf, "%d", sig->digest_algo); + result = numbuf; + } + else if (!strcmp (propname, "expired")) + { + result = sig->flags.expired? "1":"0"; + } + else + result = NULL; + } + else if (node->pkt->pkttype == PKT_PUBLIC_KEY + || node->pkt->pkttype == PKT_SECRET_KEY + || node->pkt->pkttype == PKT_PUBLIC_SUBKEY + || node->pkt->pkttype == PKT_SECRET_SUBKEY) + { + PKT_public_key *pk = node->pkt->pkt.public_key; + + if (!strcmp (propname, "secret")) + { + result = (node->pkt->pkttype == PKT_SECRET_KEY + || node->pkt->pkttype == PKT_SECRET_SUBKEY)? "1":"0"; + } + else if (!strcmp (propname, "key_algo")) + { + snprintf (numbuf, sizeof numbuf, "%d", pk->pubkey_algo); + result = numbuf; + } + else if (!strcmp (propname, "key_created")) + { + snprintf (numbuf, sizeof numbuf, "%lu", (ulong)pk->timestamp); + result = numbuf; + } + else if (!strcmp (propname, "key_created_d")) + { + result = datestr_from_pk (pk); + } + else if (!strcmp (propname, "expired")) + { + result = pk->has_expired? "1":"0"; + } + else if (!strcmp (propname, "revoked")) + { + result = pk->flags.revoked? "1":"0"; + } + else if (!strcmp (propname, "disabled")) + { + result = pk_is_disabled (pk)? "1":"0"; + } + else if (!strcmp (propname, "usage")) + { + snprintf (numbuf, sizeof numbuf, "%s%s%s%s%s", + (pk->pubkey_usage & PUBKEY_USAGE_ENC)?"e":"", + (pk->pubkey_usage & PUBKEY_USAGE_SIG)?"s":"", + (pk->pubkey_usage & PUBKEY_USAGE_CERT)?"c":"", + (pk->pubkey_usage & PUBKEY_USAGE_AUTH)?"a":"", + (pk->pubkey_usage & PUBKEY_USAGE_UNKNOWN)?"?":""); + result = numbuf; + } + else if (!strcmp (propname, "fpr")) + { + hexfingerprint (pk, parm->hexfpr, sizeof parm->hexfpr); + result = parm->hexfpr; + } + else + result = NULL; + } + else + result = NULL; + + return result; +} + + +/* + * Apply the keep-uid filter to the keyblock. The deleted nodes are + * marked and thus the caller should call commit_kbnode afterwards. + * KEYBLOCK must not have any blocks marked as deleted. + */ +static void +apply_keep_uid_filter (ctrl_t ctrl, kbnode_t keyblock, recsel_expr_t selector) +{ + kbnode_t node; + struct impex_filter_parm_s parm; + + parm.ctrl = ctrl; + + for (node = keyblock->next; node; node = node->next ) + { + if (node->pkt->pkttype == PKT_USER_ID) + { + parm.node = node; + if (!recsel_select (selector, impex_filter_getval, &parm)) + { + + /* log_debug ("keep-uid: deleting '%s'\n", */ + /* node->pkt->pkt.user_id->name); */ + /* The UID packet and all following packets up to the + * next UID or a subkey. */ + delete_kbnode (node); + for (; node->next + && node->next->pkt->pkttype != PKT_USER_ID + && node->next->pkt->pkttype != PKT_PUBLIC_SUBKEY + && node->next->pkt->pkttype != PKT_SECRET_SUBKEY ; + node = node->next) + delete_kbnode (node->next); + } + /* else */ + /* log_debug ("keep-uid: keeping '%s'\n", */ + /* node->pkt->pkt.user_id->name); */ + } + } +} + + +/* + * Apply the drop-sig filter to the keyblock. The deleted nodes are + * marked and thus the caller should call commit_kbnode afterwards. + * KEYBLOCK must not have any blocks marked as deleted. + */ +static void +apply_drop_sig_filter (ctrl_t ctrl, kbnode_t keyblock, recsel_expr_t selector) +{ + kbnode_t node; + int active = 0; + u32 main_keyid[2]; + PKT_signature *sig; + struct impex_filter_parm_s parm; + + parm.ctrl = ctrl; + + keyid_from_pk (keyblock->pkt->pkt.public_key, main_keyid); + + /* Loop over all signatures for user id and attribute packets which + * are not self signatures. */ + for (node = keyblock->next; node; node = node->next ) + { + if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY + || node->pkt->pkttype == PKT_SECRET_SUBKEY) + break; /* ready. */ + if (node->pkt->pkttype == PKT_USER_ID + || node->pkt->pkttype == PKT_ATTRIBUTE) + active = 1; + if (!active) + continue; + if (node->pkt->pkttype != PKT_SIGNATURE) + continue; + + sig = node->pkt->pkt.signature; + if (main_keyid[0] == sig->keyid[0] || main_keyid[1] == sig->keyid[1]) + continue; /* Skip self-signatures. */ + + if (IS_UID_SIG(sig) || IS_UID_REV(sig)) + { + parm.node = node; + if (recsel_select (selector, impex_filter_getval, &parm)) + delete_kbnode (node); + } + } +} + + +/* Insert a key origin into a public key packet. */ +static gpg_error_t +insert_key_origin_pk (PKT_public_key *pk, u32 curtime, + int origin, const char *url) +{ + if (origin == KEYORG_WKD || origin == KEYORG_DANE) + { + /* For WKD and DANE we insert origin information also for the + * key but we don't record the URL because we have have no use + * for that: An update using a keyserver has higher precedence + * and will thus update this origin info. For refresh using WKD + * or DANE we need to go via the User ID anyway. Recall that we + * are only inserting a new key. */ + pk->keyorg = origin; + pk->keyupdate = curtime; + } + else if (origin == KEYORG_KS && url) + { + /* If the key was retrieved from a keyserver using a fingerprint + * request we add the meta information. Note that the use of a + * fingerprint needs to be enforced by the caller of the import + * function. This is commonly triggered by verifying a modern + * signature which has an Issuer Fingerprint signature + * subpacket. */ + pk->keyorg = origin; + pk->keyupdate = curtime; + xfree (pk->updateurl); + pk->updateurl = xtrystrdup (url); + if (!pk->updateurl) + return gpg_error_from_syserror (); + } + else if (origin == KEYORG_FILE) + { + pk->keyorg = origin; + pk->keyupdate = curtime; + } + else if (origin == KEYORG_URL) + { + pk->keyorg = origin; + pk->keyupdate = curtime; + if (url) + { + xfree (pk->updateurl); + pk->updateurl = xtrystrdup (url); + if (!pk->updateurl) + return gpg_error_from_syserror (); + } + } + + return 0; +} + + +/* Insert a key origin into a user id packet. */ +static gpg_error_t +insert_key_origin_uid (PKT_user_id *uid, u32 curtime, + int origin, const char *url) + +{ + if (origin == KEYORG_WKD || origin == KEYORG_DANE) + { + /* We insert origin information on a UID only when we received + * them via the Web Key Directory or a DANE record. The key we + * receive here from the WKD has been filtered to contain only + * the user ID as looked up in the WKD. For a DANE origin we + * this should also be the case. Thus we will see here only one + * user id. */ + uid->keyorg = origin; + uid->keyupdate = curtime; + if (url) + { + xfree (uid->updateurl); + uid->updateurl = xtrystrdup (url); + if (!uid->updateurl) + return gpg_error_from_syserror (); + } + } + else if (origin == KEYORG_KS && url) + { + /* If the key was retrieved from a keyserver using a fingerprint + * request we mark that also in the user ID. However we do not + * store the keyserver URL in the UID. A later update (merge) + * from a more trusted source will replace this info. */ + uid->keyorg = origin; + uid->keyupdate = curtime; + } + else if (origin == KEYORG_FILE) + { + uid->keyorg = origin; + uid->keyupdate = curtime; + } + else if (origin == KEYORG_URL) + { + uid->keyorg = origin; + uid->keyupdate = curtime; + } + + return 0; +} + + +/* Apply meta data to KEYBLOCK. This sets the origin of the key to + * ORIGIN and the updateurl to URL. Note that this function is only + * used for a new key, that is not when we are merging keys. */ +static gpg_error_t +insert_key_origin (kbnode_t keyblock, int origin, const char *url) +{ + gpg_error_t err; + kbnode_t node; + u32 curtime = make_timestamp (); + + for (node = keyblock; node; node = node->next) + { + if (is_deleted_kbnode (node)) + ; + else if (node->pkt->pkttype == PKT_PUBLIC_KEY) + { + err = insert_key_origin_pk (node->pkt->pkt.public_key, curtime, + origin, url); + if (err) + return err; + } + else if (node->pkt->pkttype == PKT_USER_ID) + { + err = insert_key_origin_uid (node->pkt->pkt.user_id, curtime, + origin, url); + if (err) + return err; + } + } + + return 0; +} + + +/* Update meta data on KEYBLOCK. This updates the key origin on the + * public key according to ORIGIN and URL. The UIDs are already + * updated when this function is called. */ +static gpg_error_t +update_key_origin (kbnode_t keyblock, u32 curtime, int origin, const char *url) +{ + PKT_public_key *pk; + + log_assert (keyblock->pkt->pkttype == PKT_PUBLIC_KEY); + pk = keyblock->pkt->pkt.public_key; + + if (pk->keyupdate > curtime) + ; /* Don't do it for a time warp. */ + else if (origin == KEYORG_WKD || origin == KEYORG_DANE) + { + /* We only update the origin info if they either have never been + * set or are the origin was the same as the new one. If this + * is WKD we also update the UID to show from which user id this + * was updated. */ + if (!pk->keyorg || pk->keyorg == KEYORG_WKD || pk->keyorg == KEYORG_DANE) + { + pk->keyorg = origin; + pk->keyupdate = curtime; + xfree (pk->updateurl); + pk->updateurl = NULL; + if (origin == KEYORG_WKD && url) + { + pk->updateurl = xtrystrdup (url); + if (!pk->updateurl) + return gpg_error_from_syserror (); + } + } + } + else if (origin == KEYORG_KS) + { + /* All updates from a keyserver are considered to have the + * freshed key. Thus we always set the new key origin. */ + pk->keyorg = origin; + pk->keyupdate = curtime; + xfree (pk->updateurl); + pk->updateurl = NULL; + if (url) + { + pk->updateurl = xtrystrdup (url); + if (!pk->updateurl) + return gpg_error_from_syserror (); + } + } + else if (origin == KEYORG_FILE) + { + /* Updates from a file are considered to be fresh. */ + pk->keyorg = origin; + pk->keyupdate = curtime; + xfree (pk->updateurl); + pk->updateurl = NULL; + } + else if (origin == KEYORG_URL) + { + /* Updates from a URL are considered to be fresh. */ + pk->keyorg = origin; + pk->keyupdate = curtime; + xfree (pk->updateurl); + pk->updateurl = NULL; + if (url) + { + pk->updateurl = xtrystrdup (url); + if (!pk->updateurl) + return gpg_error_from_syserror (); + } + } + + return 0; +} + + +/* + * 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 gpg. If SILENT is no messages are printed - + * even most error messages are suppressed. ORIGIN is the origin of + * the key (0 for unknown) and URL the corresponding URL. FROM_SK + * indicates that the key has been made from a secret key. If R_SAVED + * is not NULL a boolean will be stored indicating whether the keyblock + * has valid parts. + */ +static gpg_error_t +import_one_real (ctrl_t ctrl, + kbnode_t keyblock, struct import_stats_s *stats, + unsigned char **fpr, size_t *fpr_len, unsigned int options, + int from_sk, int silent, + import_screener_t screener, void *screener_arg, + int origin, const char *url, int *r_valid) +{ + gpg_error_t err = 0; + PKT_public_key *pk; + kbnode_t node, uidnode; + kbnode_t keyblock_orig = NULL; + byte fpr2[MAX_FINGERPRINT_LEN]; + size_t fpr2len; + u32 keyid[2]; + int new_key = 0; + int mod_key = 0; + int same_key = 0; + int non_self = 0; + size_t an; + char pkstrbuf[PUBKEY_STRING_SIZE]; + int merge_keys_done = 0; + int any_filter = 0; + KEYDB_HANDLE hd = NULL; + + if (r_valid) + *r_valid = 0; + + /* If show-only is active we don't won't any extra output. */ + if ((options & (IMPORT_SHOW | IMPORT_DRY_RUN))) + silent = 1; + + /* 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; + + fingerprint_from_pk (pk, fpr2, &fpr2len); + for (an = fpr2len; an < MAX_FINGERPRINT_LEN; an++) + fpr2[an] = 0; + keyid_from_pk( pk, keyid ); + uidnode = find_next_kbnode( keyblock, PKT_USER_ID ); + + if (opt.verbose && !opt.interactive && !silent && !from_sk) + { + /* Note that we do not print this info in FROM_SK mode + * because import_secret_one already printed that. */ + log_info ("pub %s/%s %s ", + pubkey_string (pk, pkstrbuf, sizeof pkstrbuf), + keystr_from_pk(pk), datestr_from_pk(pk) ); + if (uidnode) + print_utf8_buffer (log_get_stream (), + uidnode->pkt->pkt.user_id->name, + uidnode->pkt->pkt.user_id->len ); + log_printf ("\n"); + } + + + if (!uidnode ) + { + if (!silent) + log_error( _("key %s: no user ID\n"), keystr_from_pk(pk)); + return 0; + } + + if (screener && screener (keyblock, screener_arg)) + { + log_error (_("key %s: %s\n"), keystr_from_pk (pk), + _("rejected by import screener")); + return 0; + } + + if (opt.interactive && !silent) + { + if (is_status_enabled()) + print_import_check (pk, uidnode->pkt->pkt.user_id); + merge_keys_and_selfsig (ctrl, keyblock); + tty_printf ("\n"); + show_basic_key_info (ctrl, keyblock, from_sk); + tty_printf ("\n"); + if (!cpr_get_answer_is_yes ("import.okay", + "Do you want to import this key? (y/N) ")) + return 0; + } + + /* Remove all non-self-sigs if requested. Noe that this is a NOP if + * that option has been globally set but we may also be called + * latter with the already parsed keyblock and a locally changed + * option. This is why we need to remove them here as well. */ + if ((options & IMPORT_SELF_SIGS_ONLY)) + remove_all_non_self_sigs (&keyblock, keyid); + + 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)) + { + merge_keys_and_selfsig (ctrl, keyblock); + clean_all_uids (ctrl, keyblock, + opt.verbose, (options&IMPORT_MINIMAL), NULL, NULL); + clean_all_subkeys (ctrl, keyblock, opt.verbose, KEY_CLEAN_NONE, + NULL, NULL); + } + + clear_kbnode_flags( keyblock ); + + if ((options&IMPORT_REPAIR_PKS_SUBKEY_BUG) + && fix_pks_corruption (ctrl, keyblock) + && opt.verbose) + log_info (_("key %s: PKS subkey corruption repaired\n"), + keystr_from_pk(pk)); + + if ((options & IMPORT_REPAIR_KEYS)) + key_check_all_keysigs (ctrl, 1, keyblock, 0, 0); + + if (chk_self_sigs (ctrl, keyblock, keyid, &non_self)) + return 0; /* Invalid keyblock - error already printed. */ + + /* 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 & NODE_GOOD_SELFSIG) + && !(node->flag & NODE_BAD_SELFSIG) ) + { + char *user=utf8_to_native(node->pkt->pkt.user_id->name, + node->pkt->pkt.user_id->len,0); + /* Fake a good signature status for the user id. */ + node->flag |= NODE_GOOD_SELFSIG; + log_info( _("key %s: accepted non self-signed user ID \"%s\"\n"), + keystr_from_pk(pk),user); + xfree(user); + } + } + + if (!delete_inv_parts (ctrl, keyblock, keyid, options ) ) + { + if (!silent) + { + 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; + } + + /* Get rid of deleted nodes. */ + commit_kbnode (&keyblock); + + /* Apply import filter. */ + if (import_filter.keep_uid) + { + apply_keep_uid_filter (ctrl, keyblock, import_filter.keep_uid); + commit_kbnode (&keyblock); + any_filter = 1; + } + if (import_filter.drop_sig) + { + apply_drop_sig_filter (ctrl, keyblock, import_filter.drop_sig); + commit_kbnode (&keyblock); + any_filter = 1; + } + + /* If we ran any filter we need to check that at least one user id + * is left in the keyring. Note that we do not use log_error in + * this case. */ + if (any_filter && !any_uid_left (keyblock)) + { + if (!opt.quiet ) + log_info ( _("key %s: no valid user IDs\n"), keystr_from_pk (pk)); + stats->no_user_id++; + return 0; + } + + /* The keyblock is valid and ready for real import. */ + if (r_valid) + *r_valid = 1; + + /* Show the key in the form it is merged or inserted. We skip this + * if "import-export" is also active without --armor or the output + * file has explicily been given. */ + if ((options & IMPORT_SHOW) + && !((options & IMPORT_EXPORT) && !opt.armor && !opt.outfile)) + { + merge_keys_and_selfsig (ctrl, keyblock); + merge_keys_done = 1; + /* Note that we do not want to show the validity because the key + * has not yet imported. */ + list_keyblock_direct (ctrl, keyblock, from_sk, 0, + opt.fingerprint || opt.with_fingerprint, 1); + es_fflush (es_stdout); + } + + /* Write the keyblock to the output and do not actually import. */ + if ((options & IMPORT_EXPORT)) + { + if (!merge_keys_done) + { + merge_keys_and_selfsig (ctrl, keyblock); + merge_keys_done = 1; + } + err = write_keyblock_to_output (keyblock, opt.armor, opt.export_options); + goto leave; + } + + if (opt.dry_run || (options & IMPORT_DRY_RUN)) + goto leave; + + /* Do we have this key already in one of our pubrings ? */ + err = get_keyblock_byfprint_fast (&keyblock_orig, &hd, + fpr2, fpr2len, 1/*locked*/); + if ((err + && gpg_err_code (err) != GPG_ERR_NO_PUBKEY + && gpg_err_code (err) != GPG_ERR_UNUSABLE_PUBKEY) + || !hd) + { + /* The !hd above is to catch a misbehaving function which + * returns NO_PUBKEY for failing to allocate a handle. */ + if (!silent) + log_error (_("key %s: public key not found: %s\n"), + keystr(keyid), gpg_strerror (err)); + } + else if (err && (opt.import_options&IMPORT_MERGE_ONLY) ) + { + if (opt.verbose && !silent ) + log_info( _("key %s: new key - skipped\n"), keystr(keyid)); + err = 0; + stats->skipped_new_keys++; + } + else if (err) /* Insert this key. */ + { + /* Note: ERR can only be NO_PUBKEY or UNUSABLE_PUBKEY. */ + int n_sigs_cleaned, n_uids_cleaned; + + err = keydb_locate_writable (hd); + if (err) + { + log_error (_("no writable keyring found: %s\n"), gpg_strerror (err)); + err = gpg_error (GPG_ERR_GENERAL); + goto leave; + } + if (opt.verbose > 1 ) + log_info (_("writing to '%s'\n"), keydb_get_resource_name (hd) ); + + if ((options & IMPORT_CLEAN)) + { + merge_keys_and_selfsig (ctrl, keyblock); + clean_all_uids (ctrl, keyblock, opt.verbose, (options&IMPORT_MINIMAL), + &n_uids_cleaned,&n_sigs_cleaned); + clean_all_subkeys (ctrl, keyblock, opt.verbose, KEY_CLEAN_NONE, + NULL, NULL); + } + + /* Unless we are in restore mode apply meta data to the + * keyblock. Note that this will never change the first packet + * and thus the address of KEYBLOCK won't change. */ + if ( !(options & IMPORT_RESTORE) ) + { + err = insert_key_origin (keyblock, origin, url); + if (err) + { + log_error ("insert_key_origin failed: %s\n", gpg_strerror (err)); + err = gpg_error (GPG_ERR_GENERAL); + goto leave; + } + } + + err = keydb_insert_keyblock (hd, keyblock ); + if (err) + log_error (_("error writing keyring '%s': %s\n"), + keydb_get_resource_name (hd), gpg_strerror (err)); + else if (!(opt.import_options & IMPORT_KEEP_OWNERTTRUST)) + { + /* 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 and by + importing and locally exported key. */ + + clear_ownertrusts (ctrl, pk); + if (non_self) + revalidation_mark (ctrl); + } + + /* Release the handle and thus unlock the keyring asap. */ + keydb_release (hd); + hd = NULL; + + /* We are ready. */ + if (!err && !opt.quiet && !silent) + { + char *p = get_user_id_byfpr_native (ctrl, fpr2); + log_info (_("key %s: public key \"%s\" imported\n"), + keystr(keyid), p); + xfree(p); + } + if (!err && is_status_enabled()) + { + char *us = get_long_user_id_string (ctrl, keyid); + write_status_text( STATUS_IMPORTED, us ); + xfree(us); + print_import_ok (pk, 1); + } + if (!err) + { + stats->imported++; + new_key = 1; + } + } + else /* Key already exists - merge. */ + { + int n_uids, n_sigs, n_subk, n_sigs_cleaned, n_uids_cleaned; + u32 curtime = make_timestamp (); + + /* Compare the original against the new key; just to be sure nothing + * weird is going on */ + if (cmp_public_keys (keyblock_orig->pkt->pkt.public_key, pk)) + { + if (!silent) + log_error( _("key %s: doesn't match our copy\n"),keystr(keyid)); + goto leave; + } + + /* Make sure the original direct key sigs are all sane. */ + n_sigs_cleaned = fix_bad_direct_key_sigs (ctrl, keyblock_orig, keyid); + if (n_sigs_cleaned) + commit_kbnode (&keyblock_orig); + + /* Try to merge KEYBLOCK into KEYBLOCK_ORIG. */ + clear_kbnode_flags( keyblock_orig ); + clear_kbnode_flags( keyblock ); + n_uids = n_sigs = n_subk = n_uids_cleaned = 0; + err = merge_blocks (ctrl, options, keyblock_orig, keyblock, keyid, + curtime, origin, url, + &n_uids, &n_sigs, &n_subk ); + if (err) + goto leave; + + /* Clean the final keyblock again if requested. we can't do + * this if only self-signatures are imported; see bug #4628. */ + if ((options & IMPORT_CLEAN) + && !(options & IMPORT_SELF_SIGS_ONLY)) + { + merge_keys_and_selfsig (ctrl, keyblock_orig); + clean_all_uids (ctrl, keyblock_orig, opt.verbose, + (options&IMPORT_MINIMAL), + &n_uids_cleaned,&n_sigs_cleaned); + clean_all_subkeys (ctrl, keyblock_orig, opt.verbose, KEY_CLEAN_NONE, + NULL, NULL); + } + + if (n_uids || n_sigs || n_subk || n_sigs_cleaned || n_uids_cleaned) + { + /* Unless we are in restore mode apply meta data to the + * keyblock. Note that this will never change the first packet + * and thus the address of KEYBLOCK won't change. */ + if ( !(options & IMPORT_RESTORE) ) + { + err = update_key_origin (keyblock_orig, curtime, origin, url); + if (err) + { + log_error ("update_key_origin failed: %s\n", + gpg_strerror (err)); + goto leave; + } + } + + mod_key = 1; + /* KEYBLOCK_ORIG has been updated; write */ + err = keydb_update_keyblock (ctrl, hd, keyblock_orig); + if (err) + log_error (_("error writing keyring '%s': %s\n"), + keydb_get_resource_name (hd), gpg_strerror (err)); + else if (non_self) + revalidation_mark (ctrl); + + /* Release the handle and thus unlock the keyring asap. */ + keydb_release (hd); + hd = NULL; + + /* We are ready. Print and update stats if we got no error. + * An error here comes from writing the keyblock and thus + * very likely means that no update happened. */ + if (!err && !opt.quiet && !silent) + { + char *p = get_user_id_byfpr_native (ctrl, fpr2); + 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); + } + + if (!err) + { + 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 () && !silent) + print_import_ok (pk, ((n_uids?2:0)|(n_sigs?4:0)|(n_subk?8:0))); + } + } + else + { + /* Release the handle and thus unlock the keyring asap. */ + keydb_release (hd); + hd = NULL; + + /* FIXME: We do not track the time we last checked a key for + * updates. To do this we would need to rewrite even the + * keys which have no changes. Adding this would be useful + * for the automatic update of expired keys via the WKD in + * case the WKD still carries the expired key. See + * get_best_pubkey_byname. */ + same_key = 1; + if (is_status_enabled ()) + print_import_ok (pk, 0); + + if (!opt.quiet && !silent) + { + char *p = get_user_id_byfpr_native (ctrl, fpr2); + log_info( _("key %s: \"%s\" not changed\n"),keystr(keyid),p); + xfree(p); + } + + stats->unchanged++; + } + } + + leave: + keydb_release (hd); + if (mod_key || new_key || same_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 + fingerprinting at all, and we must hope the user ID on the + keys are useful. Note that we need to do this for new + keys, merged keys and even for unchanged keys. This is + required because for example the --auto-key-locate feature + may import an already imported key and needs to know the + fingerprint of the key in all cases. */ + if (fpr) + { + /* Note that we need to compare against 0 here because + COUNT gets only incremented after returning from this + function. */ + if (!stats->count) + { + xfree (*fpr); + *fpr = fingerprint_from_pk (pk, NULL, fpr_len); + } + else if (origin != KEYORG_WKD) + { + xfree (*fpr); + *fpr = NULL; + } + } + } + + /* 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 (ctrl, keyblock_orig); + if (!from_sk && have_secret_key_with_kid (keyid)) + check_prefs (ctrl, keyblock_orig); + } + else if (new_key) + { + revocation_present (ctrl, keyblock); + if (!from_sk && have_secret_key_with_kid (keyid)) + check_prefs (ctrl, keyblock); + } + + release_kbnode( keyblock_orig ); + + return err; +} + + +/* Wrapper around import_one_real to retry the import in some cases. */ +static gpg_error_t +import_one (ctrl_t ctrl, + kbnode_t keyblock, struct import_stats_s *stats, + unsigned char **fpr, size_t *fpr_len, unsigned int options, + int from_sk, int silent, + import_screener_t screener, void *screener_arg, + int origin, const char *url, int *r_valid) +{ + gpg_error_t err; + + err = import_one_real (ctrl, keyblock, stats, fpr, fpr_len, options, + from_sk, silent, screener, screener_arg, + origin, url, r_valid); + if (gpg_err_code (err) == GPG_ERR_TOO_LARGE + && gpg_err_source (err) == GPG_ERR_SOURCE_KEYBOX + && ((options & (IMPORT_SELF_SIGS_ONLY | IMPORT_CLEAN)) + != (IMPORT_SELF_SIGS_ONLY | IMPORT_CLEAN))) + { + /* We hit the maximum image length. Ask the wrapper to do + * everything again but this time with some extra options. */ + u32 keyid[2]; + + keyid_from_pk (keyblock->pkt->pkt.public_key, keyid); + log_info ("key %s: keyblock too large, retrying with self-sigs-only\n", + keystr (keyid)); + options |= IMPORT_SELF_SIGS_ONLY | IMPORT_CLEAN; + err = import_one_real (ctrl, keyblock, stats, fpr, fpr_len, options, + from_sk, silent, screener, screener_arg, + origin, url, r_valid); + } + return err; +} + + +/* Transfer all the secret keys in SEC_KEYBLOCK to the gpg-agent. The + * function prints diagnostics and returns an error code. If BATCH is + * true the secret keys are stored by gpg-agent in the transfer format + * (i.e. no re-protection and aksing for passphrases). If ONLY_MARKED + * is set, only those nodes with flag NODE_TRANSFER_SECKEY are + * processed. */ +gpg_error_t +transfer_secret_keys (ctrl_t ctrl, struct import_stats_s *stats, + kbnode_t sec_keyblock, int batch, int force, + int only_marked) +{ + gpg_error_t err = 0; + void *kek = NULL; + size_t keklen; + kbnode_t ctx = NULL; + kbnode_t node; + PKT_public_key *main_pk, *pk; + struct seckey_info *ski; + int nskey; + membuf_t mbuf; + int i, j; + void *format_args[2*PUBKEY_MAX_NSKEY]; + gcry_sexp_t skey, prot, tmpsexp; + gcry_sexp_t curve = NULL; + unsigned char *transferkey = NULL; + size_t transferkeylen; + gcry_cipher_hd_t cipherhd = NULL; + unsigned char *wrappedkey = NULL; + size_t wrappedkeylen; + char *cache_nonce = NULL; + int stub_key_skipped = 0; + + /* Get the current KEK. */ + err = agent_keywrap_key (ctrl, 0, &kek, &keklen); + if (err) + { + log_error ("error getting the KEK: %s\n", gpg_strerror (err)); + goto leave; + } + + /* Prepare a cipher context. */ + err = gcry_cipher_open (&cipherhd, GCRY_CIPHER_AES128, + GCRY_CIPHER_MODE_AESWRAP, 0); + if (!err) + err = gcry_cipher_setkey (cipherhd, kek, keklen); + if (err) + goto leave; + xfree (kek); + kek = NULL; + + /* Note: We need to use walk_kbnode so that we skip nodes which are + * marked as deleted. */ + main_pk = NULL; + while ((node = walk_kbnode (sec_keyblock, &ctx, 0))) + { + if (node->pkt->pkttype != PKT_SECRET_KEY + && node->pkt->pkttype != PKT_SECRET_SUBKEY) + continue; + if (only_marked && !(node->flag & NODE_TRANSFER_SECKEY)) + continue; + pk = node->pkt->pkt.public_key; + if (!main_pk) + main_pk = pk; + + /* Make sure the keyids are available. */ + keyid_from_pk (pk, NULL); + if (node->pkt->pkttype == PKT_SECRET_KEY) + { + pk->main_keyid[0] = pk->keyid[0]; + pk->main_keyid[1] = pk->keyid[1]; + } + else + { + pk->main_keyid[0] = main_pk->keyid[0]; + pk->main_keyid[1] = main_pk->keyid[1]; + } + + + ski = pk->seckey_info; + if (!ski) + BUG (); + + if (stats) + { + stats->count++; + stats->secret_read++; + } + + /* We ignore stub keys. The way we handle them in other parts + of the code is by asking the agent whether any secret key is + available for a given keyblock and then concluding that we + have a secret key; all secret (sub)keys of the keyblock the + agent does not know of are then stub keys. This works also + for card stub keys. The learn command or the card-status + command may be used to check with the agent whether a card + has been inserted and a stub key is in turn generated by the + agent. */ + if (ski->s2k.mode == 1001 || ski->s2k.mode == 1002) + { + stub_key_skipped = 1; + continue; + } + + /* Convert our internal secret key object into an S-expression. */ + nskey = pubkey_get_nskey (pk->pubkey_algo); + if (!nskey || nskey > PUBKEY_MAX_NSKEY) + { + err = gpg_error (GPG_ERR_BAD_SECKEY); + log_error ("internal error: %s\n", gpg_strerror (err)); + goto leave; + } + + init_membuf (&mbuf, 50); + put_membuf_str (&mbuf, "(skey"); + if (pk->pubkey_algo == PUBKEY_ALGO_ECDSA + || pk->pubkey_algo == PUBKEY_ALGO_EDDSA + || pk->pubkey_algo == PUBKEY_ALGO_ECDH) + { + /* The ECC case. */ + char *curvestr = openpgp_oid_to_str (pk->pkey[0]); + if (!curvestr) + err = gpg_error_from_syserror (); + else + { + const char *curvename = openpgp_oid_to_curve (curvestr, 1); + gcry_sexp_release (curve); + err = gcry_sexp_build (&curve, NULL, "(curve %s)", + curvename?curvename:curvestr); + xfree (curvestr); + if (!err) + { + j = 0; + /* Append the public key element Q. */ + put_membuf_str (&mbuf, " _ %m"); + format_args[j++] = pk->pkey + 1; + + /* Append the secret key element D. For ECDH we + skip PKEY[2] because this holds the KEK which is + not needed by gpg-agent. */ + i = pk->pubkey_algo == PUBKEY_ALGO_ECDH? 3 : 2; + if (gcry_mpi_get_flag (pk->pkey[i], GCRYMPI_FLAG_USER1)) + put_membuf_str (&mbuf, " e %m"); + else + put_membuf_str (&mbuf, " _ %m"); + format_args[j++] = pk->pkey + i; + } + } + } + else + { + /* Standard case for the old (non-ECC) algorithms. */ + for (i=j=0; i < nskey; i++) + { + if (!pk->pkey[i]) + continue; /* Protected keys only have NPKEY+1 elements. */ + + if (gcry_mpi_get_flag (pk->pkey[i], GCRYMPI_FLAG_USER1)) + put_membuf_str (&mbuf, " e %m"); + else + put_membuf_str (&mbuf, " _ %m"); + format_args[j++] = pk->pkey + i; + } + } + put_membuf_str (&mbuf, ")"); + put_membuf (&mbuf, "", 1); + if (err) + xfree (get_membuf (&mbuf, NULL)); + else + { + char *format = get_membuf (&mbuf, NULL); + if (!format) + err = gpg_error_from_syserror (); + else + err = gcry_sexp_build_array (&skey, NULL, format, format_args); + xfree (format); + } + if (err) + { + log_error ("error building skey array: %s\n", gpg_strerror (err)); + goto leave; + } + + if (ski->is_protected) + { + char countbuf[35]; + + /* Note that the IVLEN may be zero if we are working on a + dummy key. We can't express that in an S-expression and + thus we send dummy data for the IV. */ + snprintf (countbuf, sizeof countbuf, "%lu", + (unsigned long)ski->s2k.count); + err = gcry_sexp_build + (&prot, NULL, + " (protection %s %s %b %d %s %b %s)\n", + ski->sha1chk? "sha1":"sum", + openpgp_cipher_algo_name (ski->algo), + ski->ivlen? (int)ski->ivlen:1, + ski->ivlen? ski->iv: (const unsigned char*)"X", + ski->s2k.mode, + openpgp_md_algo_name (ski->s2k.hash_algo), + (int)sizeof (ski->s2k.salt), ski->s2k.salt, + countbuf); + } + else + err = gcry_sexp_build (&prot, NULL, " (protection none)\n"); + + tmpsexp = NULL; + xfree (transferkey); + transferkey = NULL; + if (!err) + err = gcry_sexp_build (&tmpsexp, NULL, + "(openpgp-private-key\n" + " (version %d)\n" + " (algo %s)\n" + " %S%S\n" + " (csum %d)\n" + " %S)\n", + pk->version, + openpgp_pk_algo_name (pk->pubkey_algo), + curve, skey, + (int)(unsigned long)ski->csum, prot); + gcry_sexp_release (skey); + gcry_sexp_release (prot); + if (!err) + err = make_canon_sexp_pad (tmpsexp, 1, &transferkey, &transferkeylen); + gcry_sexp_release (tmpsexp); + if (err) + { + log_error ("error building transfer key: %s\n", gpg_strerror (err)); + goto leave; + } + + /* Wrap the key. */ + wrappedkeylen = transferkeylen + 8; + xfree (wrappedkey); + wrappedkey = xtrymalloc (wrappedkeylen); + if (!wrappedkey) + err = gpg_error_from_syserror (); + else + err = gcry_cipher_encrypt (cipherhd, wrappedkey, wrappedkeylen, + transferkey, transferkeylen); + if (err) + goto leave; + xfree (transferkey); + transferkey = NULL; + + /* Send the wrapped key to the agent. */ + { + char *desc = gpg_format_keydesc (ctrl, pk, FORMAT_KEYDESC_IMPORT, 1); + err = agent_import_key (ctrl, desc, &cache_nonce, + wrappedkey, wrappedkeylen, batch, force, + pk->keyid, pk->main_keyid, pk->pubkey_algo, + pk->timestamp); + xfree (desc); + } + if (!err) + { + if (opt.verbose) + log_info (_("key %s: secret key imported\n"), + keystr_from_pk_with_sub (main_pk, pk)); + if (stats) + stats->secret_imported++; + } + else if ( gpg_err_code (err) == GPG_ERR_EEXIST ) + { + if (opt.verbose) + log_info (_("key %s: secret key already exists\n"), + keystr_from_pk_with_sub (main_pk, pk)); + err = 0; + if (stats) + stats->secret_dups++; + } + else + { + log_error (_("key %s: error sending to agent: %s\n"), + keystr_from_pk_with_sub (main_pk, pk), + gpg_strerror (err)); + if (gpg_err_code (err) == GPG_ERR_CANCELED + || gpg_err_code (err) == GPG_ERR_FULLY_CANCELED) + break; /* Don't try the other subkeys. */ + } + } + + if (!err && stub_key_skipped) + /* We need to notify user how to migrate stub keys. */ + err = gpg_error (GPG_ERR_NOT_PROCESSED); + + leave: + gcry_sexp_release (curve); + xfree (cache_nonce); + xfree (wrappedkey); + xfree (transferkey); + gcry_cipher_close (cipherhd); + xfree (kek); + return err; +} + + +/* Walk a secret keyblock and produce a public keyblock out of it. + * Returns a new node or NULL on error. Modifies the tag field of the + * nodes. */ +static kbnode_t +sec_to_pub_keyblock (kbnode_t sec_keyblock) +{ + kbnode_t pub_keyblock = NULL; + kbnode_t ctx = NULL; + kbnode_t secnode, pubnode; + kbnode_t lastnode = NULL; + unsigned int tag = 0; + + /* Set a tag to all nodes. */ + for (secnode = sec_keyblock; secnode; secnode = secnode->next) + secnode->tag = ++tag; + + /* Copy. */ + while ((secnode = walk_kbnode (sec_keyblock, &ctx, 0))) + { + if (secnode->pkt->pkttype == PKT_SECRET_KEY + || secnode->pkt->pkttype == PKT_SECRET_SUBKEY) + { + /* Make a public key. */ + PACKET *pkt; + PKT_public_key *pk; + + pkt = xtrycalloc (1, sizeof *pkt); + pk = pkt? copy_public_key (NULL, secnode->pkt->pkt.public_key): NULL; + if (!pk) + { + xfree (pkt); + release_kbnode (pub_keyblock); + return NULL; + } + if (secnode->pkt->pkttype == PKT_SECRET_KEY) + pkt->pkttype = PKT_PUBLIC_KEY; + else + pkt->pkttype = PKT_PUBLIC_SUBKEY; + pkt->pkt.public_key = pk; + + pubnode = new_kbnode (pkt); + } + else + { + pubnode = clone_kbnode (secnode); + } + pubnode->tag = secnode->tag; + + if (!pub_keyblock) + pub_keyblock = lastnode = pubnode; + else + { + lastnode->next = pubnode; + lastnode = pubnode; + } + } + + return pub_keyblock; +} + + +/* Delete all notes in the keyblock at R_KEYBLOCK which are not in + * PUB_KEYBLOCK. Modifies the tags of both keyblock's nodes. */ +static gpg_error_t +resync_sec_with_pub_keyblock (kbnode_t *r_keyblock, kbnode_t pub_keyblock, + kbnode_t *r_removedsecs) +{ + kbnode_t sec_keyblock = *r_keyblock; + kbnode_t node, prevnode; + unsigned int *taglist; + unsigned int ntaglist, n; + kbnode_t attic = NULL; + kbnode_t *attic_head = &attic; + + /* Collect all tags in an array for faster searching. */ + for (ntaglist = 0, node = pub_keyblock; node; node = node->next) + ntaglist++; + taglist = xtrycalloc (ntaglist, sizeof *taglist); + if (!taglist) + return gpg_error_from_syserror (); + for (ntaglist = 0, node = pub_keyblock; node; node = node->next) + taglist[ntaglist++] = node->tag; + + /* Walks over the secret keyblock and delete all nodes which are not + * in the tag list. Those nodes have been deleted in the + * pub_keyblock. Sequential search is a bit lazy and could be + * optimized by sorting and bsearch; however secret keyrings are + * short and there are easier ways to DoS the import. */ + again: + for (prevnode=NULL, node=sec_keyblock; node; prevnode=node, node=node->next) + { + for (n=0; n < ntaglist; n++) + if (taglist[n] == node->tag) + break; + if (n == ntaglist) /* Not in public keyblock. */ + { + if (node->pkt->pkttype == PKT_SECRET_KEY + || node->pkt->pkttype == PKT_SECRET_SUBKEY) + { + if (!prevnode) + sec_keyblock = node->next; + else + prevnode->next = node->next; + node->next = NULL; + *attic_head = node; + attic_head = &node->next; + goto again; /* That's lame; I know. */ + } + else + delete_kbnode (node); + } + } + + xfree (taglist); + + /* Commit the as deleted marked nodes and return the possibly + * modified keyblock and a list of removed secret key nodes. */ + commit_kbnode (&sec_keyblock); + *r_keyblock = sec_keyblock; + *r_removedsecs = attic; + return 0; +} + + +/* Helper for import_secret_one. */ +static gpg_error_t +do_transfer (ctrl_t ctrl, kbnode_t keyblock, PKT_public_key *pk, + struct import_stats_s *stats, int batch, int only_marked) + +{ + gpg_error_t err; + struct import_stats_s subkey_stats = {0}; + + err = transfer_secret_keys (ctrl, &subkey_stats, keyblock, + batch, 0, only_marked); + if (gpg_err_code (err) == GPG_ERR_NOT_PROCESSED) + { + /* TRANSLATORS: For a smartcard, each private key on host has a + * reference (stub) to a smartcard and actual private key data + * is stored on the card. A single smartcard can have up to + * three private key data. Importing private key stub is always + * skipped in 2.1, and it returns GPG_ERR_NOT_PROCESSED. + * Instead, user should be suggested to run 'gpg --card-status', + * then, references to a card will be automatically created + * again. */ + log_info (_("To migrate '%s', with each smartcard, " + "run: %s\n"), "secring.gpg", "gpg --card-status"); + err = 0; + } + + if (!err) + { + int status = 16; + + if (!opt.quiet) + log_info (_("key %s: secret key imported\n"), keystr_from_pk (pk)); + if (subkey_stats.secret_imported) + { + status |= 1; + stats->secret_imported += 1; + } + if (subkey_stats.secret_dups) + stats->secret_dups += 1; + + if (is_status_enabled ()) + print_import_ok (pk, status); + } + + return err; +} + + +/* If the secret keys (main or subkey) in SECKEYS have a corresponding + * public key in the public key described by (FPR,FPRLEN) import these + * parts. + */ +static gpg_error_t +import_matching_seckeys (ctrl_t ctrl, kbnode_t seckeys, + const byte *mainfpr, size_t mainfprlen, + struct import_stats_s *stats, int batch) +{ + gpg_error_t err; + kbnode_t pub_keyblock = NULL; + kbnode_t node; + struct { byte fpr[MAX_FINGERPRINT_LEN]; size_t fprlen; } *fprlist = NULL; + size_t n, nfprlist; + byte fpr[MAX_FINGERPRINT_LEN]; + size_t fprlen; + PKT_public_key *pk; + + /* Get the entire public key block from our keystore and put all its + * fingerprints into an array. */ + err = get_pubkey_byfprint (ctrl, NULL, &pub_keyblock, mainfpr, mainfprlen); + if (err) + goto leave; + log_assert (pub_keyblock && pub_keyblock->pkt->pkttype == PKT_PUBLIC_KEY); + pk = pub_keyblock->pkt->pkt.public_key; + + for (nfprlist = 0, node = pub_keyblock; node; node = node->next) + if (node->pkt->pkttype == PKT_PUBLIC_KEY + || node->pkt->pkttype == PKT_PUBLIC_SUBKEY) + nfprlist++; + log_assert (nfprlist); + fprlist = xtrycalloc (nfprlist, sizeof *fprlist); + if (!fprlist) + { + err = gpg_error_from_syserror (); + goto leave; + } + for (n = 0, node = pub_keyblock; node; node = node->next) + if (node->pkt->pkttype == PKT_PUBLIC_KEY + || node->pkt->pkttype == PKT_PUBLIC_SUBKEY) + { + fingerprint_from_pk (node->pkt->pkt.public_key, + fprlist[n].fpr, &fprlist[n].fprlen); + n++; + } + log_assert (n == nfprlist); + + /* for (n=0; n < nfprlist; n++) */ + /* log_printhex (fprlist[n].fpr, fprlist[n].fprlen, "pubkey %zu:", n); */ + + /* Mark all secret keys which have a matching public key part in + * PUB_KEYBLOCK. */ + for (node = seckeys; node; node = node->next) + { + if (node->pkt->pkttype != PKT_SECRET_KEY + && node->pkt->pkttype != PKT_SECRET_SUBKEY) + continue; /* Should not happen. */ + fingerprint_from_pk (node->pkt->pkt.public_key, fpr, &fprlen); + node->flag &= ~NODE_TRANSFER_SECKEY; + for (n=0; n < nfprlist; n++) + if (fprlist[n].fprlen == fprlen && !memcmp (fprlist[n].fpr,fpr,fprlen)) + { + node->flag |= NODE_TRANSFER_SECKEY; + /* log_debug ("found matching seckey\n"); */ + break; + } + } + + /* Transfer all marked keys. */ + err = do_transfer (ctrl, seckeys, pk, stats, batch, 1); + + leave: + xfree (fprlist); + release_kbnode (pub_keyblock); + return err; +} + + +/* Import function for a single secret keyblock. 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 + * accidentally and thereby tampering with the trust calculation. + * + * Ownership of KEYBLOCK is transferred to this function! + * + * If R_SECATTIC is not null the last special sec_keyblock is stored + * there. + */ +static gpg_error_t +import_secret_one (ctrl_t ctrl, kbnode_t keyblock, + struct import_stats_s *stats, int batch, + unsigned int options, int for_migration, + import_screener_t screener, void *screener_arg, + kbnode_t *r_secattic) +{ + PKT_public_key *pk; + struct seckey_info *ski; + kbnode_t node, uidnode; + u32 keyid[2]; + gpg_error_t err = 0; + int nr_prev; + kbnode_t pub_keyblock; + kbnode_t attic = NULL; + byte fpr[MAX_FINGERPRINT_LEN]; + size_t fprlen; + char pkstrbuf[PUBKEY_STRING_SIZE]; + + /* Get the key and print some info about it */ + node = find_kbnode (keyblock, PKT_SECRET_KEY); + if (!node) + BUG (); + + pk = node->pkt->pkt.public_key; + + fingerprint_from_pk (pk, fpr, &fprlen); + keyid_from_pk (pk, keyid); + uidnode = find_next_kbnode (keyblock, PKT_USER_ID); + + if (screener && screener (keyblock, screener_arg)) + { + log_error (_("secret key %s: %s\n"), keystr_from_pk (pk), + _("rejected by import screener")); + release_kbnode (keyblock); + return 0; + } + + if (opt.verbose && !for_migration) + { + log_info ("sec %s/%s %s ", + pubkey_string (pk, pkstrbuf, sizeof pkstrbuf), + keystr_from_pk (pk), datestr_from_pk (pk)); + if (uidnode) + print_utf8_buffer (log_get_stream (), uidnode->pkt->pkt.user_id->name, + uidnode->pkt->pkt.user_id->len); + log_printf ("\n"); + } + stats->secret_read++; + + if ((options & IMPORT_NO_SECKEY)) + { + if (!for_migration) + log_error (_("importing secret keys not allowed\n")); + release_kbnode (keyblock); + return 0; + } + + if (!uidnode) + { + if (!for_migration) + log_error( _("key %s: no user ID\n"), keystr_from_pk (pk)); + release_kbnode (keyblock); + return 0; + } + + ski = pk->seckey_info; + if (!ski) + { + /* Actually an internal error. */ + log_error ("key %s: secret key info missing\n", keystr_from_pk (pk)); + release_kbnode (keyblock); + return 0; + } + + /* A quick check to not import keys with an invalid protection + cipher algorithm (only checks the primary key, though). */ + if (ski->algo > 110) + { + if (!for_migration) + log_error (_("key %s: secret key with invalid cipher %d" + " - skipped\n"), keystr_from_pk (pk), ski->algo); + release_kbnode (keyblock); + return 0; + } + +#ifdef ENABLE_SELINUX_HACKS + if (1) + { + /* We don't allow importing 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")); + release_kbnode (keyblock); + return 0; + } +#endif + + clear_kbnode_flags (keyblock); + + nr_prev = stats->skipped_new_keys; + + /* Make a public key out of the key. */ + pub_keyblock = sec_to_pub_keyblock (keyblock); + if (!pub_keyblock) + { + err = gpg_error_from_syserror (); + log_error ("key %s: failed to create public key from secret key\n", + keystr_from_pk (pk)); + } + else + { + int valid; + + /* Note that this outputs an IMPORT_OK status message for the + public key block, and below we will output another one for + the secret keys. FIXME? */ + import_one (ctrl, pub_keyblock, stats, + NULL, NULL, options, 1, for_migration, + screener, screener_arg, 0, NULL, &valid); + + /* The secret keyblock may not have nodes which are deleted in + * the public keyblock. Otherwise we would import just the + * secret key without having the public key. That would be + * surprising and clutters our private-keys-v1.d. */ + err = resync_sec_with_pub_keyblock (&keyblock, pub_keyblock, &attic); + if (err) + goto leave; + + if (!valid) + { + /* If the block was not valid the primary key is left in the + * original keyblock because we require that for the first + * node. Move it to ATTIC. */ + if (keyblock && keyblock->pkt->pkttype == PKT_SECRET_KEY) + { + node = keyblock; + keyblock = node->next; + node->next = NULL; + if (attic) + { + node->next = attic; + attic = node; + } + else + attic = node; + } + + /* Try to import the secret key iff we have a public key. */ + if (attic && !(opt.dry_run || (options & IMPORT_DRY_RUN))) + err = import_matching_seckeys (ctrl, attic, fpr, fprlen, + stats, batch); + else + err = gpg_error (GPG_ERR_NO_SECKEY); + goto leave; + } + + /* log_debug ("attic is:\n"); */ + /* dump_kbnode (attic); */ + + /* Proceed with the valid parts of PUBKEYBLOCK. */ + + /* At least we cancel the secret key import when the public key + import was skipped due to MERGE_ONLY option and a new + key. */ + if (!(opt.dry_run || (options & IMPORT_DRY_RUN)) + && stats->skipped_new_keys <= nr_prev) + { + /* Read the keyblock again to get the effects of a merge for + * the public key. */ + err = get_pubkey_byfprint (ctrl, NULL, &node, fpr, fprlen); + if (err || !node) + log_error ("key %s: failed to re-lookup public key: %s\n", + keystr_from_pk (pk), gpg_strerror (err)); + else + { + err = do_transfer (ctrl, keyblock, pk, stats, batch, 0); + if (!err) + check_prefs (ctrl, node); + release_kbnode (node); + + if (!err && attic) + { + /* Try to import invalid subkeys. This can be the + * case if the primary secret key was imported due + * to --allow-non-selfsigned-uid. */ + err = import_matching_seckeys (ctrl, attic, fpr, fprlen, + stats, batch); + } + + } + } + } + + leave: + release_kbnode (keyblock); + release_kbnode (pub_keyblock); + if (r_secattic) + *r_secattic = attic; + else + release_kbnode (attic); + return err; +} + + + +/* Return the recocation reason from signature SIG. If no revocation + * reason is availabale 0 is returned, in other cases the reason + * (0..255). If R_REASON is not NULL a malloced textual + * representation of the code is stored there. If R_COMMENT is not + * NULL the comment from the reason is stored there and its length at + * R_COMMENTLEN. Note that the value at R_COMMENT is not filtered but + * user supplied data in UTF8; thus it needs to be escaped for display + * purposes. Both return values are either NULL or a malloced + * string/buffer. */ +int +get_revocation_reason (PKT_signature *sig, char **r_reason, + char **r_comment, size_t *r_commentlen) +{ + int reason_seq = 0; + size_t reason_n; + const byte *reason_p; + char reason_code_buf[20]; + const char *reason_text = NULL; + int reason_code = 0; + + if (r_reason) + *r_reason = NULL; + if (r_comment) + *r_comment = NULL; + + /* Skip over empty reason packets. */ + while ((reason_p = enum_sig_subpkt (sig->hashed, SIGSUBPKT_REVOC_REASON, + &reason_n, &reason_seq, NULL)) + && !reason_n) + ; + if (reason_p) + { + reason_code = *reason_p; + reason_n--; reason_p++; + switch (reason_code) + { + case 0x00: reason_text = _("No reason specified"); break; + case 0x01: reason_text = _("Key is superseded"); break; + case 0x02: reason_text = _("Key has been compromised"); break; + case 0x03: reason_text = _("Key is no longer used"); break; + case 0x20: reason_text = _("User ID is no longer valid"); break; + default: + snprintf (reason_code_buf, sizeof reason_code_buf, + "code=%02x", reason_code); + reason_text = reason_code_buf; + break; + } + + if (r_reason) + *r_reason = xstrdup (reason_text); + + if (r_comment && reason_n) + { + *r_comment = xmalloc (reason_n); + memcpy (*r_comment, reason_p, reason_n); + *r_commentlen = reason_n; + } + } + + return reason_code; +} + + +/* List the recocation signature as a "rvs" record. SIGRC shows the + * character from the signature verification or 0 if no public key was + * found. */ +static void +list_standalone_revocation (ctrl_t ctrl, PKT_signature *sig, int sigrc) +{ + char *siguid = NULL; + size_t siguidlen = 0; + char *issuer_fpr = NULL; + int reason_code = 0; + char *reason_text = NULL; + char *reason_comment = NULL; + size_t reason_commentlen; + + if (sigrc != '%' && sigrc != '?' && !opt.fast_list_mode) + { + int nouid; + siguid = get_user_id (ctrl, sig->keyid, &siguidlen, &nouid); + if (nouid) + sigrc = '?'; + } + + reason_code = get_revocation_reason (sig, &reason_text, + &reason_comment, &reason_commentlen); + + if (opt.with_colons) + { + es_fputs ("rvs:", es_stdout); + if (sigrc) + es_putc (sigrc, es_stdout); + es_fprintf (es_stdout, "::%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 (siguid) + es_write_sanitized (es_stdout, siguid, siguidlen, ":", NULL); + + es_fprintf (es_stdout, ":%02x%c", sig->sig_class, + sig->flags.exportable ? 'x' : 'l'); + if (reason_text) + es_fprintf (es_stdout, ",%02x", reason_code); + es_fputs ("::", es_stdout); + + if ((issuer_fpr = issuer_fpr_string (sig))) + es_fputs (issuer_fpr, es_stdout); + + es_fprintf (es_stdout, ":::%d:", sig->digest_algo); + + if (reason_comment) + { + es_fputs ("::::", es_stdout); + es_write_sanitized (es_stdout, reason_comment, reason_commentlen, + ":", NULL); + es_putc (':', es_stdout); + } + es_putc ('\n', es_stdout); + + if (opt.show_subpackets) + print_subpackets_colon (sig); + } + else /* Human readable. */ + { + es_fputs ("rvs", es_stdout); + es_fprintf (es_stdout, "%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 (siguid) + { + es_fprintf (es_stdout, " "); + print_utf8_buffer (es_stdout, siguid, siguidlen); + } + es_putc ('\n', es_stdout); + + 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); + + if (reason_text) + { + es_fprintf (es_stdout, " %s%s\n", + _("reason for revocation: "), reason_text); + if (reason_comment) + { + const byte *s, *s_lf; + size_t n, n_lf; + + s = reason_comment; + n = reason_commentlen; + s_lf = NULL; + do + { + /* We don't want any empty lines, so we skip them. */ + for (;n && *s == '\n'; s++, n--) + ; + if (n) + { + s_lf = memchr (s, '\n', n); + n_lf = s_lf? s_lf - s : n; + es_fprintf (es_stdout, " %s", + _("revocation comment: ")); + es_write_sanitized (es_stdout, s, n_lf, NULL, NULL); + es_putc ('\n', es_stdout); + s += n_lf; n -= n_lf; + } + } while (s_lf); + } + } + } + + es_fflush (es_stdout); + + xfree (reason_text); + xfree (reason_comment); + xfree (siguid); + xfree (issuer_fpr); +} + + +/**************** + * Import a revocation certificate; this is a single signature packet. + */ +static int +import_revoke_cert (ctrl_t ctrl, kbnode_t node, unsigned int options, + struct import_stats_s *stats) +{ + PKT_public_key *pk = NULL; + kbnode_t onode; + kbnode_t keyblock = NULL; + KEYDB_HANDLE hd = NULL; + u32 keyid[2]; + int rc = 0; + int sigrc = 0; + int silent; + + /* No error output for --show-keys. */ + silent = (options & (IMPORT_SHOW | IMPORT_DRY_RUN)); + + log_assert (!node->next ); + log_assert (node->pkt->pkttype == PKT_SIGNATURE ); + log_assert (IS_KEY_REV (node->pkt->pkt.signature)); + + keyid[0] = node->pkt->pkt.signature->keyid[0]; + keyid[1] = node->pkt->pkt.signature->keyid[1]; + + pk = xmalloc_clear( sizeof *pk ); + rc = get_pubkey (ctrl, pk, keyid ); + if (gpg_err_code (rc) == GPG_ERR_NO_PUBKEY ) + { + if (!silent) + 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), gpg_strerror (rc)); + goto leave; + } + + /* Read the original keyblock. */ + hd = keydb_new (); + if (!hd) + { + rc = gpg_error_from_syserror (); + goto leave; + } + + { + 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), gpg_strerror (rc)); + goto leave; + } + rc = keydb_get_keyblock (hd, &keyblock ); + if (rc) + { + log_error (_("key %s: can't read original keyblock: %s\n"), + keystr(keyid), gpg_strerror (rc)); + goto leave; + } + + /* it is okay, that node is not in keyblock because + * check_key_signature works fine for sig_class 0x20 (KEY_REV) in + * this special case. SIGRC is only used for IMPORT_SHOW. */ + rc = check_key_signature (ctrl, keyblock, node, NULL); + switch (gpg_err_code (rc)) + { + case 0: sigrc = '!'; break; + case GPG_ERR_BAD_SIGNATURE: sigrc = '-'; break; + case GPG_ERR_NO_PUBKEY: sigrc = '?'; break; + case GPG_ERR_UNUSABLE_PUBKEY: sigrc = '?'; break; + default: sigrc = '%'; break; + } + if (rc ) + { + if (!silent) + log_error (_("key %s: invalid revocation certificate" + ": %s - rejected\n"), keystr(keyid), gpg_strerror (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 unless in dry run mode. */ + if (!(opt.dry_run || (options & IMPORT_DRY_RUN))) + { + rc = keydb_update_keyblock (ctrl, hd, keyblock ); + if (rc) + log_error (_("error writing keyring '%s': %s\n"), + keydb_get_resource_name (hd), gpg_strerror (rc) ); + keydb_release (hd); + hd = NULL; + + /* we are ready */ + if (!opt.quiet ) + { + char *p=get_user_id_native (ctrl, keyid); + log_info( _("key %s: \"%s\" revocation certificate imported\n"), + keystr(keyid),p); + xfree(p); + } + + /* 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 (ctrl, pk) == TRUST_ULTIMATE) + clear_ownertrusts (ctrl, pk); + + revalidation_mark (ctrl); + } + stats->n_revoc++; + + leave: + if ((options & IMPORT_SHOW)) + list_standalone_revocation (ctrl, node->pkt->pkt.signature, sigrc); + + keydb_release (hd); + release_kbnode( keyblock ); + free_public_key( pk ); + return rc; +} + + +/* Loop over the KEYBLOCK and check all self signatures. KEYID is the + * keyid of the primary key for reporting purposes. On return the + * following bits in the node flags are set: + * + * - NODE_GOOD_SELFSIG :: User ID or subkey has a self-signature + * - NODE_BAD_SELFSIG :: Used ID or subkey has an invalid self-signature + * - NODE_DELETION_MARK :: This node shall be deleted + * + * NON_SELF is set to true if there are any sigs other than self-sigs + * in this keyblock. + * + * Returns 0 on success or -1 (but not an error code) if the keyblock + * is invalid. + */ +static int +chk_self_sigs (ctrl_t ctrl, kbnode_t keyblock, u32 *keyid, int *non_self) +{ + kbnode_t knode = NULL; /* The node of the current subkey. */ + PKT_public_key *subpk = NULL; /* and its packet. */ + kbnode_t bsnode = NULL; /* Subkey binding signature node. */ + u32 bsdate = 0; /* Timestamp of that node. */ + kbnode_t rsnode = NULL; /* Subkey recocation signature node. */ + u32 rsdate = 0; /* Timestamp of tha node. */ + PKT_signature *sig; + int rc; + kbnode_t n; + + for (n=keyblock; (n = find_next_kbnode (n, 0)); ) + { + if (n->pkt->pkttype == PKT_PUBLIC_SUBKEY) + { + knode = n; + subpk = knode->pkt->pkt.public_key; + bsdate = 0; + rsdate = 0; + bsnode = NULL; + rsnode = NULL; + continue; + } + + if ( n->pkt->pkttype != PKT_SIGNATURE ) + continue; + + sig = n->pkt->pkt.signature; + if ( keyid[0] != sig->keyid[0] || keyid[1] != sig->keyid[1] ) + { + *non_self = 1; + continue; + } + + /* 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 (ctrl, keyblock, n, NULL); + + if ( IS_UID_SIG(sig) || IS_UID_REV(sig) ) + { + kbnode_t 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 & NODE_GOOD_SELFSIG)) + { + rc = check_key_signature (ctrl, 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 (gpg_err_code(rc) == GPG_ERR_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 |= NODE_GOOD_SELFSIG; + } + } + else if (IS_KEY_SIG (sig)) + { + rc = check_key_signature (ctrl, keyblock, n, NULL); + if ( rc ) + { + if (opt.verbose) + log_info (gpg_err_code (rc) == GPG_ERR_PUBKEY_ALGO ? + _("key %s: unsupported public key algorithm\n"): + _("key %s: invalid direct key signature\n"), + keystr (keyid)); + n->flag |= NODE_DELETION_MARK; + } + } + else if ( IS_SUBKEY_SIG (sig) ) + { + /* 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 |= NODE_DELETION_MARK; + } + else + { + rc = check_key_signature (ctrl, keyblock, n, NULL); + if ( rc ) + { + if (opt.verbose) + { + keyid_from_pk (subpk, NULL); + log_info (gpg_err_code (rc) == GPG_ERR_PUBKEY_ALGO ? + _("key %s: unsupported public key" + " algorithm\n"): + _("key %s: invalid subkey binding\n"), + keystr_with_sub (keyid, subpk->keyid)); + } + n->flag |= NODE_DELETION_MARK; + } + else + { + /* It's valid, so is it newer? */ + if (sig->timestamp >= bsdate) + { + knode->flag |= NODE_GOOD_SELFSIG; /* Subkey is valid. */ + if (bsnode) + { + /* Delete the last binding sig since this + one is newer */ + bsnode->flag |= NODE_DELETION_MARK; + if (opt.verbose) + { + keyid_from_pk (subpk, NULL); + log_info (_("key %s: removed multiple subkey" + " binding\n"), + keystr_with_sub (keyid, subpk->keyid)); + } + } + + bsnode = n; + bsdate = sig->timestamp; + } + else + n->flag |= NODE_DELETION_MARK; /* older */ + } + } + } + else if ( IS_SUBKEY_REV (sig) ) + { + /* 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 |= NODE_DELETION_MARK; + } + else + { + rc = check_key_signature (ctrl, keyblock, n, NULL); + if ( rc ) + { + if(opt.verbose) + log_info (gpg_err_code (rc) == GPG_ERR_PUBKEY_ALGO ? + _("key %s: unsupported public" + " key algorithm\n"): + _("key %s: invalid subkey revocation\n"), + keystr(keyid)); + n->flag |= NODE_DELETION_MARK; + } + else + { + /* It's valid, so is it newer? */ + if (sig->timestamp >= rsdate) + { + if (rsnode) + { + /* Delete the last revocation sig since + this one is newer. */ + rsnode->flag |= NODE_DELETION_MARK; + if (opt.verbose) + log_info (_("key %s: removed multiple subkey" + " revocation\n"),keystr(keyid)); + } + + rsnode = n; + rsdate = sig->timestamp; + } + else + n->flag |= NODE_DELETION_MARK; /* older */ + } + } + } + } + + return 0; +} + + +/* Delete all parts which are invalid and those signatures whose + * public key algorithm is not available in this implementation; 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 (ctrl_t ctrl, kbnode_t keyblock, u32 *keyid, + unsigned int options) +{ + kbnode_t node; + int nvalid=0, uid_seen=0, subkey_seen=0; + PKT_public_key *pk; + + for (node=keyblock->next; node; node = node->next ) + { + if (node->pkt->pkttype == PKT_USER_ID) + { + uid_seen = 1; + if ((node->flag & NODE_BAD_SELFSIG) + || !(node->flag & NODE_GOOD_SELFSIG)) + { + 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 & NODE_BAD_SELFSIG) + || !(node->flag & NODE_GOOD_SELFSIG)) + { + if (opt.verbose ) + { + pk = node->pkt->pkt.public_key; + keyid_from_pk (pk, NULL); + log_info (_("key %s: skipped subkey\n"), + keystr_with_sub (keyid, pk->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 + && openpgp_pk_test_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) + && !have_secret_key_with_kid (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 + && IS_KEY_REV (node->pkt->pkt.signature)) + { + 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 (ctrl, keyblock, node, NULL); + if (rc ) + { + if(opt.verbose) + log_info( _("key %s: invalid revocation" + " certificate: %s - skipped\n"), + keystr(keyid), gpg_strerror (rc)); + delete_kbnode( node ); + } + } + } + } + else if (node->pkt->pkttype == PKT_SIGNATURE + && (IS_SUBKEY_SIG (node->pkt->pkt.signature) + || IS_SUBKEY_REV (node->pkt->pkt.signature)) + && !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 & NODE_DELETION_MARK)) + 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; +} + +/* This function returns true if any UID is left in the keyring. */ +static int +any_uid_left (kbnode_t keyblock) +{ + kbnode_t node; + + for (node=keyblock->next; node; node = node->next) + if (node->pkt->pkttype == PKT_USER_ID) + return 1; + return 0; +} + + +/* Delete all non-self-sigs from KEYBLOCK. + * Returns: True if the keyblock has changed. */ +static void +remove_all_non_self_sigs (kbnode_t *keyblock, u32 *keyid) +{ + kbnode_t node; + unsigned int dropped = 0; + + for (node = *keyblock; node; node = node->next) + { + if (is_deleted_kbnode (node)) + continue; + + if (node->pkt->pkttype != PKT_SIGNATURE) + continue; + + if (node->pkt->pkt.signature->keyid[0] == keyid[0] + && node->pkt->pkt.signature->keyid[1] == keyid[1]) + continue; + delete_kbnode (node); + dropped++; + } + + if (dropped) + commit_kbnode (keyblock); + + if (dropped && opt.verbose) + log_info ("key %s: number of dropped non-self-signatures: %u\n", + keystr (keyid), dropped); +} + + +/* + * 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 has changed. + */ +int +collapse_uids( kbnode_t *keyblock ) +{ + kbnode_t uid1; + int any=0; + + for(uid1=*keyblock;uid1;uid1=uid1->next) + { + kbnode_t uid2; + + if(is_deleted_kbnode(uid1)) + continue; + + if(uid1->pkt->pkttype!=PKT_USER_ID) + continue; + + for(uid2=uid1->next;uid2;uid2=uid2->next) + { + if(is_deleted_kbnode(uid2)) + continue; + + if(uid2->pkt->pkttype!=PKT_USER_ID) + continue; + + if(cmp_user_ids(uid1->pkt->pkt.user_id, + uid2->pkt->pkt.user_id)==0) + { + /* We have a duplicated uid */ + kbnode_t sig1,last; + + any=1; + + /* Now take uid2's signatures, and attach them to + uid1 */ + for(last=uid2;last->next;last=last->next) + { + if(is_deleted_kbnode(last)) + continue; + + if(last->next->pkt->pkttype==PKT_USER_ID + || last->next->pkt->pkttype==PKT_PUBLIC_SUBKEY + || last->next->pkt->pkttype==PKT_SECRET_SUBKEY) + break; + } + + /* Snip out uid2 */ + (find_prev_kbnode(*keyblock,uid2,0))->next=last->next; + + /* Now put uid2 in place as part of uid1 */ + last->next=uid1->next; + uid1->next=uid2; + delete_kbnode(uid2); + + /* Now dedupe uid1 */ + for(sig1=uid1->next;sig1;sig1=sig1->next) + { + kbnode_t sig2; + + if(is_deleted_kbnode(sig1)) + continue; + + if(sig1->pkt->pkttype==PKT_USER_ID + || sig1->pkt->pkttype==PKT_PUBLIC_SUBKEY + || sig1->pkt->pkttype==PKT_SECRET_SUBKEY) + break; + + if(sig1->pkt->pkttype!=PKT_SIGNATURE) + continue; + + for(sig2=sig1->next,last=sig1;sig2;last=sig2,sig2=sig2->next) + { + if(is_deleted_kbnode(sig2)) + continue; + + if(sig2->pkt->pkttype==PKT_USER_ID + || sig2->pkt->pkttype==PKT_PUBLIC_SUBKEY + || sig2->pkt->pkttype==PKT_SECRET_SUBKEY) + break; + + if(sig2->pkt->pkttype!=PKT_SIGNATURE) + continue; + + if(cmp_signatures(sig1->pkt->pkt.signature, + sig2->pkt->pkt.signature)==0) + { + /* We have a match, so delete the second + signature */ + delete_kbnode(sig2); + sig2=last; + } + } + } + } + } + } + + commit_kbnode(keyblock); + + if(any && !opt.quiet) + { + const char *key="???"; + + if ((uid1 = find_kbnode (*keyblock, PKT_PUBLIC_KEY)) ) + key = keystr_from_pk (uid1->pkt->pkt.public_key); + else if ((uid1 = find_kbnode( *keyblock, PKT_SECRET_KEY)) ) + key = keystr_from_pk (uid1->pkt->pkt.public_key); + + log_info (_("key %s: duplicated user ID detected - merged\n"), key); + } + + return any; +} + + +/* 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 (ctrl_t ctrl, kbnode_t keyblock) +{ + kbnode_t 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 + && IS_KEY_SIG (onode->pkt->pkt.signature) + && onode->pkt->pkt.signature->revkey) + { + int idx; + PKT_signature *sig=onode->pkt->pkt.signature; + + for(idx=0;idxnumrevkeys;idx++) + { + u32 keyid[2]; + + keyid_from_fingerprint (ctrl, 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 + && IS_KEY_REV (inode->pkt->pkt.signature) + && 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? */ + gpg_error_t err; + + err = get_pubkey_byfprint_fast (NULL, + sig->revkey[idx].fpr, + MAX_FINGERPRINT_LEN); + if (gpg_err_code (err) == GPG_ERR_NO_PUBKEY + || gpg_err_code (err) == GPG_ERR_UNUSABLE_PUBKEY) + { + char *tempkeystr = xstrdup (keystr_from_pk (pk)); + + /* No, so try and get it */ + if ((opt.keyserver_options.options + & KEYSERVER_AUTO_KEY_RETRIEVE) + && keyserver_any_configured (ctrl)) + { + log_info(_("WARNING: key %s may be revoked:" + " fetching revocation key %s\n"), + tempkeystr,keystr(keyid)); + keyserver_import_fprint (ctrl, + sig->revkey[idx].fpr, + MAX_FINGERPRINT_LEN, + opt.keyserver, 0); + + /* Do we have it now? */ + err = get_pubkey_byfprint_fast (NULL, + sig->revkey[idx].fpr, + MAX_FINGERPRINT_LEN); + } + + if (gpg_err_code (err) == GPG_ERR_NO_PUBKEY + || gpg_err_code (err) == GPG_ERR_UNUSABLE_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 NODE_FLAG_A. + */ +static int +merge_blocks (ctrl_t ctrl, unsigned int options, + kbnode_t keyblock_orig, kbnode_t keyblock, + u32 *keyid, u32 curtime, int origin, const char *url, + int *n_uids, int *n_sigs, int *n_subk ) +{ + kbnode_t 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 + && IS_KEY_REV (node->pkt->pkt.signature)) + { + /* 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 + && IS_KEY_REV (onode->pkt->pkt.signature) + && !cmp_signatures(onode->pkt->pkt.signature, + node->pkt->pkt.signature)) + { + found = 1; + break; + } + } + if (!found) + { + kbnode_t n2 = clone_kbnode(node); + insert_kbnode( keyblock_orig, n2, 0 ); + n2->flag |= NODE_FLAG_A; + ++*n_sigs; + if(!opt.quiet) + { + char *p = get_user_id_native (ctrl, 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 + && IS_KEY_SIG (node->pkt->pkt.signature)) + { + /* 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 + && IS_KEY_SIG (onode->pkt->pkt.signature) + && !cmp_signatures(onode->pkt->pkt.signature, + node->pkt->pkt.signature)) + { + found = 1; + break; + } + } + if (!found ) + { + kbnode_t n2 = clone_kbnode(node); + insert_kbnode( keyblock_orig, n2, 0 ); + n2->flag |= NODE_FLAG_A; + ++*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 & NODE_FLAG_A) && 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); + 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_new_uid (options, keyblock_orig, node, + curtime, origin, url, n_sigs); + 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); + 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_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); + if (rc ) + return rc; + ++*n_subk; + } + } + } + + /* 6th: merge subkey certificates */ + for (onode=keyblock_orig->next; onode; onode=onode->next) + { + if (!(onode->flag & NODE_FLAG_A) + && (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 + || node->pkt->pkttype == PKT_SECRET_SUBKEY) + && !cmp_public_keys( onode->pkt->pkt.public_key, + node->pkt->pkt.public_key ) ) + break; + } + if (node) /* Found: merge. */ + { + rc = merge_keysigs( onode, node, n_sigs); + if (rc ) + return rc; + } + } + } + + return 0; +} + + +/* Helper function for merge_blocks. + * + * Append the new userid starting with NODE and all signatures to + * KEYBLOCK. ORIGIN and URL conveys the usual key origin info. The + * integer at N_SIGS is updated with the number of new signatures. + */ +static gpg_error_t +append_new_uid (unsigned int options, + kbnode_t keyblock, kbnode_t node, u32 curtime, + int origin, const char *url, int *n_sigs) +{ + gpg_error_t err; + kbnode_t n; + kbnode_t n_where = NULL; + + log_assert (node->pkt->pkttype == PKT_USER_ID); + + /* Find the right position for the new user id and its signatures. */ + 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->pkt->pkttype == PKT_USER_ID + && !(options & IMPORT_RESTORE) ) + { + err = insert_key_origin_uid (n->pkt->pkt.user_id, + curtime, origin, url); + if (err) + return err; + } + + if (n_where) + { + insert_kbnode( n_where, n, 0 ); + n_where = n; + } + else + add_kbnode( keyblock, n ); + n->flag |= NODE_FLAG_A; + node->flag |= NODE_FLAG_A; + if (n->pkt->pkttype == PKT_SIGNATURE ) + ++*n_sigs; + + node = node->next; + if (node && node->pkt->pkttype != PKT_SIGNATURE ) + break; + } + + return 0; +} + + +/* Helper function for merge_blocks + * 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_t dst, kbnode_t src, int *n_sigs) +{ + kbnode_t n, n2; + int found = 0; + + log_assert (dst->pkt->pkttype == PKT_USER_ID); + log_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 (IS_SUBKEY_SIG (n->pkt->pkt.signature) + || IS_SUBKEY_REV (n->pkt->pkt.signature) ) + 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 |= NODE_FLAG_A; + n->flag |= NODE_FLAG_A; + ++*n_sigs; + } + } + + return 0; +} + + +/* Helper function for merge_blocks + * Merge the sigs from SRC onto DST. SRC and DST are both a PKT_xxx_SUBKEY. + */ +static int +merge_keysigs (kbnode_t dst, kbnode_t src, int *n_sigs) +{ + kbnode_t n, n2; + int found = 0; + + log_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 |= NODE_FLAG_A; + n->flag |= NODE_FLAG_A; + ++*n_sigs; + } + } + + return 0; +} + + +/* Helper function for merge_blocks. + * 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_t keyblock, kbnode_t node, int *n_sigs) +{ + kbnode_t n; + + log_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 |= NODE_FLAG_A; + node->flag |= NODE_FLAG_A; + if (n->pkt->pkttype == PKT_SIGNATURE ) + ++*n_sigs; + + node = node->next; + if (node && node->pkt->pkttype != PKT_SIGNATURE ) + break; + } + + return 0; +} diff --git a/g10/kbnode.c b/g10/kbnode.c new file mode 100644 index 0000000..9ed6caf --- /dev/null +++ b/g10/kbnode.c @@ -0,0 +1,431 @@ +/* kbnode.c - keyblock node utility functions + * Copyright (C) 1998, 1999, 2000, 2001, 2002, + * 2005, 2010 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include +#include +#include +#include + +#include "gpg.h" +#include "../common/util.h" +#include "../common/init.h" +#include "packet.h" +#include "keydb.h" + +#define USE_UNUSED_NODES 1 + +static int cleanup_registered; +static KBNODE unused_nodes; + +static void +release_unused_nodes (void) +{ +#if USE_UNUSED_NODES + while (unused_nodes) + { + kbnode_t next = unused_nodes->next; + xfree (unused_nodes); + unused_nodes = next; + } +#endif /*USE_UNUSED_NODES*/ +} + + +static kbnode_t +alloc_node (void) +{ + kbnode_t n; + + n = unused_nodes; + if (n) + unused_nodes = n->next; + else + { + if (!cleanup_registered) + { + cleanup_registered = 1; + register_mem_cleanup_func (release_unused_nodes); + } + n = xmalloc (sizeof *n); + } + n->next = NULL; + n->pkt = NULL; + n->flag = 0; + n->tag = 0; + n->private_flag=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, NULL); + 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, NULL); + 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, NULL); + 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; + } + log_debug ("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; + log_printf (" \""); + es_write_sanitized (log_get_stream (), uid->name, uid->len, + NULL, NULL); + log_printf ("\" %c%c%c%c\n", + uid->flags.expired? 'e':'.', + uid->flags.revoked? 'r':'.', + uid->created? 'v':'.', + uid->flags.primary? 'p':'.' ); + } + else if (node->pkt->pkttype == PKT_SIGNATURE) + { + log_printf (" 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) + { + log_printf (" 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; + + log_printf (" 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->flags.revoked? 'r':'.', + pk->flags.valid? 'v':'.', + pk->flags.mdc? 'm':'.'); + } + else + log_printf ("\n"); + + log_flush (); + } +} diff --git a/g10/key-check.c b/g10/key-check.c new file mode 100644 index 0000000..45f384b --- /dev/null +++ b/g10/key-check.c @@ -0,0 +1,750 @@ +/* key-check.c - Detect and fix various problems with keys + * Copyright (C) 1998-2010 Free Software Foundation, Inc. + * Copyright (C) 1998-2017 Werner Koch + * Copyright (C) 2015-2018 g10 Code GmbH + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include + +#include "gpg.h" +#include "options.h" +#include "packet.h" +#include "keydb.h" +#include "main.h" +#include "../common/ttyio.h" +#include "../common/i18n.h" +#include "keyedit.h" + +#include "key-check.h" + + +/* Print PREFIX followed by TEXT. With mode > 0 use log_info, with + * mode < 0 use ttyio, else print to stdout. If TEXT is not NULL, it + * may be modified by this function. */ +static void +print_info (int mode, const char *prefix, char *text) +{ + char *p; + + if (!text) + text = ""; + else if ((p = strchr (text,'\n'))) + *p = 0; /* Strip LF. */ + + if (mode > 0) + log_info ("%s %s\n", prefix, text); + else + tty_fprintf (mode? NULL:es_stdout, "%s %s\n", prefix, text); +} + + +/* Order two signatures. The actual ordering isn't important. Our + * goal is to ensure that identical signatures occur together. */ +static int +sig_comparison (const void *av, const void *bv) +{ + const KBNODE an = *(const KBNODE *) av; + const KBNODE bn = *(const KBNODE *) bv; + const PKT_signature *a; + const PKT_signature *b; + int ndataa; + int ndatab; + int i; + + log_assert (an->pkt->pkttype == PKT_SIGNATURE); + log_assert (bn->pkt->pkttype == PKT_SIGNATURE); + + a = an->pkt->pkt.signature; + b = bn->pkt->pkt.signature; + + /* Signatures with a different help counter are not identical for + * our purpose. */ + if (a->help_counter < b->help_counter) + return -1; + if (a->help_counter > b->help_counter) + return 1; + + if (a->digest_algo < b->digest_algo) + return -1; + if (a->digest_algo > b->digest_algo) + return 1; + + ndataa = pubkey_get_nsig (a->pubkey_algo); + ndatab = pubkey_get_nsig (b->pubkey_algo); + if (ndataa != ndatab) + return (ndataa < ndatab)? -1 : 1; + + for (i = 0; i < ndataa; i ++) + { + int c = gcry_mpi_cmp (a->data[i], b->data[i]); + if (c != 0) + return c; + } + + /* Okay, they are equal. */ + return 0; +} + + +static gpg_error_t +remove_duplicate_sigs (kbnode_t kb, int *dups, int *modified) +{ + gpg_error_t err; + kbnode_t n; + int nsigs; + kbnode_t *sigs; /* Allocated array with the signature packet. */ + int i; + int last_i; + int block; + PKT_signature *sig; + + /* Count the sigs. */ + for (nsigs = 0, n = kb; n; n = n->next) + { + if (is_deleted_kbnode (n)) + continue; + else if (n->pkt->pkttype == PKT_SIGNATURE) + nsigs ++; + } + + if (!nsigs) + return 0; /* No signatures at all. */ + + /* Add them all to the SIGS array. */ + sigs = xtrycalloc (nsigs, sizeof *sigs); + if (!sigs) + { + err = gpg_error_from_syserror (); + log_error (_("error allocating memory: %s\n"), gpg_strerror (err)); + return err; + } + + block = 0; + i = 0; + for (n = kb; n; n = n->next) + { + if (is_deleted_kbnode (n)) + continue; + + if (n->pkt->pkttype != PKT_SIGNATURE) + { + switch (n->pkt->pkttype) + { + case PKT_PUBLIC_SUBKEY: + case PKT_SECRET_SUBKEY: + case PKT_USER_ID: + case PKT_ATTRIBUTE: + /* Bump the block number so that we only consider + * signatures below the same object as duplicates. */ + block++; + break; + default: + break; + } + continue; + } + sig = n->pkt->pkt.signature; + sig->help_counter = block; + sigs[i++] = n; + } + log_assert (i == nsigs); + + qsort (sigs, nsigs, sizeof (sigs[0]), sig_comparison); + + last_i = 0; + for (i = 1; i < nsigs; i ++) + { + log_assert (sigs[last_i]); + log_assert (sigs[last_i]->pkt->pkttype == PKT_SIGNATURE); + log_assert (sigs[i]); + log_assert (sigs[i]->pkt->pkttype == PKT_SIGNATURE); + + if (sig_comparison (&sigs[last_i], &sigs[i]) == 0) + { + /* They are the same. Kill the latter. */ + if (DBG_PACKET) + { + sig = sigs[i]->pkt->pkt.signature; + + log_debug ("Signature appears multiple times, " + "deleting duplicate:\n"); + log_debug (" sig: class 0x%x, issuer: %s," + " timestamp: %s (%lld), digest: %02x %02x\n", + sig->sig_class, keystr (sig->keyid), + isotimestamp (sig->timestamp), + (long long) sig->timestamp, + sig->digest_start[0], sig->digest_start[1]); + } + + /* Remove sigs[i] from the keyblock. */ + { + kbnode_t z, *prevp; + int to_kill = last_i; + last_i = i; + + for (prevp = &kb, z = kb; z; prevp = &z->next, z = z->next) + if (z == sigs[to_kill]) + break; + + *prevp = sigs[to_kill]->next; + + sigs[to_kill]->next = NULL; + release_kbnode (sigs[to_kill]); + sigs[to_kill] = NULL; + + ++*dups; + *modified = 1; + } + } + else + last_i = i; + } + + xfree (sigs); + return 0; +} + + +/* Perform a few sanity checks on a keyblock is okay and possibly + * repair some damage. Concretely: + * + * - Detect duplicate signatures and remove them. + * + * - Detect out of order signatures and relocate them (e.g., a sig + * over user id X located under subkey Y). + * + * Note: this function does not remove signatures that don't belong or + * components that are not signed! (Although it would be trivial to + * do so.) + * + * If ONLY_SELFSIGS is true, then this function only reorders self + * signatures (it still checks all signatures for duplicates, + * however). + * + * Allowed values for MODE are: + * -1 - print to the TTY + * 0 - print to stdout + * 1 - use log_info. + * + * Returns true if the keyblock was modified. */ +int +key_check_all_keysigs (ctrl_t ctrl, int mode, kbnode_t kb, + int only_selected, int only_selfsigs) +{ + gpg_error_t err; + PKT_public_key *pk; + KBNODE n, n_next, *n_prevp, n2; + char *pending_desc = NULL; + PKT_public_key *issuer; + KBNODE last_printed_component; + KBNODE current_component = NULL; + int dups = 0; + int missing_issuer = 0; + int reordered = 0; + int bad_signature = 0; + int missing_selfsig = 0; + int modified = 0; + PKT_signature *sig; + + log_assert (kb->pkt->pkttype == PKT_PUBLIC_KEY); + pk = kb->pkt->pkt.public_key; + + /* First we look for duplicates. */ + if (remove_duplicate_sigs (kb, &dups, &modified)) + goto leave; /* Error */ + + /* Now make sure the sigs occur after the component (aka block) + * (public key, subkey, user id) that they sign. */ + issuer = NULL; + last_printed_component = NULL; + for (n_prevp = &kb, n = kb; + n; + /* If we moved n, then n_prevp is need valid. */ + n_prevp = (n->next == n_next ? &n->next : n_prevp), n = n_next) + { + PACKET *p; + int processed_current_component; + int rc; + int dump_sig_params = 0; + + n_next = n->next; + + if (is_deleted_kbnode (n)) + continue; + + p = n->pkt; + + if (issuer && issuer != pk) + { + free_public_key (issuer); + issuer = NULL; + } + + xfree (pending_desc); + pending_desc = NULL; + + switch (p->pkttype) + { + case PKT_PUBLIC_KEY: + log_assert (p->pkt.public_key == pk); + if (only_selected && ! (n->flag & NODFLG_SELKEY)) + { + current_component = NULL; + break; + } + + if (DBG_PACKET) + log_debug ("public key %s: timestamp: %s (%lld)\n", + pk_keyid_str (pk), + isotimestamp (pk->timestamp), + (long long) pk->timestamp); + current_component = n; + break; + case PKT_PUBLIC_SUBKEY: + if (only_selected && ! (n->flag & NODFLG_SELKEY)) + { + current_component = NULL; + break; + } + + if (DBG_PACKET) + log_debug ("subkey %s: timestamp: %s (%lld)\n", + pk_keyid_str (p->pkt.public_key), + isotimestamp (p->pkt.public_key->timestamp), + (long long) p->pkt.public_key->timestamp); + current_component = n; + break; + case PKT_USER_ID: + if (only_selected && ! (n->flag & NODFLG_SELUID)) + { + current_component = NULL; + break; + } + + if (DBG_PACKET) + log_debug ("user id: %s\n", + p->pkt.user_id->attrib_data + ? "[ photo id ]" + : p->pkt.user_id->name); + current_component = n; + break; + case PKT_SIGNATURE: + if (! current_component) + /* The current component is not selected, don't check the + sigs under it. */ + break; + + sig = n->pkt->pkt.signature; + + pending_desc = xasprintf (" sig: class: 0x%x, issuer: %s," + " timestamp: %s (%lld), digest: %02x %02x", + sig->sig_class, + keystr (sig->keyid), + isotimestamp (sig->timestamp), + (long long) sig->timestamp, + sig->digest_start[0], sig->digest_start[1]); + + + if (keyid_cmp (pk_keyid (pk), sig->keyid) == 0) + issuer = pk; + else /* Issuer is a different key. */ + { + if (only_selfsigs) + continue; + + issuer = xtrycalloc (1, sizeof *issuer); + if (!issuer) + err = gpg_error_from_syserror (); + else + err = get_pubkey (ctrl, issuer, sig->keyid); + if (err) + { + xfree (issuer); + issuer = NULL; + if (DBG_PACKET) + { + if (pending_desc) + log_debug ("%s", pending_desc); + log_debug (" Can't check signature allegedly" + " issued by %s: %s\n", + keystr (sig->keyid), gpg_strerror (err)); + } + missing_issuer ++; + break; + } + } + + if ((err = openpgp_pk_test_algo (sig->pubkey_algo))) + { + if (DBG_PACKET && pending_desc) + log_debug ("%s", pending_desc); + log_info (_("can't check signature with unsupported" + " public-key algorithm (%d): %s.\n"), + sig->pubkey_algo, gpg_strerror (err)); + break; + } + if ((err = openpgp_md_test_algo (sig->digest_algo))) + { + if (DBG_PACKET && pending_desc) + log_debug ("%s", pending_desc); + log_info (_("can't check signature with unsupported" + " message-digest algorithm %d: %s.\n"), + sig->digest_algo, gpg_strerror (err)); + break; + } + + /* We iterate over the keyblock. Most likely, the matching + component is the current component so always try that + first. */ + processed_current_component = 0; + for (n2 = current_component; + n2; + n2 = (processed_current_component ? n2->next : kb), + processed_current_component = 1) + if (is_deleted_kbnode (n2)) + continue; + else if (processed_current_component && n2 == current_component) + /* Don't process it twice. */ + continue; + else + { + err = check_signature_over_key_or_uid (ctrl, + issuer, sig, kb, n2->pkt, + NULL, NULL); + if (! err) + break; + } + + /* n/sig is a signature and n2 is the component (public key, + subkey or user id) that it signs, if any. + current_component is that component that it appears to + apply to (according to the ordering). */ + + if (current_component == n2) + { + if (DBG_PACKET) + { + log_debug ("%s", pending_desc); + log_debug (" Good signature over last key or uid!\n"); + } + + rc = 0; + } + else if (n2) + { + log_assert (n2->pkt->pkttype == PKT_USER_ID + || n2->pkt->pkttype == PKT_PUBLIC_KEY + || n2->pkt->pkttype == PKT_PUBLIC_SUBKEY); + + if (DBG_PACKET) + { + log_debug ("%s", pending_desc); + log_debug (" Good signature out of order!" + " (Over %s (%d) '%s')\n", + n2->pkt->pkttype == PKT_USER_ID + ? "user id" + : n2->pkt->pkttype == PKT_PUBLIC_SUBKEY + ? "subkey" + : "primary key", + n2->pkt->pkttype, + n2->pkt->pkttype == PKT_USER_ID + ? n2->pkt->pkt.user_id->name + : pk_keyid_str (n2->pkt->pkt.public_key)); + } + + /* Reorder the packets: move the signature n to be just + after n2. */ + + /* Unlink the signature. */ + log_assert (n_prevp); + *n_prevp = n->next; + + /* Insert the sig immediately after the component. */ + n->next = n2->next; + n2->next = n; + + reordered ++; + modified = 1; + + rc = 0; + } + else + { + if (DBG_PACKET) + { + log_debug ("%s", pending_desc); + log_debug (" Bad signature.\n"); + } + + if (DBG_PACKET) + dump_sig_params = 1; + + bad_signature ++; + + rc = GPG_ERR_BAD_SIGNATURE; + } + + /* We don't cache the result here, because we haven't + completely checked that the signature is legitimate. For + instance, if we have a revocation certificate on Alice's + key signed by Bob, the signature may be good, but we + haven't checked that Bob is a designated revoker. */ + /* cache_sig_result (sig, rc); */ + + { + int has_selfsig = 0; + if (! rc && issuer == pk) + { + if (n2->pkt->pkttype == PKT_PUBLIC_KEY + && (/* Direct key signature. */ + sig->sig_class == 0x1f + /* Key revocation signature. */ + || sig->sig_class == 0x20)) + has_selfsig = 1; + if (n2->pkt->pkttype == PKT_PUBLIC_SUBKEY + && (/* Subkey binding sig. */ + sig->sig_class == 0x18 + /* Subkey revocation sig. */ + || sig->sig_class == 0x28)) + has_selfsig = 1; + if (n2->pkt->pkttype == PKT_USER_ID + && (/* Certification sigs. */ + sig->sig_class == 0x10 + || sig->sig_class == 0x11 + || sig->sig_class == 0x12 + || sig->sig_class == 0x13 + /* Certification revocation sig. */ + || sig->sig_class == 0x30)) + has_selfsig = 1; + } + + if (DBG_PACKET + && ((n2 && n2 != last_printed_component) + || (! n2 && last_printed_component != current_component))) + { + int is_reordered = n2 && n2 != current_component; + if (n2) + last_printed_component = n2; + else + last_printed_component = current_component; + + if (!modified) + ; + else if (last_printed_component->pkt->pkttype == PKT_USER_ID) + { + log_debug ("uid "); + print_utf8_buffer (log_get_stream (), + last_printed_component + ->pkt->pkt.user_id->name, + last_printed_component + ->pkt->pkt.user_id->len); + log_flush (); + } + else if (last_printed_component->pkt->pkttype + == PKT_PUBLIC_KEY) + log_debug ("pub %s\n", + pk_keyid_str (last_printed_component + ->pkt->pkt.public_key)); + else + log_debug ("sub %s\n", + pk_keyid_str (last_printed_component + ->pkt->pkt.public_key)); + + if (modified) + { + if (is_reordered) + log_debug ("%s\n", _(" (reordered signatures follow)")); + } + } + + if (DBG_PACKET && modified) + keyedit_print_one_sig (ctrl, log_get_stream (), + rc, kb, n, NULL, NULL, NULL, + has_selfsig, 0, only_selfsigs); + } + + if (dump_sig_params) + { + int i; + + for (i = 0; i < pubkey_get_nsig (sig->pubkey_algo); i ++) + { + char buffer[1024]; + size_t len; + char *printable; + gcry_mpi_print (GCRYMPI_FMT_USG, + buffer, sizeof (buffer), &len, + sig->data[i]); + printable = bin2hex (buffer, len, NULL); + log_debug (" %d: %s\n", i, printable); + xfree (printable); + } + } + break; + default: + if (DBG_PACKET) + log_debug ("unhandled packet: %d\n", p->pkttype); + break; + } + } + + xfree (pending_desc); + pending_desc = NULL; + + if (issuer != pk) + free_public_key (issuer); + issuer = NULL; + + /* If we reordered signatures we need to de-duplicate again because + * a signature can now be a duplicate in another block. */ + if (reordered) + { + if (remove_duplicate_sigs (kb, &dups, &modified)) + goto leave; + } + + /* Identify keys / uids that don't have a self-sig. */ + { + int has_selfsig = 0; + PACKET *p; + + current_component = NULL; + for (n = kb; n; n = n->next) + { + if (is_deleted_kbnode (n)) + continue; + + p = n->pkt; + + switch (p->pkttype) + { + case PKT_PUBLIC_KEY: + case PKT_PUBLIC_SUBKEY: + case PKT_USER_ID: + if (current_component && ! has_selfsig) + missing_selfsig ++; + current_component = n; + has_selfsig = 0; + break; + + case PKT_SIGNATURE: + if (! current_component || has_selfsig) + break; + + sig = n->pkt->pkt.signature; + + if (! (sig->flags.checked && sig->flags.valid)) + break; + + if (keyid_cmp (pk_keyid (pk), sig->keyid) != 0) + /* Different issuer, couldn't be a self-sig. */ + break; + + if (current_component->pkt->pkttype == PKT_PUBLIC_KEY + && (/* Direct key signature. */ + sig->sig_class == 0x1f + /* Key revocation signature. */ + || sig->sig_class == 0x20)) + has_selfsig = 1; + if (current_component->pkt->pkttype == PKT_PUBLIC_SUBKEY + && (/* Subkey binding sig. */ + sig->sig_class == 0x18 + /* Subkey revocation sig. */ + || sig->sig_class == 0x28)) + has_selfsig = 1; + if (current_component->pkt->pkttype == PKT_USER_ID + && (/* Certification sigs. */ + sig->sig_class == 0x10 + || sig->sig_class == 0x11 + || sig->sig_class == 0x12 + || sig->sig_class == 0x13 + /* Certification revocation sig. */ + || sig->sig_class == 0x30)) + has_selfsig = 1; + + break; + + default: + if (current_component && ! has_selfsig) + missing_selfsig ++; + current_component = NULL; + } + } + } + + + leave: + if (!opt.quiet) + { + char prefix[100]; + char *p; + + /* To avoid string changes in 2.2 we strip the LF here. */ + snprintf (prefix, sizeof prefix, _("key %s:\n"), pk_keyid_str (pk)); + p = strrchr (prefix, '\n'); + if (p) + *p = 0; + + if (dups) + { + p = xtryasprintf + (ngettext ("%d duplicate signature removed\n", + "%d duplicate signatures removed\n", dups), dups); + print_info (mode, prefix, p); + xfree (p); + } + + if (missing_issuer) + { + p = xtryasprintf + (ngettext ("%d signature not checked due to a missing key\n", + "%d signatures not checked due to missing keys\n", + missing_issuer), missing_issuer); + print_info (mode, prefix, p); + xfree (p); + } + if (bad_signature) + { + p = xtryasprintf (ngettext ("%d bad signature\n", + "%d bad signatures\n", + bad_signature), bad_signature); + print_info (mode, prefix, p); + xfree (p); + } + + if (reordered) + { + p = xtryasprintf (ngettext ("%d signature reordered\n", + "%d signatures reordered\n", + reordered), reordered); + print_info (mode, prefix, p); + xfree (p); + } + + if (only_selfsigs && (bad_signature || reordered)) + { + p = xtryasprintf + (_("Warning: errors found and only checked self-signatures," + " run '%s' to check all signatures.\n"), "check"); + print_info (mode, prefix, p); + xfree (p); + } + } + + return modified; +} diff --git a/g10/key-check.h b/g10/key-check.h new file mode 100644 index 0000000..9f7886c --- /dev/null +++ b/g10/key-check.h @@ -0,0 +1,28 @@ +/* key-check.h - Detect and fix various problems with keys + * Copyright (C) 2017 g10 Code GmbH + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#ifndef GNUPG_G10_PACKET_TOOLS_H +#define GNUPG_G10_PACKET_TOOLS_H + +#include "gpg.h" + +int key_check_all_keysigs (ctrl_t ctrl, int mode, kbnode_t kb, + int only_selected, int only_selfsigs); + +#endif /* GNUPG_G10_PACKET_TOOLS_H */ diff --git a/g10/key-clean.c b/g10/key-clean.c new file mode 100644 index 0000000..f66a0db --- /dev/null +++ b/g10/key-clean.c @@ -0,0 +1,614 @@ +/* key-clean.c - Functions to clean a keyblock + * Copyright (C) 1998-2008, 2010-2011 Free Software Foundation, Inc. + * Copyright (C) 2014, 2016-2018 Werner Koch + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include +#include +#include +#include + +#include "gpg.h" +#include "keydb.h" +#include "../common/util.h" +#include "../common/host2net.h" +#include "../common/i18n.h" +#include "options.h" +#include "packet.h" +#include "main.h" +#include "key-clean.h" + + +/* + * 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. + */ +void +mark_usable_uid_certs (ctrl_t ctrl, kbnode_t keyblock, kbnode_t uidnode, + u32 *main_kid, struct key_item *klist, + u32 curtime, u32 *next_expire) +{ + kbnode_t 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 + || node->pkt->pkttype == PKT_SECRET_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-0x10flag |= 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 + || node->pkt->pkttype == PKT_SECRET_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 + || n->pkt->pkttype == PKT_SECRET_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 + buf32_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 (ctrl_t ctrl, kbnode_t keyblock, kbnode_t uidnode, + int noisy, int self_only) +{ + int deleted = 0; + kbnode_t node; + u32 keyid[2]; + + log_assert (keyblock->pkt->pkttype == PKT_PUBLIC_KEY + || keyblock->pkt->pkttype == PKT_SECRET_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 (ctrl, 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; + + 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 superseded. 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 superseded" + /* */ :"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 resurrected 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_t keyblock, kbnode_t uidnode, int noisy) +{ + kbnode_t node; + PKT_user_id *uid = uidnode->pkt->pkt.user_id; + int deleted = 0; + + log_assert (keyblock->pkt->pkttype == PKT_PUBLIC_KEY + || keyblock->pkt->pkttype == PKT_SECRET_KEY); + log_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->flags.expired && !uid->flags.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->flags.revoked) + reason = _("revoked"); + else if (uid->flags.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 (ctrl_t ctrl, kbnode_t keyblock, kbnode_t uidnode, + int noisy, int self_only, int *uids_cleaned, int *sigs_cleaned) +{ + int dummy = 0; + + log_assert (keyblock->pkt->pkttype == PKT_PUBLIC_KEY + || keyblock->pkt->pkttype == PKT_SECRET_KEY); + log_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 (ctrl, keyblock, uidnode, + noisy, self_only); +} + + +/* NB: This function marks the deleted nodes only and the caller is + * responsible to skip or remove them. Needs to be called after a + * merge_keys_and_selfsig(). */ +void +clean_all_uids (ctrl_t ctrl, kbnode_t keyblock, int noisy, int self_only, + int *uids_cleaned, int *sigs_cleaned) +{ + kbnode_t node; + + for (node = keyblock->next; + node && !(node->pkt->pkttype == PKT_PUBLIC_SUBKEY + || node->pkt->pkttype == PKT_SECRET_SUBKEY); + node = node->next) + { + if (node->pkt->pkttype == PKT_USER_ID) + clean_one_uid (ctrl, keyblock, node, noisy, self_only, + uids_cleaned, sigs_cleaned); + } + + /* Remove bogus subkey binding signatures: The only signatures + * allowed are of class 0x18 and 0x28. */ + log_assert (!node || (node->pkt->pkttype == PKT_PUBLIC_SUBKEY + || node->pkt->pkttype == PKT_SECRET_SUBKEY)); +} + + +/* Helper for clean_all_subkeys. */ +static int +clean_one_subkey (ctrl_t ctrl, kbnode_t subkeynode, int noisy, int clean_level) +{ + kbnode_t node; + PKT_public_key *pk = subkeynode->pkt->pkt.public_key; + unsigned int use = pk->pubkey_usage; + int do_clean = 0; + + (void)ctrl; + (void)noisy; + + log_assert (subkeynode->pkt->pkttype == PKT_PUBLIC_SUBKEY + || subkeynode->pkt->pkttype == PKT_SECRET_SUBKEY); + + if (DBG_LOOKUP) + log_debug ("\tchecking subkey %08lX [%c%c%c%c%c]\n", + (ulong) keyid_from_pk (pk, NULL), + (use & PUBKEY_USAGE_ENC)? 'e':'-', + (use & PUBKEY_USAGE_SIG)? 's':'-', + (use & PUBKEY_USAGE_CERT)? 'c':'-', + (use & PUBKEY_USAGE_AUTH)? 'a':'-', + (use & PUBKEY_USAGE_UNKNOWN)? '?':'-'); + + if (!pk->flags.valid) + { + if (DBG_LOOKUP) + log_debug ("\tsubkey not valid\n"); + if (clean_level == KEY_CLEAN_INVALID) + do_clean = 1; + } + if (pk->has_expired) + { + if (DBG_LOOKUP) + log_debug ("\tsubkey has expired\n"); + if (clean_level == KEY_CLEAN_ALL) + do_clean = 1; + else if (clean_level == KEY_CLEAN_AUTHENCR + && (use & (PUBKEY_USAGE_ENC | PUBKEY_USAGE_AUTH)) + && !(use & (PUBKEY_USAGE_SIG | PUBKEY_USAGE_CERT))) + do_clean = 1; + else if (clean_level == KEY_CLEAN_ENCR + && (use & PUBKEY_USAGE_ENC) + && !(use & (PUBKEY_USAGE_SIG | PUBKEY_USAGE_CERT + | PUBKEY_USAGE_AUTH))) + do_clean = 1; + } + if (pk->flags.revoked) + { + if (DBG_LOOKUP) + log_debug ("\tsubkey has been revoked (keeping)\n"); + /* Avoid any cleaning because revocations are important. */ + do_clean = 0; + } + if (!do_clean) + return 0; + + if (DBG_LOOKUP) + log_debug ("\t=> removing this subkey\n"); + + delete_kbnode (subkeynode); + for (node = subkeynode->next; + node && !(node->pkt->pkttype == PKT_PUBLIC_SUBKEY + || node->pkt->pkttype == PKT_SECRET_SUBKEY); + node = node->next) + delete_kbnode (node); + + return 1; +} + + +/* Helper for clean_all_subkeys. Here duplicate signatures from a + * subkey are removed. This should in general not happen because + * import takes care of that. However, sometimes other tools are used + * to manage a keyring or key has been imported a long time ago. */ +static int +clean_one_subkey_dupsigs (ctrl_t ctrl, kbnode_t subkeynode) +{ + kbnode_t node; + PKT_public_key *pk = subkeynode->pkt->pkt.public_key; + int any_choosen = 0; + int count = 0; + + (void)ctrl; + + log_assert (subkeynode->pkt->pkttype == PKT_PUBLIC_SUBKEY + || subkeynode->pkt->pkttype == PKT_SECRET_SUBKEY); + + if (DBG_LOOKUP) + log_debug ("\tchecking subkey %08lX for dupsigs\n", + (ulong) keyid_from_pk (pk, NULL)); + + /* First check that the choosen flag has been set. Note that we + * only look at plain signatures so to keep all revocation + * signatures which may carry important information. */ + for (node = subkeynode->next; + node && !(node->pkt->pkttype == PKT_PUBLIC_SUBKEY + || node->pkt->pkttype == PKT_SECRET_SUBKEY); + node = node->next) + { + if (!is_deleted_kbnode (node) + && node->pkt->pkttype == PKT_SIGNATURE + && IS_SUBKEY_SIG (node->pkt->pkt.signature) + && node->pkt->pkt.signature->flags.chosen_selfsig) + { + any_choosen = 1; + break; + } + } + + if (!any_choosen) + return 0; /* Ooops no choosen flag set - we can't decide. */ + + for (node = subkeynode->next; + node && !(node->pkt->pkttype == PKT_PUBLIC_SUBKEY + || node->pkt->pkttype == PKT_SECRET_SUBKEY); + node = node->next) + { + if (!is_deleted_kbnode (node) + && node->pkt->pkttype == PKT_SIGNATURE + && IS_SUBKEY_SIG (node->pkt->pkt.signature) + && !node->pkt->pkt.signature->flags.chosen_selfsig) + { + delete_kbnode (node); + count++; + } + } + + return count; +} + + +/* This function only marks the deleted nodes and the caller is + * responsible to skip or remove them. Needs to be called after a + * merge_keys_and_selfsig. CLEAN_LEVEL is one of the KEY_CLEAN_* + * values. */ +void +clean_all_subkeys (ctrl_t ctrl, kbnode_t keyblock, int noisy, int clean_level, + int *subkeys_cleaned, int *sigs_cleaned) +{ + kbnode_t first_subkey, node; + int n; + + if (DBG_LOOKUP) + log_debug ("clean_all_subkeys: checking key %08lX\n", + (ulong) keyid_from_pk (keyblock->pkt->pkt.public_key, NULL)); + + for (node = keyblock->next; node; node = node->next) + if (!is_deleted_kbnode (node) + && (node->pkt->pkttype == PKT_PUBLIC_SUBKEY + || node->pkt->pkttype == PKT_SECRET_SUBKEY)) + break; + first_subkey = node; + + /* Remove bogus subkey binding signatures: The only signatures + * allowed are of class 0x18 and 0x28. */ + for (node = first_subkey; node; node = node->next) + { + if (is_deleted_kbnode (node)) + continue; + if (node->pkt->pkttype == PKT_SIGNATURE + && !(IS_SUBKEY_SIG (node->pkt->pkt.signature) + || IS_SUBKEY_REV (node->pkt->pkt.signature))) + { + delete_kbnode (node); + if (sigs_cleaned) + ++*sigs_cleaned; + } + } + + /* Do the selected cleaning. */ + if (clean_level > KEY_CLEAN_NONE) + { + /* Clean enitre subkeys. */ + for (node = first_subkey; node; node = node->next) + { + if (is_deleted_kbnode (node)) + continue; + if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY + || node->pkt->pkttype == PKT_SECRET_SUBKEY) + { + if (clean_one_subkey (ctrl, node, noisy, clean_level)) + { + if (subkeys_cleaned) + ++*subkeys_cleaned; + } + } + } + + /* Clean duplicate signatures from a subkey. */ + for (node = first_subkey; node; node = node->next) + { + if (is_deleted_kbnode (node)) + continue; + if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY + || node->pkt->pkttype == PKT_SECRET_SUBKEY) + { + n = clean_one_subkey_dupsigs (ctrl, node); + if (sigs_cleaned) + *sigs_cleaned += n; + } + } + } +} diff --git a/g10/key-clean.h b/g10/key-clean.h new file mode 100644 index 0000000..a0fb769 --- /dev/null +++ b/g10/key-clean.h @@ -0,0 +1,52 @@ +/* key-clean.h - Functions to clean a keyblock + * Copyright (C) 2018 Werner Koch + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#ifndef GNUPG_G10_KEY_CLEAN_H +#define GNUPG_G10_KEY_CLEAN_H + +#include "gpg.h" + +/* No explict cleaning. */ +#define KEY_CLEAN_NONE 0 +/* Remove only invalid subkeys (ie. missing key-bindings) */ +#define KEY_CLEAN_INVALID 1 +/* Remove expired encryption keys */ +#define KEY_CLEAN_ENCR 2 +/* Remove expired authentication and encryption keys. */ +#define KEY_CLEAN_AUTHENCR 3 +/* Remove all expired subkeys. */ +#define KEY_CLEAN_ALL 4 + + +void mark_usable_uid_certs (ctrl_t ctrl, kbnode_t keyblock, kbnode_t uidnode, + u32 *main_kid, struct key_item *klist, + u32 curtime, u32 *next_expire); + +void clean_one_uid (ctrl_t ctrl, kbnode_t keyblock, kbnode_t uidnode, + int noisy, int self_only, + int *uids_cleaned, int *sigs_cleaned); +void clean_all_uids (ctrl_t ctrl, kbnode_t keyblock, int noisy, int self_only, + int *uids_cleaned,int *sigs_cleaned); +void clean_all_subkeys (ctrl_t ctrl, kbnode_t keyblock, + int noisy, int clean_level, + int *subkeys_cleaned, int *sigs_cleaned); + + +#endif /*GNUPG_G10_KEY_CLEAN_H*/ diff --git a/g10/keydb.c b/g10/keydb.c new file mode 100644 index 0000000..e538fe4 --- /dev/null +++ b/g10/keydb.c @@ -0,0 +1,2116 @@ +/* keydb.c - key database dispatcher + * Copyright (C) 2001-2013 Free Software Foundation, Inc. + * Coyrright (C) 2001-2015 Werner Koch + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gpg.h" +#include "../common/util.h" +#include "../common/sysutils.h" +#include "options.h" +#include "main.h" /*try_make_homedir ()*/ +#include "packet.h" +#include "keyring.h" +#include "../kbx/keybox.h" +#include "keydb.h" +#include "../common/i18n.h" + +static int active_handles; + +typedef enum + { + KEYDB_RESOURCE_TYPE_NONE = 0, + KEYDB_RESOURCE_TYPE_KEYRING, + KEYDB_RESOURCE_TYPE_KEYBOX + } KeydbResourceType; +#define MAX_KEYDB_RESOURCES 40 + +struct resource_item +{ + KeydbResourceType type; + union { + KEYRING_HANDLE kr; + KEYBOX_HANDLE kb; + } u; + void *token; +}; + +static struct resource_item all_resources[MAX_KEYDB_RESOURCES]; +static int used_resources; + +/* A pointer used to check for the primary key database by comparing + to the struct resource_item's TOKEN. */ +static void *primary_keydb; + +/* Whether we have successfully registered any resource. */ +static int any_registered; + +/* This is a simple cache used to return the last result of a + successful fingerprint search. This works only for keybox resources + because (due to lack of a copy_keyblock function) we need to store + an image of the keyblock which is fortunately instantly available + for keyboxes. */ +enum keyblock_cache_states { + KEYBLOCK_CACHE_EMPTY, + KEYBLOCK_CACHE_PREPARED, + KEYBLOCK_CACHE_FILLED +}; + +struct keyblock_cache { + enum keyblock_cache_states state; + byte fpr[MAX_FINGERPRINT_LEN]; + iobuf_t iobuf; /* Image of the keyblock. */ + int pk_no; + int uid_no; + /* Offset of the record in the keybox. */ + int resource; + off_t offset; +}; + + +struct keydb_handle +{ + /* When we locked all of the resources in ACTIVE (using keyring_lock + / keybox_lock, as appropriate). */ + int locked; + + /* If this flag is set a lock will only be released by + * keydb_release. */ + int keep_lock; + + /* The index into ACTIVE of the resources in which the last search + result was found. Initially -1. */ + int found; + + /* Initially -1 (invalid). This is used to save a search result and + later restore it as the selected result. */ + int saved_found; + + /* The number of skipped long blobs since the last search + (keydb_search_reset). */ + unsigned long skipped_long_blobs; + + /* If set, this disables the use of the keyblock cache. */ + int no_caching; + + /* Whether the next search will be from the beginning of the + database (and thus consider all records). */ + int is_reset; + + /* The "file position." In our case, this is index of the current + resource in ACTIVE. */ + int current; + + /* The number of resources in ACTIVE. */ + int used; + + /* Cache of the last found and parsed key block (only used for + keyboxes, not keyrings). */ + struct keyblock_cache keyblock_cache; + + /* Copy of ALL_RESOURCES when keydb_new is called. */ + struct resource_item active[MAX_KEYDB_RESOURCES]; +}; + +/* Looking up keys is expensive. To hide the cost, we cache whether + keys exist in the key database. Then, if we know a key does not + exist, we don't have to spend time looking it up. This + particularly helps the --list-sigs and --check-sigs commands. + + The cache stores the results in a hash using separate chaining. + Concretely: we use the LSB of the keyid to index the hash table and + each bucket consists of a linked list of entries. An entry + consists of the 64-bit key id. If a key id is not in the cache, + then we don't know whether it is in the DB or not. + + To simplify the cache consistency protocol, we simply flush the + whole cache whenever a key is inserted or updated. */ + +#define KID_NOT_FOUND_CACHE_BUCKETS 256 +static struct kid_not_found_cache_bucket * + kid_not_found_cache[KID_NOT_FOUND_CACHE_BUCKETS]; + +struct kid_not_found_cache_bucket +{ + struct kid_not_found_cache_bucket *next; + u32 kid[2]; +}; + +struct +{ + unsigned int count; /* The current number of entries in the hash table. */ + unsigned int peak; /* The peak of COUNT. */ + unsigned int flushes; /* The number of flushes. */ +} kid_not_found_stats; + +struct +{ + unsigned int handles; /* Number of handles created. */ + unsigned int locks; /* Number of locks taken. */ + unsigned int parse_keyblocks; /* Number of parse_keyblock_image calls. */ + unsigned int get_keyblocks; /* Number of keydb_get_keyblock calls. */ + unsigned int build_keyblocks; /* Number of build_keyblock_image calls. */ + unsigned int update_keyblocks;/* Number of update_keyblock calls. */ + unsigned int insert_keyblocks;/* Number of update_keyblock calls. */ + unsigned int delete_keyblocks;/* Number of delete_keyblock calls. */ + unsigned int search_resets; /* Number of keydb_search_reset calls. */ + unsigned int found; /* Number of successful keydb_search calls. */ + unsigned int found_cached; /* Ditto but from the cache. */ + unsigned int notfound; /* Number of failed keydb_search calls. */ + unsigned int notfound_cached; /* Ditto but from the cache. */ +} keydb_stats; + + +static int lock_all (KEYDB_HANDLE hd); +static void unlock_all (KEYDB_HANDLE hd); + + +/* Check whether the keyid KID is in key id is definitely not in the + database. + + Returns: + + 0 - Indeterminate: the key id is not in the cache; we don't know + whether the key is in the database or not. If you want a + definitive answer, you'll need to perform a lookup. + + 1 - There is definitely no key with this key id in the database. + We searched for a key with this key id previously, but we + didn't find it in the database. */ +static int +kid_not_found_p (u32 *kid) +{ + struct kid_not_found_cache_bucket *k; + + for (k = kid_not_found_cache[kid[0] % KID_NOT_FOUND_CACHE_BUCKETS]; k; k = k->next) + if (k->kid[0] == kid[0] && k->kid[1] == kid[1]) + { + if (DBG_CACHE) + log_debug ("keydb: kid_not_found_p (%08lx%08lx) => not in DB\n", + (ulong)kid[0], (ulong)kid[1]); + return 1; + } + + if (DBG_CACHE) + log_debug ("keydb: kid_not_found_p (%08lx%08lx) => indeterminate\n", + (ulong)kid[0], (ulong)kid[1]); + return 0; +} + + +/* Insert the keyid KID into the kid_not_found_cache. FOUND is whether + the key is in the key database or not. + + Note this function does not check whether the key id is already in + the cache. As such, kid_not_found_p() should be called first. */ +static void +kid_not_found_insert (u32 *kid) +{ + struct kid_not_found_cache_bucket *k; + + if (DBG_CACHE) + log_debug ("keydb: kid_not_found_insert (%08lx%08lx)\n", + (ulong)kid[0], (ulong)kid[1]); + k = xmalloc (sizeof *k); + k->kid[0] = kid[0]; + k->kid[1] = kid[1]; + k->next = kid_not_found_cache[kid[0] % KID_NOT_FOUND_CACHE_BUCKETS]; + kid_not_found_cache[kid[0] % KID_NOT_FOUND_CACHE_BUCKETS] = k; + kid_not_found_stats.count++; +} + + +/* Flush the kid not found cache. */ +static void +kid_not_found_flush (void) +{ + struct kid_not_found_cache_bucket *k, *knext; + int i; + + if (DBG_CACHE) + log_debug ("keydb: kid_not_found_flush\n"); + + if (!kid_not_found_stats.count) + return; + + for (i=0; i < DIM(kid_not_found_cache); i++) + { + for (k = kid_not_found_cache[i]; k; k = knext) + { + knext = k->next; + xfree (k); + } + kid_not_found_cache[i] = NULL; + } + if (kid_not_found_stats.count > kid_not_found_stats.peak) + kid_not_found_stats.peak = kid_not_found_stats.count; + kid_not_found_stats.count = 0; + kid_not_found_stats.flushes++; +} + + +static void +keyblock_cache_clear (struct keydb_handle *hd) +{ + hd->keyblock_cache.state = KEYBLOCK_CACHE_EMPTY; + iobuf_close (hd->keyblock_cache.iobuf); + hd->keyblock_cache.iobuf = NULL; + hd->keyblock_cache.resource = -1; + hd->keyblock_cache.offset = -1; +} + + +/* Handle the creation of a keyring or a keybox if it does not yet + exist. Take into account that other processes might have the + keyring/keybox already locked. This lock check does not work if + the directory itself is not yet available. If IS_BOX is true the + filename is expected to refer to a keybox. If FORCE_CREATE is true + the keyring or keybox will be created. + + Return 0 if it is okay to access the specified file. */ +static gpg_error_t +maybe_create_keyring_or_box (char *filename, int is_box, int force_create) +{ + gpg_err_code_t ec; + dotlock_t lockhd = NULL; + IOBUF iobuf; + int rc; + mode_t oldmask; + char *last_slash_in_filename; + char *bak_fname = NULL; + char *tmp_fname = NULL; + int save_slash; + + /* A quick test whether the filename already exists. */ + if (!gnupg_access (filename, F_OK)) + return !gnupg_access (filename, R_OK)? 0 : gpg_error (GPG_ERR_EACCES); + + /* If we don't want to create a new file at all, there is no need to + go any further - bail out right here. */ + if (!force_create) + return gpg_error (GPG_ERR_ENOENT); + + /* First of all we try to create the home directory. Note, that we + don't do any locking here because any sane application of gpg + would create the home directory by itself and not rely on gpg's + tricky auto-creation which is anyway only done for certain home + directory name pattern. */ + last_slash_in_filename = strrchr (filename, DIRSEP_C); +#if HAVE_W32_SYSTEM + { + /* Windows may either have a slash or a backslash. Take care of it. */ + char *p = strrchr (filename, '/'); + if (!last_slash_in_filename || p > last_slash_in_filename) + last_slash_in_filename = p; + } +#endif /*HAVE_W32_SYSTEM*/ + if (!last_slash_in_filename) + return gpg_error (GPG_ERR_ENOENT); /* No slash at all - should + not happen though. */ + save_slash = *last_slash_in_filename; + *last_slash_in_filename = 0; + if (gnupg_access(filename, F_OK)) + { + static int tried; + + if (!tried) + { + tried = 1; + try_make_homedir (filename); + } + if ((ec = gnupg_access (filename, F_OK))) + { + rc = gpg_error (ec); + *last_slash_in_filename = save_slash; + goto leave; + } + } + *last_slash_in_filename = save_slash; + + /* To avoid races with other instances of gpg 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 = dotlock_create (filename, 0); + if (!lockhd) + { + rc = gpg_error_from_syserror (); + /* 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': %s\n", + filename, gpg_strerror (rc)); + + if (!force_create) + return gpg_error (GPG_ERR_ENOENT); /* Won't happen. */ + else + return rc; + } + + if ( dotlock_take (lockhd, -1) ) + { + rc = gpg_error_from_syserror (); + /* This is something bad. Probably a stale lockfile. */ + log_info ("can't lock '%s': %s\n", filename, gpg_strerror (rc)); + goto leave; + } + + /* Now the real test while we are locked. */ + + /* Gpg either uses pubring.gpg or pubring.kbx and thus different + * lock files. Now, when one gpg process is updating a pubring.gpg + * and thus holding the corresponding lock, a second gpg process may + * get to here at the time between the two rename operation used by + * the first process to update pubring.gpg. The lock taken above + * may not protect the second process if it tries to create a + * pubring.kbx file which would be protected by a different lock + * file. + * + * We can detect this case by checking that the two temporary files + * used by the update code exist at the same time. In that case we + * do not create a new file but act as if FORCE_CREATE has not been + * given. Obviously there is a race between our two checks but the + * worst thing is that we won't create a new file, which is better + * than to accidentally creating one. */ + rc = keybox_tmp_names (filename, is_box, &bak_fname, &tmp_fname); + if (rc) + goto leave; + + if (!gnupg_access (filename, F_OK)) + { + rc = 0; /* Okay, we may access the file now. */ + goto leave; + } + if (!gnupg_access (bak_fname, F_OK) && !gnupg_access (tmp_fname, F_OK)) + { + /* Very likely another process is updating a pubring.gpg and we + should not create a pubring.kbx. */ + rc = gpg_error (GPG_ERR_ENOENT); + goto leave; + } + + + /* The file does not yet exist, create it now. */ + oldmask = umask (077); + if (is_secured_filename (filename)) + { + iobuf = NULL; + gpg_err_set_errno (EPERM); + } + else + iobuf = iobuf_create (filename, 0); + umask (oldmask); + if (!iobuf) + { + rc = gpg_error_from_syserror (); + if (is_box) + log_error (_("error creating keybox '%s': %s\n"), + filename, gpg_strerror (rc)); + else + log_error (_("error creating keyring '%s': %s\n"), + filename, gpg_strerror (rc)); + goto leave; + } + + iobuf_close (iobuf); + /* Must invalidate that ugly cache */ + iobuf_ioctl (NULL, IOBUF_IOCTL_INVALIDATE_CACHE, 0, filename); + + /* Make sure that at least one record is in a new keybox file, so + that the detection magic will work the next time it is used. */ + if (is_box) + { + estream_t fp = es_fopen (filename, "wb"); + if (!fp) + rc = gpg_error_from_syserror (); + else + { + rc = _keybox_write_header_blob (fp, 1); + es_fclose (fp); + } + if (rc) + { + if (is_box) + log_error (_("error creating keybox '%s': %s\n"), + filename, gpg_strerror (rc)); + else + log_error (_("error creating keyring '%s': %s\n"), + filename, gpg_strerror (rc)); + goto leave; + } + } + + if (!opt.quiet) + { + if (is_box) + log_info (_("keybox '%s' created\n"), filename); + else + log_info (_("keyring '%s' created\n"), filename); + } + + rc = 0; + + leave: + if (lockhd) + { + dotlock_release (lockhd); + dotlock_destroy (lockhd); + } + xfree (bak_fname); + xfree (tmp_fname); + return rc; +} + + +/* Helper for keydb_add_resource. Opens FILENAME to figure out the + resource type. + + Returns the specified file's likely type. If the file does not + exist, returns KEYDB_RESOURCE_TYPE_NONE and sets *R_FOUND to 0. + Otherwise, tries to figure out the file's type. This is either + KEYDB_RESOURCE_TYPE_KEYBOX, KEYDB_RESOURCE_TYPE_KEYRING or + KEYDB_RESOURCE_TYPE_KEYNONE. If the file is a keybox and it has + the OpenPGP flag set, then R_OPENPGP is also set. */ +static KeydbResourceType +rt_from_file (const char *filename, int *r_found, int *r_openpgp) +{ + u32 magic; + unsigned char verbuf[4]; + FILE *fp; + KeydbResourceType rt = KEYDB_RESOURCE_TYPE_NONE; + + *r_found = *r_openpgp = 0; + fp = gnupg_fopen (filename, "rb"); + if (fp) + { + *r_found = 1; + + if (fread (&magic, 4, 1, fp) == 1 ) + { + if (magic == 0x13579ace || magic == 0xce9a5713) + ; /* GDBM magic - not anymore supported. */ + else if (fread (&verbuf, 4, 1, fp) == 1 + && verbuf[0] == 1 + && fread (&magic, 4, 1, fp) == 1 + && !memcmp (&magic, "KBXf", 4)) + { + if ((verbuf[3] & 0x02)) + *r_openpgp = 1; + rt = KEYDB_RESOURCE_TYPE_KEYBOX; + } + else + rt = KEYDB_RESOURCE_TYPE_KEYRING; + } + else /* Maybe empty: assume keyring. */ + rt = KEYDB_RESOURCE_TYPE_KEYRING; + + fclose (fp); + } + + return rt; +} + +char * +keydb_search_desc_dump (struct keydb_search_desc *desc) +{ + char b[MAX_FORMATTED_FINGERPRINT_LEN + 1]; + char fpr[2 * MAX_FINGERPRINT_LEN + 1]; + + switch (desc->mode) + { + case KEYDB_SEARCH_MODE_EXACT: + return xasprintf ("EXACT: '%s'", desc->u.name); + case KEYDB_SEARCH_MODE_SUBSTR: + return xasprintf ("SUBSTR: '%s'", desc->u.name); + case KEYDB_SEARCH_MODE_MAIL: + return xasprintf ("MAIL: '%s'", desc->u.name); + case KEYDB_SEARCH_MODE_MAILSUB: + return xasprintf ("MAILSUB: '%s'", desc->u.name); + case KEYDB_SEARCH_MODE_MAILEND: + return xasprintf ("MAILEND: '%s'", desc->u.name); + case KEYDB_SEARCH_MODE_WORDS: + return xasprintf ("WORDS: '%s'", desc->u.name); + case KEYDB_SEARCH_MODE_SHORT_KID: + return xasprintf ("SHORT_KID: '%s'", + format_keyid (desc->u.kid, KF_SHORT, b, sizeof (b))); + case KEYDB_SEARCH_MODE_LONG_KID: + return xasprintf ("LONG_KID: '%s'", + format_keyid (desc->u.kid, KF_LONG, b, sizeof (b))); + case KEYDB_SEARCH_MODE_FPR16: + bin2hex (desc->u.fpr, 16, fpr); + return xasprintf ("FPR16: '%s'", + format_hexfingerprint (fpr, b, sizeof (b))); + case KEYDB_SEARCH_MODE_FPR20: + bin2hex (desc->u.fpr, 20, fpr); + return xasprintf ("FPR20: '%s'", + format_hexfingerprint (fpr, b, sizeof (b))); + case KEYDB_SEARCH_MODE_FPR: + bin2hex (desc->u.fpr, 20, fpr); + return xasprintf ("FPR: '%s'", + format_hexfingerprint (fpr, b, sizeof (b))); + case KEYDB_SEARCH_MODE_ISSUER: + return xasprintf ("ISSUER: '%s'", desc->u.name); + case KEYDB_SEARCH_MODE_ISSUER_SN: + return xasprintf ("ISSUER_SN: '%*s'", + (int) (desc->snlen == -1 + ? strlen (desc->sn) : desc->snlen), + desc->sn); + case KEYDB_SEARCH_MODE_SN: + return xasprintf ("SN: '%*s'", + (int) (desc->snlen == -1 + ? strlen (desc->sn) : desc->snlen), + desc->sn); + case KEYDB_SEARCH_MODE_SUBJECT: + return xasprintf ("SUBJECT: '%s'", desc->u.name); + case KEYDB_SEARCH_MODE_KEYGRIP: + return xasprintf ("KEYGRIP: %s", desc->u.grip); + case KEYDB_SEARCH_MODE_FIRST: + return xasprintf ("FIRST"); + case KEYDB_SEARCH_MODE_NEXT: + return xasprintf ("NEXT"); + default: + return xasprintf ("Bad search mode (%d)", desc->mode); + } +} + + + +/* Register a resource (keyring or keybox). The first keyring or + * keybox that is added using this function is created if it does not + * already exist and the KEYDB_RESOURCE_FLAG_READONLY is not set. + * + * FLAGS are a combination of the KEYDB_RESOURCE_FLAG_* constants. + * + * URL must have the following form: + * + * gnupg-ring:filename = plain keyring + * gnupg-kbx:filename = keybox file + * filename = check file's type (create as a plain keyring) + * + * Note: on systems with drive letters (Windows) invalid URLs (i.e., + * those with an unrecognized part before the ':' such as "c:\...") + * will silently be treated as bare filenames. On other systems, such + * URLs will cause this function to return GPG_ERR_GENERAL. + * + * If KEYDB_RESOURCE_FLAG_DEFAULT is set, the resource is a keyring + * and the file ends in ".gpg", then this function also checks if a + * file with the same name, but the extension ".kbx" exists, is a + * keybox and the OpenPGP flag is set. If so, this function opens + * that resource instead. + * + * If the file is not found, KEYDB_RESOURCE_FLAG_GPGVDEF is set and + * the URL ends in ".kbx", then this function will try opening the + * same URL, but with the extension ".gpg". If that file is a keybox + * with the OpenPGP flag set or it is a keyring, then we use that + * instead. + * + * If the file is not found, KEYDB_RESOURCE_FLAG_DEFAULT is set, the + * file should be created and the file's extension is ".gpg" then we + * replace the extension with ".kbx". + * + * If the KEYDB_RESOURCE_FLAG_PRIMARY is set and the resource is a + * keyring (not a keybox), then this resource is considered the + * primary resource. This is used by keydb_locate_writable(). If + * another primary keyring is set, then that keyring is considered the + * primary. + * + * If KEYDB_RESOURCE_FLAG_READONLY is set and the resource is a + * keyring (not a keybox), then the keyring is marked as read only and + * operations just as keyring_insert_keyblock will return + * GPG_ERR_ACCESS. */ +gpg_error_t +keydb_add_resource (const char *url, unsigned int flags) +{ + /* The file named by the URL (i.e., without the prototype). */ + const char *resname = url; + + char *filename = NULL; + int create; + int read_only = !!(flags&KEYDB_RESOURCE_FLAG_READONLY); + int is_default = !!(flags&KEYDB_RESOURCE_FLAG_DEFAULT); + int is_gpgvdef = !!(flags&KEYDB_RESOURCE_FLAG_GPGVDEF); + gpg_error_t err = 0; + KeydbResourceType rt = KEYDB_RESOURCE_TYPE_NONE; + void *token; + + /* Create the resource if it is the first registered one. */ + create = (!read_only && !any_registered); + + if (strlen (resname) > 11 && !strncmp( resname, "gnupg-ring:", 11) ) + { + rt = KEYDB_RESOURCE_TYPE_KEYRING; + resname += 11; + } + else if (strlen (resname) > 10 && !strncmp (resname, "gnupg-kbx:", 10) ) + { + rt = KEYDB_RESOURCE_TYPE_KEYBOX; + resname += 10; + } +#if !defined(HAVE_DRIVE_LETTERS) && !defined(__riscos__) + else if (strchr (resname, ':')) + { + log_error ("invalid key resource URL '%s'\n", url ); + err = gpg_error (GPG_ERR_GENERAL); + goto leave; + } +#endif /* !HAVE_DRIVE_LETTERS && !__riscos__ */ + + if (*resname != DIRSEP_C +#ifdef HAVE_W32_SYSTEM + && *resname != '/' /* Fixme: does not handle drive letters. */ +#endif + ) + { + /* Do tilde expansion etc. */ + if (strchr (resname, DIRSEP_C) +#ifdef HAVE_W32_SYSTEM + || strchr (resname, '/') /* Windows also accepts this. */ +#endif + ) + filename = make_filename (resname, NULL); + else + filename = make_filename (gnupg_homedir (), resname, NULL); + } + else + filename = xstrdup (resname); + + /* See whether we can determine the filetype. */ + if (rt == KEYDB_RESOURCE_TYPE_NONE) + { + int found, openpgp_flag; + int pass = 0; + size_t filenamelen; + + check_again: + filenamelen = strlen (filename); + rt = rt_from_file (filename, &found, &openpgp_flag); + if (found) + { + /* The file exists and we have the resource type in RT. + + Now let us check whether in addition to the "pubring.gpg" + a "pubring.kbx with openpgp keys exists. This is so that + GPG 2.1 will use an existing "pubring.kbx" by default iff + that file has been created or used by 2.1. This check is + needed because after creation or use of the kbx file with + 2.1 an older version of gpg may have created a new + pubring.gpg for its own use. */ + if (!pass && is_default && rt == KEYDB_RESOURCE_TYPE_KEYRING + && filenamelen > 4 && !strcmp (filename+filenamelen-4, ".gpg")) + { + strcpy (filename+filenamelen-4, ".kbx"); + if ((rt_from_file (filename, &found, &openpgp_flag) + == KEYDB_RESOURCE_TYPE_KEYBOX) && found && openpgp_flag) + rt = KEYDB_RESOURCE_TYPE_KEYBOX; + else /* Restore filename */ + strcpy (filename+filenamelen-4, ".gpg"); + } + } + else if (!pass && is_gpgvdef + && filenamelen > 4 && !strcmp (filename+filenamelen-4, ".kbx")) + { + /* Not found but gpgv's default "trustedkeys.kbx" file has + been requested. We did not found it so now check whether + a "trustedkeys.gpg" file exists and use that instead. */ + KeydbResourceType rttmp; + + strcpy (filename+filenamelen-4, ".gpg"); + rttmp = rt_from_file (filename, &found, &openpgp_flag); + if (found + && ((rttmp == KEYDB_RESOURCE_TYPE_KEYBOX && openpgp_flag) + || (rttmp == KEYDB_RESOURCE_TYPE_KEYRING))) + rt = rttmp; + else /* Restore filename */ + strcpy (filename+filenamelen-4, ".kbx"); + } + else if (!pass + && is_default && create + && filenamelen > 4 && !strcmp (filename+filenamelen-4, ".gpg")) + { + /* The file does not exist, the default resource has been + requested, the file shall be created, and the file has a + ".gpg" suffix. Change the suffix to ".kbx" and try once + more. This way we achieve that we open an existing + ".gpg" keyring, but create a new keybox file with an + ".kbx" suffix. */ + strcpy (filename+filenamelen-4, ".kbx"); + pass++; + goto check_again; + } + else /* No file yet: create keybox. */ + rt = KEYDB_RESOURCE_TYPE_KEYBOX; + } + + switch (rt) + { + case KEYDB_RESOURCE_TYPE_NONE: + log_error ("unknown type of key resource '%s'\n", url ); + err = gpg_error (GPG_ERR_GENERAL); + goto leave; + + case KEYDB_RESOURCE_TYPE_KEYRING: + err = maybe_create_keyring_or_box (filename, 0, create); + if (err) + goto leave; + + if (keyring_register_filename (filename, read_only, &token)) + { + if (used_resources >= MAX_KEYDB_RESOURCES) + err = gpg_error (GPG_ERR_RESOURCE_LIMIT); + else + { + if ((flags & KEYDB_RESOURCE_FLAG_PRIMARY)) + primary_keydb = token; + all_resources[used_resources].type = rt; + all_resources[used_resources].u.kr = NULL; /* Not used here */ + all_resources[used_resources].token = token; + 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 & KEYDB_RESOURCE_FLAG_PRIMARY)) + primary_keydb = token; + } + break; + + case KEYDB_RESOURCE_TYPE_KEYBOX: + { + err = maybe_create_keyring_or_box (filename, 1, create); + if (err) + goto leave; + + err = keybox_register_file (filename, 0, &token); + if (!err) + { + if (used_resources >= MAX_KEYDB_RESOURCES) + err = gpg_error (GPG_ERR_RESOURCE_LIMIT); + else + { + KEYBOX_HANDLE kbxhd; + + if ((flags & KEYDB_RESOURCE_FLAG_PRIMARY)) + primary_keydb = token; + all_resources[used_resources].type = rt; + all_resources[used_resources].u.kb = NULL; /* Not used here */ + all_resources[used_resources].token = token; + + /* Do a compress run if needed and no other user is + * currently using the keybox. */ + kbxhd = keybox_new_openpgp (token, 0); + if (kbxhd) + { + if (!keybox_lock (kbxhd, 1, 0)) + { + keybox_compress (kbxhd); + keybox_lock (kbxhd, 0, 0); + } + + keybox_release (kbxhd); + } + + used_resources++; + } + } + else if (gpg_err_code (err) == GPG_ERR_EEXIST) + { + /* Already registered. We will mark it as the primary key + if requested. */ + if ((flags & KEYDB_RESOURCE_FLAG_PRIMARY)) + primary_keydb = token; + } + } + break; + + default: + log_error ("resource type of '%s' not supported\n", url); + err = gpg_error (GPG_ERR_GENERAL); + goto leave; + } + + /* fixme: check directory permissions and print a warning */ + + leave: + if (err) + { + log_error (_("keyblock resource '%s': %s\n"), + filename, gpg_strerror (err)); + write_status_error ("add_keyblock_resource", err); + } + else + any_registered = 1; + xfree (filename); + return err; +} + + +void +keydb_dump_stats (void) +{ + log_info ("keydb: handles=%u locks=%u parse=%u get=%u\n", + keydb_stats.handles, + keydb_stats.locks, + keydb_stats.parse_keyblocks, + keydb_stats.get_keyblocks); + log_info (" build=%u update=%u insert=%u delete=%u\n", + keydb_stats.build_keyblocks, + keydb_stats.update_keyblocks, + keydb_stats.insert_keyblocks, + keydb_stats.delete_keyblocks); + log_info (" reset=%u found=%u not=%u cache=%u not=%u\n", + keydb_stats.search_resets, + keydb_stats.found, + keydb_stats.notfound, + keydb_stats.found_cached, + keydb_stats.notfound_cached); + log_info ("kid_not_found_cache: count=%u peak=%u flushes=%u\n", + kid_not_found_stats.count, + kid_not_found_stats.peak, + kid_not_found_stats.flushes); +} + + +/* Create a new database handle. A database handle is similar to a + file handle: it contains a local file position. This is used when + searching: subsequent searches resume where the previous search + left off. To rewind the position, use keydb_search_reset(). This + function returns NULL on error, sets ERRNO, and prints an error + diagnostic. */ +KEYDB_HANDLE +keydb_new (void) +{ + KEYDB_HANDLE hd; + int i, j; + int die = 0; + int reterrno; + + if (DBG_CLOCK) + log_clock ("keydb_new"); + + hd = xtrycalloc (1, sizeof *hd); + if (!hd) + goto leave; + hd->found = -1; + hd->saved_found = -1; + hd->is_reset = 1; + + log_assert (used_resources <= MAX_KEYDB_RESOURCES); + for (i=j=0; ! die && i < used_resources; i++) + { + 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].u.kr = keyring_new (all_resources[i].token); + if (!hd->active[j].u.kr) + { + reterrno = errno; + die = 1; + } + j++; + break; + case KEYDB_RESOURCE_TYPE_KEYBOX: + hd->active[j].type = all_resources[i].type; + hd->active[j].token = all_resources[i].token; + hd->active[j].u.kb = keybox_new_openpgp (all_resources[i].token, 0); + if (!hd->active[j].u.kb) + { + reterrno = errno; + die = 1; + } + j++; + break; + } + } + hd->used = j; + + active_handles++; + keydb_stats.handles++; + + if (die) + { + keydb_release (hd); + gpg_err_set_errno (reterrno); + hd = NULL; + } + + leave: + if (!hd) + log_error (_("error opening key DB: %s\n"), + gpg_strerror (gpg_error_from_syserror())); + + return hd; +} + + +void +keydb_release (KEYDB_HANDLE hd) +{ + int i; + + if (!hd) + return; + log_assert (active_handles > 0); + active_handles--; + + hd->keep_lock = 0; + 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; + case KEYDB_RESOURCE_TYPE_KEYBOX: + keybox_release (hd->active[i].u.kb); + break; + } + } + + keyblock_cache_clear (hd); + xfree (hd); +} + + +/* Take a lock on the files immediately and not only during insert or + * update. This lock is released with keydb_release. */ +gpg_error_t +keydb_lock (KEYDB_HANDLE hd) +{ + gpg_error_t err; + + if (!hd) + return gpg_error (GPG_ERR_INV_ARG); + + err = lock_all (hd); + if (!err) + hd->keep_lock = 1; + + return err; +} + + +/* Set a flag on the handle to suppress use of cached results. This + * is required for updating a keyring and for key listings. Fixme: + * Using a new parameter for keydb_new might be a better solution. */ +void +keydb_disable_caching (KEYDB_HANDLE hd) +{ + if (hd) + hd->no_caching = 1; +} + + +/* Return the file name of the resource in which the current search + * result was found or, if there is no search result, the filename of + * the current resource (i.e., the resource that the file position + * points to). Note: the filename is not necessarily the URL used to + * open it! + * + * This function only returns 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; + case KEYDB_RESOURCE_TYPE_KEYBOX: + s = keybox_get_resource_name (hd->active[idx].u.kb); + break; + } + + return s? s: ""; +} + + + +static int +lock_all (KEYDB_HANDLE hd) +{ + int i, rc = 0; + + /* Fixme: This locking scheme may lead to a deadlock if the resources + are not added in the same order by all processes. We are + currently only allowing one resource so it is not a problem. + [Oops: Who claimed the latter] + + To fix this we need to use a lock file to protect lock_all. */ + + 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; + case KEYDB_RESOURCE_TYPE_KEYBOX: + rc = keybox_lock (hd->active[i].u.kb, 1, -1); + break; + } + } + + if (rc) + { + /* Revert the already taken 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; + case KEYDB_RESOURCE_TYPE_KEYBOX: + keybox_lock (hd->active[i].u.kb, 0, 0); + break; + } + } + } + else + { + hd->locked = 1; + keydb_stats.locks++; + } + + return rc; +} + + +static void +unlock_all (KEYDB_HANDLE hd) +{ + int i; + + if (!hd->locked || hd->keep_lock) + 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; + case KEYDB_RESOURCE_TYPE_KEYBOX: + keybox_lock (hd->active[i].u.kb, 0, 0); + break; + } + } + hd->locked = 0; +} + + + +/* Save the last found state and invalidate the current selection + * (i.e., the entry selected by keydb_search() is invalidated and + * something like keydb_get_keyblock() will return an error). This + * does not change the file position. This makes it possible to do + * something like: + * + * keydb_search (hd, ...); // Result 1. + * keydb_push_found_state (hd); + * keydb_search_reset (hd); + * keydb_search (hd, ...); // Result 2. + * keydb_pop_found_state (hd); + * keydb_get_keyblock (hd, ...); // -> Result 1. + * + * Note: it is only possible to save a single save state at a time. + * In other words, the save stack only has room for a single + * instance of the state. */ +void +keydb_push_found_state (KEYDB_HANDLE hd) +{ + if (!hd) + return; + + if (hd->found < 0 || hd->found >= hd->used) + { + hd->saved_found = -1; + return; + } + + switch (hd->active[hd->found].type) + { + case KEYDB_RESOURCE_TYPE_NONE: + break; + case KEYDB_RESOURCE_TYPE_KEYRING: + keyring_push_found_state (hd->active[hd->found].u.kr); + break; + case KEYDB_RESOURCE_TYPE_KEYBOX: + keybox_push_found_state (hd->active[hd->found].u.kb); + break; + } + + hd->saved_found = hd->found; + hd->found = -1; +} + + +/* Restore the previous save state. If the saved state is NULL or + invalid, this is a NOP. */ +void +keydb_pop_found_state (KEYDB_HANDLE hd) +{ + if (!hd) + return; + + hd->found = hd->saved_found; + hd->saved_found = -1; + if (hd->found < 0 || hd->found >= hd->used) + return; + + switch (hd->active[hd->found].type) + { + case KEYDB_RESOURCE_TYPE_NONE: + break; + case KEYDB_RESOURCE_TYPE_KEYRING: + keyring_pop_found_state (hd->active[hd->found].u.kr); + break; + case KEYDB_RESOURCE_TYPE_KEYBOX: + keybox_pop_found_state (hd->active[hd->found].u.kb); + break; + } +} + + + +static gpg_error_t +parse_keyblock_image (iobuf_t iobuf, int pk_no, int uid_no, + kbnode_t *r_keyblock) +{ + gpg_error_t err; + struct parse_packet_ctx_s parsectx; + PACKET *pkt; + kbnode_t keyblock = NULL; + kbnode_t node, *tail; + int in_cert, save_mode; + int pk_count, uid_count; + + *r_keyblock = NULL; + + pkt = xtrymalloc (sizeof *pkt); + if (!pkt) + return gpg_error_from_syserror (); + init_packet (pkt); + init_parse_packet (&parsectx, iobuf); + save_mode = set_packet_list_mode (0); + in_cert = 0; + tail = NULL; + pk_count = uid_count = 0; + while ((err = parse_packet (&parsectx, pkt)) != -1) + { + if (gpg_err_code (err) == GPG_ERR_UNKNOWN_PACKET) + { + free_packet (pkt, &parsectx); + init_packet (pkt); + continue; + } + if (err) + { + es_fflush (es_stdout); + log_error ("parse_keyblock_image: read error: %s\n", + gpg_strerror (err)); + if (gpg_err_code (err) == GPG_ERR_INV_PACKET) + { + free_packet (pkt, &parsectx); + init_packet (pkt); + continue; + } + /* Unknown version maybe due to v5 keys - we treat this + * error different. */ + if (gpg_err_code (err) != GPG_ERR_UNKNOWN_VERSION) + err = gpg_error (GPG_ERR_INV_KEYRING); + break; + } + + /* Filter allowed packets. */ + switch (pkt->pkttype) + { + case PKT_PUBLIC_KEY: + case PKT_PUBLIC_SUBKEY: + case PKT_SECRET_KEY: + case PKT_SECRET_SUBKEY: + case PKT_USER_ID: + case PKT_ATTRIBUTE: + case PKT_SIGNATURE: + case PKT_RING_TRUST: + break; /* Allowed per RFC. */ + + default: + log_info ("skipped packet of type %d in keybox\n", (int)pkt->pkttype); + free_packet(pkt, &parsectx); + init_packet(pkt); + continue; + } + + /* Other sanity checks. */ + if (!in_cert && pkt->pkttype != PKT_PUBLIC_KEY) + { + log_error ("parse_keyblock_image: first packet in a keybox blob " + "is not a public key packet\n"); + err = gpg_error (GPG_ERR_INV_KEYRING); + break; + } + if (in_cert && (pkt->pkttype == PKT_PUBLIC_KEY + || pkt->pkttype == PKT_SECRET_KEY)) + { + log_error ("parse_keyblock_image: " + "multiple keyblocks in a keybox blob\n"); + err = gpg_error (GPG_ERR_INV_KEYRING); + break; + } + in_cert = 1; + + node = new_kbnode (pkt); + + switch (pkt->pkttype) + { + case PKT_PUBLIC_KEY: + case PKT_PUBLIC_SUBKEY: + case PKT_SECRET_KEY: + case PKT_SECRET_SUBKEY: + if (++pk_count == pk_no) + node->flag |= 1; + break; + + case PKT_USER_ID: + if (++uid_count == uid_no) + node->flag |= 2; + break; + + default: + break; + } + + if (!keyblock) + keyblock = node; + else + *tail = node; + tail = &node->next; + pkt = xtrymalloc (sizeof *pkt); + if (!pkt) + { + err = gpg_error_from_syserror (); + break; + } + init_packet (pkt); + } + set_packet_list_mode (save_mode); + + if (err == -1 && keyblock) + err = 0; /* Got the entire keyblock. */ + + if (err) + release_kbnode (keyblock); + else + { + *r_keyblock = keyblock; + keydb_stats.parse_keyblocks++; + } + free_packet (pkt, &parsectx); + deinit_parse_packet (&parsectx); + xfree (pkt); + return err; +} + + +/* Return the keyblock last found by keydb_search() in *RET_KB. + * + * On success, the function returns 0 and the caller must free *RET_KB + * using release_kbnode(). Otherwise, the function returns an error + * code. + * + * The returned keyblock has the kbnode 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. */ +gpg_error_t +keydb_get_keyblock (KEYDB_HANDLE hd, KBNODE *ret_kb) +{ + gpg_error_t err = 0; + + *ret_kb = NULL; + + if (!hd) + return gpg_error (GPG_ERR_INV_ARG); + + if (DBG_CLOCK) + log_clock ("keydb_get_keybock enter"); + + if (hd->keyblock_cache.state == KEYBLOCK_CACHE_FILLED) + { + err = iobuf_seek (hd->keyblock_cache.iobuf, 0); + if (err) + { + log_error ("keydb_get_keyblock: failed to rewind iobuf for cache\n"); + keyblock_cache_clear (hd); + } + else + { + err = parse_keyblock_image (hd->keyblock_cache.iobuf, + hd->keyblock_cache.pk_no, + hd->keyblock_cache.uid_no, + ret_kb); + if (err) + keyblock_cache_clear (hd); + if (DBG_CLOCK) + log_clock (err? "keydb_get_keyblock leave (cached, failed)" + : "keydb_get_keyblock leave (cached)"); + return err; + } + } + + if (hd->found < 0 || hd->found >= hd->used) + return gpg_error (GPG_ERR_VALUE_NOT_FOUND); + + switch (hd->active[hd->found].type) + { + case KEYDB_RESOURCE_TYPE_NONE: + err = gpg_error (GPG_ERR_GENERAL); /* oops */ + break; + case KEYDB_RESOURCE_TYPE_KEYRING: + err = keyring_get_keyblock (hd->active[hd->found].u.kr, ret_kb); + break; + case KEYDB_RESOURCE_TYPE_KEYBOX: + { + iobuf_t iobuf; + int pk_no, uid_no; + + err = keybox_get_keyblock (hd->active[hd->found].u.kb, + &iobuf, &pk_no, &uid_no); + if (!err) + { + err = parse_keyblock_image (iobuf, pk_no, uid_no, ret_kb); + if (!err && hd->keyblock_cache.state == KEYBLOCK_CACHE_PREPARED) + { + hd->keyblock_cache.state = KEYBLOCK_CACHE_FILLED; + hd->keyblock_cache.iobuf = iobuf; + hd->keyblock_cache.pk_no = pk_no; + hd->keyblock_cache.uid_no = uid_no; + } + else + { + iobuf_close (iobuf); + } + } + } + break; + } + + if (hd->keyblock_cache.state != KEYBLOCK_CACHE_FILLED) + keyblock_cache_clear (hd); + + if (!err) + keydb_stats.get_keyblocks++; + + if (DBG_CLOCK) + log_clock (err? "keydb_get_keyblock leave (failed)" + : "keydb_get_keyblock leave"); + return err; +} + + +/* Build a keyblock image from KEYBLOCK. Returns 0 on success and + * only then stores a new iobuf object at R_IOBUF. */ +static gpg_error_t +build_keyblock_image (kbnode_t keyblock, iobuf_t *r_iobuf) +{ + gpg_error_t err; + iobuf_t iobuf; + kbnode_t kbctx, node; + + *r_iobuf = NULL; + + iobuf = iobuf_temp (); + for (kbctx = NULL; (node = walk_kbnode (keyblock, &kbctx, 0));) + { + /* Make sure to use only packets valid on a keyblock. */ + switch (node->pkt->pkttype) + { + case PKT_PUBLIC_KEY: + case PKT_PUBLIC_SUBKEY: + case PKT_SIGNATURE: + case PKT_USER_ID: + case PKT_ATTRIBUTE: + case PKT_RING_TRUST: + break; + default: + continue; + } + + err = build_packet_and_meta (iobuf, node->pkt); + if (err) + { + iobuf_close (iobuf); + return err; + } + } + + keydb_stats.build_keyblocks++; + *r_iobuf = iobuf; + return 0; +} + + +/* Update the keyblock KB (i.e., extract the fingerprint and find the + * corresponding keyblock in the keyring). + * + * This doesn't do anything if --dry-run was specified. + * + * Returns 0 on success. Otherwise, it returns an error code. Note: + * if there isn't a keyblock in the keyring corresponding to KB, then + * this function returns GPG_ERR_VALUE_NOT_FOUND. + * + * This function selects the matching record and modifies the current + * file position to point to the record just after the selected entry. + * Thus, if you do a subsequent search using HD, you should first do a + * keydb_search_reset. Further, if the selected record is important, + * you should use keydb_push_found_state and keydb_pop_found_state to + * save and restore it. */ +gpg_error_t +keydb_update_keyblock (ctrl_t ctrl, KEYDB_HANDLE hd, kbnode_t kb) +{ + gpg_error_t err; + PKT_public_key *pk; + KEYDB_SEARCH_DESC desc; + size_t len; + + log_assert (kb); + log_assert (kb->pkt->pkttype == PKT_PUBLIC_KEY); + pk = kb->pkt->pkt.public_key; + + if (!hd) + return gpg_error (GPG_ERR_INV_ARG); + + kid_not_found_flush (); + keyblock_cache_clear (hd); + + if (opt.dry_run) + return 0; + + err = lock_all (hd); + if (err) + return err; + +#ifdef USE_TOFU + tofu_notice_key_changed (ctrl, kb); +#endif + + memset (&desc, 0, sizeof (desc)); + fingerprint_from_pk (pk, desc.u.fpr, &len); + if (len == 20) + desc.mode = KEYDB_SEARCH_MODE_FPR20; + else + log_bug ("%s: Unsupported key length: %zu\n", __func__, len); + + keydb_search_reset (hd); + err = keydb_search (hd, &desc, 1, NULL); + if (err) + return gpg_error (GPG_ERR_VALUE_NOT_FOUND); + log_assert (hd->found >= 0 && hd->found < hd->used); + + switch (hd->active[hd->found].type) + { + case KEYDB_RESOURCE_TYPE_NONE: + err = gpg_error (GPG_ERR_GENERAL); /* oops */ + break; + case KEYDB_RESOURCE_TYPE_KEYRING: + err = keyring_update_keyblock (hd->active[hd->found].u.kr, kb); + break; + case KEYDB_RESOURCE_TYPE_KEYBOX: + { + iobuf_t iobuf; + + err = build_keyblock_image (kb, &iobuf); + if (!err) + { + err = keybox_update_keyblock (hd->active[hd->found].u.kb, + iobuf_get_temp_buffer (iobuf), + iobuf_get_temp_length (iobuf)); + iobuf_close (iobuf); + } + } + break; + } + + unlock_all (hd); + if (!err) + keydb_stats.update_keyblocks++; + return err; +} + + +/* Insert a keyblock into one of the underlying keyrings or keyboxes. + * + * Be default, the keyring / keybox from which the last search result + * came is used. If there was no previous search result (or + * keydb_search_reset was called), then the keyring / keybox where the + * next search would start is used (i.e., the current file position). + * + * Note: this doesn't do anything if --dry-run was specified. + * + * Returns 0 on success. Otherwise, it returns an error code. */ +gpg_error_t +keydb_insert_keyblock (KEYDB_HANDLE hd, kbnode_t kb) +{ + gpg_error_t err; + int idx; + + if (!hd) + return gpg_error (GPG_ERR_INV_ARG); + + kid_not_found_flush (); + keyblock_cache_clear (hd); + + if (opt.dry_run) + return 0; + + if (hd->found >= 0 && hd->found < hd->used) + idx = hd->found; + else if (hd->current >= 0 && hd->current < hd->used) + idx = hd->current; + else + return gpg_error (GPG_ERR_GENERAL); + + err = lock_all (hd); + if (err) + return err; + + switch (hd->active[idx].type) + { + case KEYDB_RESOURCE_TYPE_NONE: + err = gpg_error (GPG_ERR_GENERAL); /* oops */ + break; + case KEYDB_RESOURCE_TYPE_KEYRING: + err = keyring_insert_keyblock (hd->active[idx].u.kr, kb); + break; + case KEYDB_RESOURCE_TYPE_KEYBOX: + { /* We need to turn our kbnode_t list of packets into a proper + keyblock first. This is required by the OpenPGP key parser + included in the keybox code. Eventually we can change this + kludge to have the caller pass the image. */ + iobuf_t iobuf; + + err = build_keyblock_image (kb, &iobuf); + if (!err) + { + err = keybox_insert_keyblock (hd->active[idx].u.kb, + iobuf_get_temp_buffer (iobuf), + iobuf_get_temp_length (iobuf)); + iobuf_close (iobuf); + } + } + break; + } + + unlock_all (hd); + if (!err) + keydb_stats.insert_keyblocks++; + return err; +} + + +/* Delete the currently selected keyblock. If you haven't done a + * search yet on this database handle (or called keydb_search_reset), + * then this will return an error. + * + * Returns 0 on success or an error code, if an error occurs. */ +gpg_error_t +keydb_delete_keyblock (KEYDB_HANDLE hd) +{ + gpg_error_t rc; + + if (!hd) + return gpg_error (GPG_ERR_INV_ARG); + + kid_not_found_flush (); + keyblock_cache_clear (hd); + + if (hd->found < 0 || hd->found >= hd->used) + return gpg_error (GPG_ERR_VALUE_NOT_FOUND); + + if (opt.dry_run) + return 0; + + rc = lock_all (hd); + if (rc) + return rc; + + switch (hd->active[hd->found].type) + { + case KEYDB_RESOURCE_TYPE_NONE: + rc = gpg_error (GPG_ERR_GENERAL); + break; + case KEYDB_RESOURCE_TYPE_KEYRING: + rc = keyring_delete_keyblock (hd->active[hd->found].u.kr); + break; + case KEYDB_RESOURCE_TYPE_KEYBOX: + rc = keybox_delete (hd->active[hd->found].u.kb); + break; + } + + unlock_all (hd); + if (!rc) + keydb_stats.delete_keyblocks++; + return rc; +} + + + +/* A database may consists of multiple keyrings / key boxes. This + * sets the "file position" to the start of the first keyring / key + * box that is writable (i.e., doesn't have the read-only flag set). + * + * This first tries the primary keyring (the last keyring (not + * keybox!) added using keydb_add_resource() and with + * KEYDB_RESOURCE_FLAG_PRIMARY set). If that is not writable, then it + * tries the keyrings / keyboxes in the order in which they were + * added. */ +gpg_error_t +keydb_locate_writable (KEYDB_HANDLE hd) +{ + gpg_error_t rc; + + if (!hd) + return GPG_ERR_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_keydb) + { + for ( ; hd->current >= 0 && hd->current < hd->used; hd->current++) + { + if(hd->active[hd->current].token == primary_keydb) + { + 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; + case KEYDB_RESOURCE_TYPE_KEYBOX: + if (keybox_is_writable (hd->active[hd->current].token)) + return 0; /* found (hd->current is set to it) */ + break; + } + } + + return gpg_error (GPG_ERR_NOT_FOUND); +} + + +/* Rebuild the on-disk caches of all key resources. */ +void +keydb_rebuild_caches (ctrl_t ctrl, int noisy) +{ + int i, rc; + + for (i=0; i < used_resources; i++) + { + if (!keyring_is_writable (all_resources[i].token)) + continue; + switch (all_resources[i].type) + { + case KEYDB_RESOURCE_TYPE_NONE: /* ignore */ + break; + case KEYDB_RESOURCE_TYPE_KEYRING: + rc = keyring_rebuild_cache (ctrl, all_resources[i].token,noisy); + if (rc) + log_error (_("failed to rebuild keyring cache: %s\n"), + gpg_strerror (rc)); + break; + case KEYDB_RESOURCE_TYPE_KEYBOX: + /* N/A. */ + break; + } + } +} + + +/* Return the number of skipped blocks (because they were to large to + read from a keybox) since the last search reset. */ +unsigned long +keydb_get_skipped_counter (KEYDB_HANDLE hd) +{ + return hd ? hd->skipped_long_blobs : 0; +} + + +/* Clears the current search result and resets the handle's position + * so that the next search starts at the beginning of the database + * (the start of the first resource). + * + * Returns 0 on success and an error code if an error occurred. + * (Currently, this function always returns 0 if HD is valid.) */ +gpg_error_t +keydb_search_reset (KEYDB_HANDLE hd) +{ + gpg_error_t rc = 0; + int i; + + if (!hd) + return gpg_error (GPG_ERR_INV_ARG); + + keyblock_cache_clear (hd); + + if (DBG_CLOCK) + log_clock ("keydb_search_reset"); + + if (DBG_CACHE) + log_debug ("keydb_search: reset (hd=%p)", hd); + + hd->skipped_long_blobs = 0; + hd->current = 0; + hd->found = -1; + /* Now 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; + case KEYDB_RESOURCE_TYPE_KEYBOX: + rc = keybox_search_reset (hd->active[i].u.kb); + break; + } + } + hd->is_reset = 1; + if (!rc) + keydb_stats.search_resets++; + return rc; +} + + +/* Search the database for keys matching the search description. If + * the DB contains any legacy keys, these are silently ignored. + * + * DESC is an array of search terms with NDESC entries. The search + * terms are or'd together. That is, the next entry in the DB that + * matches any of the descriptions will be returned. + * + * Note: this function resumes searching where the last search left + * off (i.e., at the current file position). If you want to search + * from the start of the database, then you need to first call + * keydb_search_reset(). + * + * If no key matches the search description, returns + * GPG_ERR_NOT_FOUND. If there was a match, returns 0. If an error + * occurred, returns an error code. + * + * The returned key is considered to be selected and the raw data can, + * for instance, be returned by calling keydb_get_keyblock(). */ +gpg_error_t +keydb_search (KEYDB_HANDLE hd, KEYDB_SEARCH_DESC *desc, + size_t ndesc, size_t *descindex) +{ + int i; + gpg_error_t rc; + int was_reset = hd->is_reset; + /* If an entry is already in the cache, then don't add it again. */ + int already_in_cache = 0; + + if (descindex) + *descindex = 0; /* Make sure it is always set on return. */ + + if (!hd) + return gpg_error (GPG_ERR_INV_ARG); + + if (!any_registered) + { + write_status_error ("keydb_search", gpg_error (GPG_ERR_KEYRING_OPEN)); + return gpg_error (GPG_ERR_NOT_FOUND); + } + + if (DBG_CLOCK) + log_clock ("keydb_search enter"); + + if (DBG_LOOKUP) + { + log_debug ("%s: %zd search descriptions:\n", __func__, ndesc); + for (i = 0; i < ndesc; i ++) + { + char *t = keydb_search_desc_dump (&desc[i]); + log_debug ("%s %d: %s\n", __func__, i, t); + xfree (t); + } + } + + + if (ndesc == 1 && desc[0].mode == KEYDB_SEARCH_MODE_LONG_KID + && (already_in_cache = kid_not_found_p (desc[0].u.kid)) == 1 ) + { + if (DBG_CLOCK) + log_clock ("keydb_search leave (not found, cached)"); + keydb_stats.notfound_cached++; + return gpg_error (GPG_ERR_NOT_FOUND); + } + + /* NB: If one of the exact search modes below is used in a loop to + walk over all keys (with the same fingerprint) the caching must + have been disabled for the handle. */ + if (!hd->no_caching + && ndesc == 1 + && (desc[0].mode == KEYDB_SEARCH_MODE_FPR20 + || desc[0].mode == KEYDB_SEARCH_MODE_FPR) + && hd->keyblock_cache.state == KEYBLOCK_CACHE_FILLED + && !memcmp (hd->keyblock_cache.fpr, desc[0].u.fpr, 20) + /* Make sure the current file position occurs before the cached + result to avoid an infinite loop. */ + && (hd->current < hd->keyblock_cache.resource + || (hd->current == hd->keyblock_cache.resource + && (keybox_offset (hd->active[hd->current].u.kb) + <= hd->keyblock_cache.offset)))) + { + /* (DESCINDEX is already set). */ + if (DBG_CLOCK) + log_clock ("keydb_search leave (cached)"); + + hd->current = hd->keyblock_cache.resource; + /* HD->KEYBLOCK_CACHE.OFFSET is the last byte in the record. + Seek just beyond that. */ + keybox_seek (hd->active[hd->current].u.kb, + hd->keyblock_cache.offset + 1); + keydb_stats.found_cached++; + return 0; + } + + rc = -1; + while ((rc == -1 || gpg_err_code (rc) == GPG_ERR_EOF) + && hd->current >= 0 && hd->current < hd->used) + { + if (DBG_LOOKUP) + log_debug ("%s: searching %s (resource %d of %d)\n", + __func__, + hd->active[hd->current].type == KEYDB_RESOURCE_TYPE_KEYRING + ? "keyring" + : (hd->active[hd->current].type == KEYDB_RESOURCE_TYPE_KEYBOX + ? "keybox" : "unknown type"), + 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, 1); + break; + case KEYDB_RESOURCE_TYPE_KEYBOX: + do + rc = keybox_search (hd->active[hd->current].u.kb, desc, + ndesc, KEYBOX_BLOBTYPE_PGP, + descindex, &hd->skipped_long_blobs); + while (gpg_err_code (rc) == GPG_ERR_LEGACY_KEY + || gpg_err_code (rc) == GPG_ERR_UNKNOWN_VERSION) + ; + break; + } + + if (DBG_LOOKUP) + log_debug ("%s: searched %s (resource %d of %d) => %s\n", + __func__, + hd->active[hd->current].type == KEYDB_RESOURCE_TYPE_KEYRING + ? "keyring" + : (hd->active[hd->current].type == KEYDB_RESOURCE_TYPE_KEYBOX + ? "keybox" : "unknown type"), + hd->current, hd->used, + rc == -1 ? "EOF" : gpg_strerror (rc)); + + if (rc == -1 || gpg_err_code (rc) == GPG_ERR_EOF) + { + /* EOF -> switch to next resource */ + hd->current++; + } + else if (!rc) + hd->found = hd->current; + } + hd->is_reset = 0; + + rc = ((rc == -1 || gpg_err_code (rc) == GPG_ERR_EOF) + ? gpg_error (GPG_ERR_NOT_FOUND) + : rc); + + keyblock_cache_clear (hd); + if (!hd->no_caching + && !rc + && ndesc == 1 && (desc[0].mode == KEYDB_SEARCH_MODE_FPR20 + || desc[0].mode == KEYDB_SEARCH_MODE_FPR) + && hd->active[hd->current].type == KEYDB_RESOURCE_TYPE_KEYBOX) + { + hd->keyblock_cache.state = KEYBLOCK_CACHE_PREPARED; + hd->keyblock_cache.resource = hd->current; + /* The current offset is at the start of the next record. Since + a record is at least 1 byte, we just use offset - 1, which is + within the record. */ + hd->keyblock_cache.offset + = keybox_offset (hd->active[hd->current].u.kb) - 1; + memcpy (hd->keyblock_cache.fpr, desc[0].u.fpr, 20); + } + + if (gpg_err_code (rc) == GPG_ERR_NOT_FOUND + && ndesc == 1 && desc[0].mode == KEYDB_SEARCH_MODE_LONG_KID && was_reset + && !already_in_cache) + kid_not_found_insert (desc[0].u.kid); + + if (DBG_CLOCK) + log_clock (rc? "keydb_search leave (not found)" + : "keydb_search leave (found)"); + if (!rc) + keydb_stats.found++; + else + keydb_stats.notfound++; + return rc; +} + + +/* Return the first non-legacy key in the database. + * + * If you want the very first key in the database, you can directly + * call keydb_search with the search description + * KEYDB_SEARCH_MODE_FIRST. */ +gpg_error_t +keydb_search_first (KEYDB_HANDLE hd) +{ + gpg_error_t err; + KEYDB_SEARCH_DESC desc; + + err = keydb_search_reset (hd); + if (err) + return err; + + memset (&desc, 0, sizeof desc); + desc.mode = KEYDB_SEARCH_MODE_FIRST; + return keydb_search (hd, &desc, 1, NULL); +} + + +/* Return the next key (not the next matching key!). + * + * Unlike calling keydb_search with KEYDB_SEARCH_MODE_NEXT, this + * function silently skips legacy keys. */ +gpg_error_t +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, NULL); +} + + +/* This is a convenience function for searching for keys with a long + * key id. + * + * Note: this function resumes searching where the last search left + * off. If you want to search the whole database, then you need to + * first call keydb_search_reset(). */ +gpg_error_t +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, NULL); +} + + +/* This is a convenience function for searching for keys with a long + * (20 byte) fingerprint. + * + * Note: this function resumes searching where the last search left + * off. If you want to search the whole database, then you need to + * first call keydb_search_reset(). */ +gpg_error_t +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, NULL); +} diff --git a/g10/keydb.h b/g10/keydb.h new file mode 100644 index 0000000..0f8d711 --- /dev/null +++ b/g10/keydb.h @@ -0,0 +1,570 @@ +/* keydb.h - Key database + * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, + * 2006, 2010 Free Software Foundation, Inc. + * Copyright (C) 2015, 2016 g10 Code GmbH + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#ifndef G10_KEYDB_H +#define G10_KEYDB_H + +#include "../common/types.h" +#include "../common/util.h" +#include "packet.h" + +/* What qualifies as a certification (key-signature in contrast to a + * data signature)? Note that a back signature is special and can be + * made by key and data signatures capable subkeys.) */ +#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_BACK_SIG(s) ((s)->sig_class == 0x19) +#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; +typedef struct getkey_ctx_s *getkey_ctx_t; + +/**************** + * 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_t next; + PACKET *pkt; + int flag; /* Local use during keyblock processing (not cloned).*/ + unsigned int tag; /* Ditto. */ + int private_flag; +}; + +#define is_deleted_kbnode(a) ((a)->private_flag & 1) +#define is_cloned_kbnode(a) ((a)->private_flag & 2) + + +/* + * A structure to store key identification as well as some stuff + * needed for key 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]; +}; + + +/* Bit flags used with build_pk_list. */ +enum + { + PK_LIST_ENCRYPT_TO = 1, /* This is an encrypt-to recipient. */ + PK_LIST_HIDDEN = 2, /* This is a hidden recipient. */ + PK_LIST_CONFIG = 4, /* Specified via config file. */ + PK_LIST_FROM_FILE = 8 /* Take key from file with that name. */ + }; + +/* To store private data in the flags the private data must be left + * shifted by this value. */ +enum + { + PK_LIST_SHIFT = 4 + }; + + +/* Structure to hold a couple of public key certificates. */ +typedef struct pk_list *PK_LIST; /* Deprecated. */ +typedef struct pk_list *pk_list_t; +struct pk_list +{ + PK_LIST next; + PKT_public_key *pk; + int flags; /* See PK_LIST_ constants. */ +}; + +/* Structure to hold a list of secret key certificates. */ +typedef struct sk_list *SK_LIST; +struct sk_list +{ + SK_LIST next; + PKT_public_key *pk; + 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]; +}; + + +/* Helper type for preference functions. */ +struct pref_hint +{ + int digest_length; /* We want at least this digest length. */ + int exact; /* We need to use exactly this length. */ +}; + + +/* Constants to describe from where a key was fetched or updated. */ +enum + { + KEYORG_UNKNOWN = 0, + KEYORG_KS = 1, /* Public keyserver. */ + KEYORG_KS_PREF = 2, /* Preferred keysrver. */ + KEYORG_DANE = 3, /* OpenPGP DANE. */ + KEYORG_WKD = 4, /* Web Key Directory. */ + KEYORG_URL = 5, /* Trusted URL. */ + KEYORG_FILE = 6, /* Trusted file. */ + KEYORG_SELF = 7 /* We generated it. */ + }; + + +/* + * Check whether the signature SIG is in the klist K. + */ +static inline 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; +} + + + +/*-- keydb.c --*/ + +#define KEYDB_RESOURCE_FLAG_PRIMARY 2 /* The primary resource. */ +#define KEYDB_RESOURCE_FLAG_DEFAULT 4 /* The default one. */ +#define KEYDB_RESOURCE_FLAG_READONLY 8 /* Open in read only mode. */ +#define KEYDB_RESOURCE_FLAG_GPGVDEF 16 /* Default file for gpgv. */ + +/* Format a search term for debugging output. The caller must free + the result. */ +char *keydb_search_desc_dump (struct keydb_search_desc *desc); + +/* Register a resource (keyring or keybox). */ +gpg_error_t keydb_add_resource (const char *url, unsigned int flags); + +/* Dump some statistics to the log. */ +void keydb_dump_stats (void); + +/* Create a new database handle. Returns NULL on error, sets ERRNO, + and prints an error diagnostic. */ +KEYDB_HANDLE keydb_new (void); + +/* Free all resources owned by the database handle. */ +void keydb_release (KEYDB_HANDLE hd); + +/* Take a lock on the files immediately and not only during insert or + * update. This lock is released with keydb_release. */ +gpg_error_t keydb_lock (KEYDB_HANDLE hd); + +/* Set a flag on the handle to suppress use of cached results. This + is required for updating a keyring and for key listings. Fixme: + Using a new parameter for keydb_new might be a better solution. */ +void keydb_disable_caching (KEYDB_HANDLE hd); + +/* Save the last found state and invalidate the current selection. */ +void keydb_push_found_state (KEYDB_HANDLE hd); + +/* Restore the previous save state. */ +void keydb_pop_found_state (KEYDB_HANDLE hd); + +/* Return the file name of the resource. */ +const char *keydb_get_resource_name (KEYDB_HANDLE hd); + +/* Return the keyblock last found by keydb_search. */ +gpg_error_t keydb_get_keyblock (KEYDB_HANDLE hd, KBNODE *ret_kb); + +/* Update the keyblock KB. */ +gpg_error_t keydb_update_keyblock (ctrl_t ctrl, KEYDB_HANDLE hd, kbnode_t kb); + +/* Insert a keyblock into one of the underlying keyrings or keyboxes. */ +gpg_error_t keydb_insert_keyblock (KEYDB_HANDLE hd, kbnode_t kb); + +/* Delete the currently selected keyblock. */ +gpg_error_t keydb_delete_keyblock (KEYDB_HANDLE hd); + +/* Find the first writable resource. */ +gpg_error_t keydb_locate_writable (KEYDB_HANDLE hd); + +/* Rebuild the on-disk caches of all key resources. */ +void keydb_rebuild_caches (ctrl_t ctrl, int noisy); + +/* Return the number of skipped blocks (because they were to large to + read from a keybox) since the last search reset. */ +unsigned long keydb_get_skipped_counter (KEYDB_HANDLE hd); + +/* Clears the current search result and resets the handle's position. */ +gpg_error_t keydb_search_reset (KEYDB_HANDLE hd); + +/* Search the database for keys matching the search description. */ +gpg_error_t keydb_search (KEYDB_HANDLE hd, KEYDB_SEARCH_DESC *desc, + size_t ndesc, size_t *descindex); + +/* Return the first non-legacy key in the database. */ +gpg_error_t keydb_search_first (KEYDB_HANDLE hd); + +/* Return the next key (not the next matching key!). */ +gpg_error_t keydb_search_next (KEYDB_HANDLE hd); + +/* This is a convenience function for searching for keys with a long + key id. */ +gpg_error_t keydb_search_kid (KEYDB_HANDLE hd, u32 *kid); + +/* This is a convenience function for searching for keys with a long + (20 byte) fingerprint. */ +gpg_error_t keydb_search_fpr (KEYDB_HANDLE hd, const byte *fpr); + + +/*-- pkclist.c --*/ +void show_revocation_reason (ctrl_t ctrl, PKT_public_key *pk, int mode ); +int check_signatures_trust (ctrl_t ctrl, PKT_signature *sig); + +void release_pk_list (PK_LIST pk_list); +int build_pk_list (ctrl_t ctrl, strlist_t rcpts, PK_LIST *ret_pk_list); +gpg_error_t find_and_check_key (ctrl_t ctrl, + const char *name, unsigned int use, + int mark_hidden, int from_file, + pk_list_t *pk_list_addr); + +int algo_available( preftype_t preftype, int algo, + const struct pref_hint *hint ); +int select_algo_from_prefs( PK_LIST pk_list, int preftype, + int request, const struct pref_hint *hint); +int select_mdc_from_pklist (PK_LIST pk_list); +void warn_missing_mdc_from_pklist (PK_LIST pk_list); +void warn_missing_aes_from_pklist (PK_LIST pk_list); + +/*-- skclist.c --*/ +int random_is_faked (void); +void release_sk_list( SK_LIST sk_list ); +gpg_error_t build_sk_list (ctrl_t ctrl, strlist_t locusr, + SK_LIST *ret_sk_list, unsigned use); + +/*-- passphrase.h --*/ + +/* Flags for passphrase_to_dek */ +#define GETPASSWORD_FLAG_SYMDECRYPT 1 + + +unsigned char encode_s2k_iterations (int iterations); +int have_static_passphrase(void); +const char *get_static_passphrase (void); +void set_passphrase_from_string(const char *pass); +void read_passphrase_from_fd( int fd ); +void passphrase_clear_cache (const char *cacheid); +DEK *passphrase_to_dek (int cipher_algo, STRING2KEY *s2k, + int create, int nocache, + const char *tryagain_text, unsigned int flags, + int *canceled); +void set_next_passphrase( const char *s ); +char *get_last_passphrase(void); +void next_to_last_passphrase(void); + +void emit_status_need_passphrase (ctrl_t ctrl, u32 *keyid, + u32 *mainkeyid, int pubkey_algo); + +#define FORMAT_KEYDESC_NORMAL 0 +#define FORMAT_KEYDESC_IMPORT 1 +#define FORMAT_KEYDESC_EXPORT 2 +#define FORMAT_KEYDESC_DELKEY 3 +char *gpg_format_keydesc (ctrl_t ctrl, + PKT_public_key *pk, int mode, int escaped); + + +/*-- getkey.c --*/ + +/* Cache a copy of a public key in the public key cache. */ +void cache_public_key( PKT_public_key *pk ); + +/* Disable and drop the public key cache. */ +void getkey_disable_caches(void); + +/* Return the public key used for signature SIG and store it at PK. */ +gpg_error_t get_pubkey_for_sig (ctrl_t ctrl, + PKT_public_key *pk, PKT_signature *sig, + PKT_public_key *forced_pk); + +/* Return the public key with the key id KEYID and store it at PK. */ +int get_pubkey (ctrl_t ctrl, PKT_public_key *pk, u32 *keyid); + +/* Same as get_pubkey but with auto LDAP fetch. */ +gpg_error_t get_pubkey_with_ldap_fallback (ctrl_t ctrl, + PKT_public_key *pk, u32 * keyid); + +/* Similar to get_pubkey, but it does not take PK->REQ_USAGE into + account nor does it merge in the self-signed data. This function + also only considers primary keys. */ +int get_pubkey_fast (PKT_public_key *pk, u32 *keyid); + +/* Return the entire keyblock used to create SIG. This is a + * specialized version of get_pubkeyblock. */ +kbnode_t get_pubkeyblock_for_sig (ctrl_t ctrl, PKT_signature *sig); + +/* Return the key block for the key with KEYID. */ +kbnode_t get_pubkeyblock (ctrl_t ctrl, u32 *keyid); + +/* A list used by get_pubkeys to gather all of the matches. */ +struct pubkey_s +{ + struct pubkey_s *next; + /* The key to use (either the public key or the subkey). */ + PKT_public_key *pk; + kbnode_t keyblock; +}; +typedef struct pubkey_s *pubkey_t; + +/* Free a list of public keys. */ +void pubkeys_free (pubkey_t keys); + + +/* Mode flags for get_pubkey_byname. */ +enum get_pubkey_modes + { + GET_PUBKEY_NORMAL = 0, + GET_PUBKEY_NO_AKL = 1, + GET_PUBKEY_NO_LOCAL = 2 + }; + +/* Find a public key identified by NAME. */ +int get_pubkey_byname (ctrl_t ctrl, enum get_pubkey_modes mode, + GETKEY_CTX *retctx, PKT_public_key *pk, + const char *name, + KBNODE *ret_keyblock, KEYDB_HANDLE *ret_kdbhd, + int include_unusable); + +/* Likewise, but only return the best match if NAME resembles a mail + * address. */ +gpg_error_t get_best_pubkey_byname (ctrl_t ctrl, enum get_pubkey_modes mode, + GETKEY_CTX *retctx, PKT_public_key *pk, + const char *name, KBNODE *ret_keyblock, + int include_unusable); + +/* Get a public key directly from file FNAME. */ +gpg_error_t get_pubkey_fromfile (ctrl_t ctrl, + PKT_public_key *pk, const char *fname); + +/* Get a public key from a buffer. */ +gpg_error_t get_pubkey_from_buffer (ctrl_t ctrl, PKT_public_key *pkbuf, + const void *buffer, size_t buflen, + u32 *want_keyid, kbnode_t *r_keyblock); + +/* Return the public key with the key id KEYID iff the secret key is + * available and store it at PK. */ +gpg_error_t get_seckey (ctrl_t ctrl, PKT_public_key *pk, u32 *keyid); + +/* Lookup a key with the specified fingerprint. */ +int get_pubkey_byfprint (ctrl_t ctrl, PKT_public_key *pk, kbnode_t *r_keyblock, + const byte *fprint, size_t fprint_len); + +/* This function is similar to get_pubkey_byfprint, but it doesn't + merge the self-signed data into the public key and subkeys or into + the user ids. */ +gpg_error_t get_pubkey_byfprint_fast (PKT_public_key *pk, + const byte *fprint, size_t fprint_len); + +/* This function is similar to get_pubkey_byfprint, but it doesn't + merge the self-signed data into the public key and subkeys or into + the user ids. */ +gpg_error_t get_keyblock_byfprint_fast (kbnode_t *r_keyblock, + KEYDB_HANDLE *r_hd, + const byte *fprint, size_t fprint_len, + int lock); + + +/* Returns true if a secret key is available for the public key with + key id KEYID. */ +int have_secret_key_with_kid (u32 *keyid); + +/* Parse the --default-key parameter. Returns the last key (in terms + of when the option is given) that is available. */ +const char *parse_def_secret_key (ctrl_t ctrl); + +/* Look up a secret key. */ +gpg_error_t get_seckey_default (ctrl_t ctrl, PKT_public_key *pk); +gpg_error_t get_seckey_default_or_card (ctrl_t ctrl, PKT_public_key *pk, + const byte *fpr, size_t fpr_len); + +/* Search for keys matching some criteria. */ +gpg_error_t getkey_bynames (ctrl_t ctrl, + getkey_ctx_t *retctx, PKT_public_key *pk, + strlist_t names, int want_secret, + kbnode_t *ret_keyblock); + +/* Search for one key matching some criteria. */ +gpg_error_t getkey_byname (ctrl_t ctrl, + getkey_ctx_t *retctx, PKT_public_key *pk, + const char *name, int want_secret, + kbnode_t *ret_keyblock); + +/* Return the next search result. */ +gpg_error_t getkey_next (ctrl_t ctrl, getkey_ctx_t ctx, + PKT_public_key *pk, kbnode_t *ret_keyblock); + +/* Release any resources used by a key listing context. */ +void getkey_end (ctrl_t ctrl, getkey_ctx_t ctx); + +/* Return the database handle used by this context. The context still + owns the handle. */ +KEYDB_HANDLE get_ctx_handle(GETKEY_CTX ctx); + +/* Enumerate some secret keys. */ +gpg_error_t enum_secret_keys (ctrl_t ctrl, void **context, PKT_public_key *pk); + +/* Set the mainkey_id fields for all keys in KEYBLOCK. */ +void setup_main_keyids (kbnode_t keyblock); + +/* This function merges information from the self-signed data into the + data structures. */ +void merge_keys_and_selfsig (ctrl_t ctrl, kbnode_t keyblock); + +char *get_user_id_string_native (ctrl_t ctrl, u32 *keyid); +char *get_long_user_id_string (ctrl_t ctrl, u32 *keyid); +char *get_user_id (ctrl_t ctrl, u32 *keyid, size_t *rn, int *r_nouid); +char *get_user_id_native (ctrl_t ctrl, u32 *keyid); +char *get_user_id_byfpr (ctrl_t ctrl, const byte *fpr, size_t *rn); +char *get_user_id_byfpr_native (ctrl_t ctrl, const byte *fpr); + +void release_akl(void); +int akl_empty_or_only_local (void); +int parse_auto_key_locate(const char *options); +int parse_key_origin (char *string); +const char *key_origin_string (int origin); + +/*-- keyid.c --*/ +int pubkey_letter( int algo ); +char *pubkey_string (PKT_public_key *pk, char *buffer, size_t bufsize); +#define PUBKEY_STRING_SIZE 32 +u32 v3_keyid (gcry_mpi_t a, u32 *ki); +void hash_public_key( gcry_md_hd_t md, PKT_public_key *pk ); +char *format_keyid (u32 *keyid, int format, char *buffer, int len); + +/* Return PK's keyid. The memory is owned by PK. */ +u32 *pk_keyid (PKT_public_key *pk); + +/* Return the keyid of the primary key associated with PK. The memory + is owned by PK. */ +u32 *pk_main_keyid (PKT_public_key *pk); + +/* Order A and B. If A < B then return -1, if A == B then return 0, + and if A > B then return 1. */ +static int GPGRT_ATTR_UNUSED +keyid_cmp (const u32 *a, const u32 *b) +{ + if (a[0] < b[0]) + return -1; + if (a[0] > b[0]) + return 1; + if (a[1] < b[1]) + return -1; + if (a[1] > b[1]) + return 1; + return 0; +} + +/* Return whether PK is a primary key. */ +static int GPGRT_ATTR_UNUSED +pk_is_primary (PKT_public_key *pk) +{ + return keyid_cmp (pk_keyid (pk), pk_main_keyid (pk)) == 0; +} + +/* Copy the keyid in SRC to DEST and return DEST. */ +u32 *keyid_copy (u32 *dest, const u32 *src); + +size_t keystrlen(void); +const char *keystr(u32 *keyid); +const char *keystr_with_sub (u32 *main_kid, u32 *sub_kid); +const char *keystr_from_pk(PKT_public_key *pk); +const char *keystr_from_pk_with_sub (PKT_public_key *main_pk, + PKT_public_key *sub_pk); + +/* Return PK's key id as a string using the default format. PK owns + the storage. */ +const char *pk_keyid_str (PKT_public_key *pk); + +const char *keystr_from_desc(KEYDB_SEARCH_DESC *desc); +u32 keyid_from_pk( PKT_public_key *pk, u32 *keyid ); +u32 keyid_from_sig (PKT_signature *sig, u32 *keyid ); +u32 keyid_from_fingerprint (ctrl_t ctrl, 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 ); + +/* Convert an UTC TIMESTAMP into an UTC yyyy-mm-dd string. Return + * that string. The caller should pass a buffer with at least a size + * of MK_DATESTR_SIZE. */ +char *mk_datestr (char *buffer, size_t bufsize, u32 timestamp); +#define MK_DATESTR_SIZE 11 + +const char *datestr_from_pk( PKT_public_key *pk ); +const char *datestr_from_sig( PKT_signature *sig ); +const char *expirestr_from_pk( PKT_public_key *pk ); +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, int fill); +const char *colon_strtime (u32 t); +const char *colon_datestr_from_pk (PKT_public_key *pk); +const char *colon_datestr_from_sig (PKT_signature *sig); +const char *colon_expirestr_from_sig (PKT_signature *sig); +byte *fingerprint_from_pk( PKT_public_key *pk, byte *buf, size_t *ret_len ); +char *hexfingerprint (PKT_public_key *pk, char *buffer, size_t buflen); +char *format_hexfingerprint (const char *fingerprint, + char *buffer, size_t buflen); +gpg_error_t keygrip_from_pk (PKT_public_key *pk, unsigned char *array); +gpg_error_t hexkeygrip_from_pk (PKT_public_key *pk, char **r_grip); + + +/*-- 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..1cb62de --- /dev/null +++ b/g10/keyedit.c @@ -0,0 +1,6577 @@ +/* keyedit.c - Edit properties of a key + * Copyright (C) 1998-2010 Free Software Foundation, Inc. + * Copyright (C) 1998-2017 Werner Koch + * Copyright (C) 2015, 2016 g10 Code GmbH + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include +#include +#include +#include +#include +#include +#ifdef HAVE_LIBREADLINE +# define GNUPG_LIBREADLINE_H_INCLUDED +# include +#endif + +#include "gpg.h" +#include "options.h" +#include "packet.h" +#include "../common/status.h" +#include "../common/iobuf.h" +#include "keydb.h" +#include "photoid.h" +#include "../common/util.h" +#include "main.h" +#include "trustdb.h" +#include "filter.h" +#include "../common/ttyio.h" +#include "../common/status.h" +#include "../common/i18n.h" +#include "keyserver-internal.h" +#include "call-agent.h" +#include "../common/host2net.h" +#include "tofu.h" +#include "key-check.h" +#include "key-clean.h" +#include "keyedit.h" + +static void show_prefs (PKT_user_id * uid, PKT_signature * selfsig, + int verbose); +static void show_names (ctrl_t ctrl, estream_t fp, + kbnode_t keyblock, PKT_public_key * pk, + unsigned int flag, int with_prefs); +static void show_key_with_all_names (ctrl_t ctrl, estream_t fp, + KBNODE keyblock, int only_marked, + int with_revoker, int with_fpr, + int with_subkeys, int with_prefs, + int nowarn); +static void show_key_and_fingerprint (ctrl_t ctrl, + kbnode_t keyblock, int with_subkeys); +static void show_key_and_grip (kbnode_t keyblock); +static void subkey_expire_warning (kbnode_t keyblock); +static int menu_adduid (ctrl_t ctrl, kbnode_t keyblock, + int photo, const char *photo_name, const char *uidstr); +static void menu_deluid (KBNODE pub_keyblock); +static int menu_delsig (ctrl_t ctrl, kbnode_t pub_keyblock); +static int menu_clean (ctrl_t ctrl, kbnode_t keyblock, int self_only); +static void menu_delkey (KBNODE pub_keyblock); +static int menu_addrevoker (ctrl_t ctrl, kbnode_t pub_keyblock, int sensitive); +static gpg_error_t menu_expire (ctrl_t ctrl, kbnode_t pub_keyblock, + int unattended, u32 newexpiration); +static int menu_changeusage (ctrl_t ctrl, kbnode_t keyblock); +static int menu_backsign (ctrl_t ctrl, kbnode_t pub_keyblock); +static int menu_set_primary_uid (ctrl_t ctrl, kbnode_t pub_keyblock); +static int menu_set_preferences (ctrl_t ctrl, kbnode_t pub_keyblock); +static int menu_set_keyserver_url (ctrl_t ctrl, + const char *url, kbnode_t pub_keyblock); +static int menu_set_notation (ctrl_t ctrl, + const char *string, kbnode_t pub_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, char *p); +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 (ctrl_t ctrl, kbnode_t keyblock); +static int menu_revuid (ctrl_t ctrl, kbnode_t keyblock); +static int core_revuid (ctrl_t ctrl, kbnode_t keyblock, KBNODE node, + const struct revocation_reason_info *reason, + int *modified); +static int menu_revkey (ctrl_t ctrl, kbnode_t pub_keyblock); +static int menu_revsubkey (ctrl_t ctrl, kbnode_t pub_keyblock); +#ifndef NO_TRUST_MODELS +static int enable_disable_key (ctrl_t ctrl, kbnode_t keyblock, int disable); +#endif /*!NO_TRUST_MODELS*/ +static void menu_showphoto (ctrl_t ctrl, kbnode_t keyblock); + +static int update_trust = 0; + +#define CONTROL_D ('D' - 'A' + 1) + +struct sign_attrib +{ + int non_exportable, non_revocable; + struct revocation_reason_info *reason; + byte trust_depth, trust_value; + char *trust_regexp; +}; + + + +/* TODO: Fix duplicated code between here and the check-sigs/list-sigs + code in keylist.c. */ +static int +print_and_check_one_sig_colon (ctrl_t ctrl, kbnode_t keyblock, kbnode_t 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 */ + + rc = check_key_signature (ctrl, keyblock, node, is_selfsig); + switch (gpg_err_code (rc)) + { + case 0: + node->flag &= ~(NODFLG_BADSIG | NODFLG_NOKEY | NODFLG_SIGERR); + sigrc = '!'; + break; + case GPG_ERR_BAD_SIGNATURE: + node->flag = NODFLG_BADSIG; + sigrc = '-'; + if (inv_sigs) + ++ * inv_sigs; + break; + case GPG_ERR_NO_PUBKEY: + case GPG_ERR_UNUSABLE_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) + { + es_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) + es_printf ("%d %d", sig->trust_depth, sig->trust_value); + + es_printf (":"); + + if (sig->trust_regexp) + es_write_sanitized (es_stdout, + sig->trust_regexp, strlen (sig->trust_regexp), + ":", NULL); + + es_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 (rc is its status), check it + * and return true if the signature is okay. NODE must be a signature + * packet. With EXTENDED set all possible signature list options will + * always be printed. + */ +int +keyedit_print_one_sig (ctrl_t ctrl, estream_t fp, + int rc, kbnode_t keyblock, kbnode_t node, + int *inv_sigs, int *no_key, int *oth_err, + int is_selfsig, int print_without_key, int extended) +{ + PKT_signature *sig = node->pkt->pkt.signature; + int 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 (gpg_err_code (rc)) + { + case 0: + node->flag &= ~(NODFLG_BADSIG | NODFLG_NOKEY | NODFLG_SIGERR); + sigrc = '!'; + break; + case GPG_ERR_BAD_SIGNATURE: + node->flag = NODFLG_BADSIG; + sigrc = '-'; + if (inv_sigs) + ++ * inv_sigs; + break; + case GPG_ERR_NO_PUBKEY: + case GPG_ERR_UNUSABLE_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_fprintf (fp, "%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) || extended ) + tty_fprintf (fp, " %s", expirestr_from_sig (sig)); + tty_fprintf (fp, " "); + if (sigrc == '%') + tty_fprintf (fp, "[%s] ", gpg_strerror (rc)); + else if (sigrc == '?') + ; + else if (is_selfsig) + { + tty_fprintf (fp, is_rev ? _("[revocation]") : _("[self-signature]")); + if (extended && sig->flags.chosen_selfsig) + tty_fprintf (fp, "*"); + } + else + { + size_t n; + char *p = get_user_id (ctrl, sig->keyid, &n, NULL); + tty_print_utf8_string2 (fp, p, n, + opt.screen_columns - keystrlen () - 26 - + ((opt. + list_options & LIST_SHOW_SIG_EXPIRE) ? 11 + : 0)); + xfree (p); + } + if (fp == log_get_stream ()) + log_printf ("\n"); + else + tty_fprintf (fp, "\n"); + + if (sig->flags.policy_url + && ((opt.list_options & LIST_SHOW_POLICY_URLS) || extended)) + show_policy_url (sig, 3, (!fp? -1 : fp == log_get_stream ()? 1 : 0)); + + if (sig->flags.notation + && ((opt.list_options & LIST_SHOW_NOTATIONS) || extended)) + show_notation (sig, 3, (!fp? -1 : fp == log_get_stream ()? 1 : 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) || extended)) + show_keyserver_url (sig, 3, (!fp? -1 : fp == log_get_stream ()? 1 : 0)); + + if (extended) + { + PKT_public_key *pk = keyblock->pkt->pkt.public_key; + const unsigned char *s; + + s = parse_sig_subpkt (sig->hashed, SIGSUBPKT_PRIMARY_UID, NULL); + if (s && *s) + tty_fprintf (fp, " [primary]\n"); + + s = parse_sig_subpkt (sig->hashed, SIGSUBPKT_KEY_EXPIRE, NULL); + if (s && buf32_to_u32 (s)) + tty_fprintf (fp, " [expires: %s]\n", + isotimestamp (pk->timestamp + buf32_to_u32 (s))); + } + } + + return (sigrc == '!'); +} + + +static int +print_and_check_one_sig (ctrl_t ctrl, kbnode_t keyblock, kbnode_t node, + int *inv_sigs, int *no_key, int *oth_err, + int *is_selfsig, int print_without_key, int extended) +{ + int rc; + + rc = check_key_signature (ctrl, keyblock, node, is_selfsig); + return keyedit_print_one_sig (ctrl, NULL, rc, + keyblock, node, inv_sigs, no_key, oth_err, + *is_selfsig, print_without_key, extended); +} + + +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 sign the uids after asking. If no + * user id is marked, all user ids will be signed; if some user_ids + * are marked only those will be signed. If QUICK is true the + * function won't ask the user and use sensible defaults. + */ +static int +sign_uids (ctrl_t ctrl, estream_t fp, + kbnode_t keyblock, strlist_t locusr, int *ret_modified, + int local, int nonrevocable, int trust, int interactive, + int quick) +{ + int rc = 0; + SK_LIST sk_list = NULL; + SK_LIST sk_rover = NULL; + PKT_public_key *pk = NULL; + KBNODE node, uidnode; + PKT_public_key *primary_pk = NULL; + int select_all = !count_selected_uids (keyblock) || interactive; + + /* 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 (ctrl, locusr, &sk_list, 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 class = 0, selfsig = 0; + u32 duration = 0, timestamp = 0; + byte trust_depth = 0, trust_value = 0; + + pk = sk_rover->pk; + keyid_from_pk (pk, 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; + } + else if (node->pkt->pkttype == PKT_USER_ID) + { + uidnode = (node->flag & NODFLG_MARK_A) ? node : NULL; + if (uidnode) + { + int yesreally = 0; + char *user; + + user = utf8_to_native (uidnode->pkt->pkt.user_id->name, + uidnode->pkt->pkt.user_id->len, 0); + + if (opt.only_sign_text_ids + && uidnode->pkt->pkt.user_id->attribs) + { + tty_fprintf (fp, _("Skipping user ID \"%s\"," + " which is not a text ID.\n"), + user); + uidnode->flag &= ~NODFLG_MARK_A; + uidnode = NULL; + } + else if (uidnode->pkt->pkt.user_id->flags.revoked) + { + tty_fprintf (fp, _("User ID \"%s\" is revoked."), user); + + if (selfsig) + tty_fprintf (fp, "\n"); + else if (opt.expert && !quick) + { + tty_fprintf (fp, "\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_fprintf (fp, _(" Unable to sign.\n")); + } + } + else if (uidnode->pkt->pkt.user_id->flags.expired) + { + tty_fprintf (fp, _("User ID \"%s\" is expired."), user); + + if (selfsig) + tty_fprintf (fp, "\n"); + else if (opt.expert && !quick) + { + tty_fprintf (fp, "\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_fprintf (fp, _(" Unable to sign.\n")); + } + } + else if (!uidnode->pkt->pkt.user_id->created && !selfsig) + { + tty_fprintf (fp, _("User ID \"%s\" is not self-signed."), + user); + + if (opt.expert && !quick) + { + tty_fprintf (fp, "\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_fprintf (fp, _(" Unable to sign.\n")); + } + } + + if (uidnode && interactive && !yesreally && !quick) + { + tty_fprintf (fp, + _("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; + + 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 && !quick) + { + tty_fprintf (fp, + _("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) "))) + { + node->flag |= NODFLG_DELSIG; + xfree (user); + continue; + } + } + + /* Is the current signature expired? */ + if (node->pkt->pkt.signature->flags.expired) + { + tty_fprintf (fp, _("Your current signature on \"%s\"\n" + "has expired.\n"), user); + + if (quick || 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_fprintf (fp, _("Your current signature on \"%s\"\n" + "is a local signature.\n"), user); + + if (quick || 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 signing it again. */ + if (!node->pkt->pkt.signature->flags.exportable && local) + tty_fprintf ( fp, + _("\"%s\" was already locally signed by key %s\n"), + user, keystr_from_pk (pk)); + else + tty_fprintf (fp, + _("\"%s\" was already signed by key %s\n"), + user, keystr_from_pk (pk)); + + if (opt.flags.force_sign_key + || (opt.expert && !quick + && 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; + } + + snprintf (buf, sizeof buf, "%08lX%08lX", + (ulong) pk->keyid[0], (ulong) pk->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_fprintf (fp, _("Nothing to sign with key %s\n"), + keystr_from_pk (pk)); + continue; + } + + /* Ask whether we really should sign these user id(s). */ + tty_fprintf (fp, "\n"); + show_key_with_all_names (ctrl, fp, keyblock, 1, 0, 1, 0, 0, 0); + tty_fprintf (fp, "\n"); + + if (primary_pk->expiredate && !selfsig) + { + /* Static analyzer note: A claim that PRIMARY_PK might be + NULL is not correct because it set from the public key + packet which is always the first packet in a keyblock and + parsed in the above loop over the keyblock. In case the + keyblock has no packets at all and thus the loop was not + entered the above count_uids_with_flag would have + detected this case. */ + + u32 now = make_timestamp (); + + if (primary_pk->expiredate <= now) + { + tty_fprintf (fp, _("This key has expired!")); + + if (opt.expert && !quick) + { + tty_fprintf (fp, " "); + if (!cpr_get_answer_is_yes ("sign_uid.expired_okay", + _("Are you sure you still " + "want to sign it? (y/N) "))) + continue; + } + else + { + tty_fprintf (fp, _(" Unable to sign.\n")); + continue; + } + } + else + { + tty_fprintf (fp, _("This key is due to expire on %s.\n"), + expirestr_from_pk (primary_pk)); + + if (opt.ask_cert_expire && !quick) + { + 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; + } + + 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 && !quick) + duration = ask_expire_interval (1, opt.def_cert_expire); + else + duration = parse_expire_string (opt.def_cert_expire); + } + + if (selfsig) + ; + else + { + if (opt.batch || !opt.ask_cert_level || quick) + class = 0x10 + opt.def_cert_level; + else + { + char *answer; + + tty_fprintf (fp, + _("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_fprintf (fp, "\n"); + tty_fprintf (fp, _(" (0) I will not answer.%s\n"), + opt.def_cert_level == 0 ? " (default)" : ""); + tty_fprintf (fp, _(" (1) I have not checked at all.%s\n"), + opt.def_cert_level == 1 ? " (default)" : ""); + tty_fprintf (fp, _(" (2) I have done casual checking.%s\n"), + opt.def_cert_level == 2 ? " (default)" : ""); + tty_fprintf (fp, + _(" (3) I have done very careful checking.%s\n"), + opt.def_cert_level == 3 ? " (default)" : ""); + tty_fprintf (fp, "\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_fprintf (fp, _("Invalid selection.\n")); + + xfree (answer); + } + } + + if (trust && !quick) + trustsig_prompt (&trust_value, &trust_depth, &trust_regexp); + } + + if (!quick) + { + p = get_user_id_native (ctrl, sk_keyid); + tty_fprintf (fp, + _("Are you sure that you want to sign this key with your\n" + "key \"%s\" (%s)\n"), p, keystr_from_pk (pk)); + xfree (p); + } + + if (selfsig) + { + tty_fprintf (fp, "\n"); + tty_fprintf (fp, _("This will be a self-signature.\n")); + + if (local) + { + tty_fprintf (fp, "\n"); + tty_fprintf (fp, _("WARNING: the signature will not be marked " + "as non-exportable.\n")); + } + + if (nonrevocable) + { + tty_fprintf (fp, "\n"); + tty_fprintf (fp, _("WARNING: the signature will not be marked " + "as non-revocable.\n")); + } + } + else + { + if (local) + { + tty_fprintf (fp, "\n"); + tty_fprintf (fp, + _("The signature will be marked as non-exportable.\n")); + } + + if (nonrevocable) + { + tty_fprintf (fp, "\n"); + tty_fprintf (fp, + _("The signature will be marked as non-revocable.\n")); + } + + switch (class) + { + case 0x11: + tty_fprintf (fp, "\n"); + tty_fprintf (fp, _("I have not checked this key at all.\n")); + break; + + case 0x12: + tty_fprintf (fp, "\n"); + tty_fprintf (fp, _("I have checked this key casually.\n")); + break; + + case 0x13: + tty_fprintf (fp, "\n"); + tty_fprintf (fp, _("I have checked this key very carefully.\n")); + break; + } + } + + tty_fprintf (fp, "\n"); + + if (opt.batch && opt.answer_yes) + ; + else if (quick) + ; + 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 modifying 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; + + log_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 (ctrl, &sig, primary_pk, + node->pkt->pkt.user_id, + NULL, + pk, + 0x13, 0, 0, 0, + keygen_add_std_prefs, primary_pk, + NULL); + else + rc = make_keysig_packet (ctrl, &sig, primary_pk, + node->pkt->pkt.user_id, + NULL, + pk, + class, 0, + timestamp, duration, + sign_mk_attrib, &attrib, + NULL); + if (rc) + { + write_status_error ("keysig", rc); + log_error (_("signing failed: %s\n"), gpg_strerror (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); + return rc; +} + + +/* + * Change the passphrase of the primary and all secondary keys. Note + * that it is common to use only one passphrase for the primary and + * all subkeys. However, this is now (since GnuPG 2.1) all up to the + * gpg-agent. Returns 0 on success or an error code. + */ +static gpg_error_t +change_passphrase (ctrl_t ctrl, kbnode_t keyblock) +{ + gpg_error_t err; + kbnode_t node; + PKT_public_key *pk; + int any; + u32 keyid[2], subid[2]; + char *hexgrip = NULL; + char *cache_nonce = NULL; + char *passwd_nonce = NULL; + + node = find_kbnode (keyblock, PKT_PUBLIC_KEY); + if (!node) + { + log_error ("Oops; public key missing!\n"); + err = gpg_error (GPG_ERR_INTERNAL); + goto leave; + } + pk = node->pkt->pkt.public_key; + keyid_from_pk (pk, keyid); + + /* Check whether it is likely that we will be able to change the + passphrase for any subkey. */ + for (any = 0, node = keyblock; node; node = node->next) + { + if (node->pkt->pkttype == PKT_PUBLIC_KEY + || node->pkt->pkttype == PKT_PUBLIC_SUBKEY) + { + char *serialno; + + pk = node->pkt->pkt.public_key; + keyid_from_pk (pk, subid); + + xfree (hexgrip); + err = hexkeygrip_from_pk (pk, &hexgrip); + if (err) + goto leave; + err = agent_get_keyinfo (ctrl, hexgrip, &serialno, NULL); + if (!err && serialno) + ; /* Key on card. */ + else if (gpg_err_code (err) == GPG_ERR_NOT_FOUND) + ; /* Maybe stub key. */ + else if (!err) + any = 1; /* Key is known. */ + else + log_error ("key %s: error getting keyinfo from agent: %s\n", + keystr_with_sub (keyid, subid), gpg_strerror (err)); + xfree (serialno); + } + } + err = 0; + if (!any) + { + tty_printf (_("Key has only stub or on-card key items - " + "no passphrase to change.\n")); + goto leave; + } + + /* Change the passphrase for all keys. */ + for (node = keyblock; node; node = node->next) + { + if (node->pkt->pkttype == PKT_PUBLIC_KEY + || node->pkt->pkttype == PKT_PUBLIC_SUBKEY) + { + char *desc; + + pk = node->pkt->pkt.public_key; + keyid_from_pk (pk, subid); + + xfree (hexgrip); + err = hexkeygrip_from_pk (pk, &hexgrip); + if (err) + goto leave; + + /* Note that when using --dry-run we don't change the + * passphrase but merely verify the current passphrase. */ + desc = gpg_format_keydesc (ctrl, pk, FORMAT_KEYDESC_NORMAL, 1); + err = agent_passwd (ctrl, hexgrip, desc, !!opt.dry_run, + &cache_nonce, &passwd_nonce); + xfree (desc); + + if (err) + log_log ((gpg_err_code (err) == GPG_ERR_CANCELED + || gpg_err_code (err) == GPG_ERR_FULLY_CANCELED) + ? GPGRT_LOG_INFO : GPGRT_LOG_ERROR, + _("key %s: error changing passphrase: %s\n"), + keystr_with_sub (keyid, subid), + gpg_strerror (err)); + if (gpg_err_code (err) == GPG_ERR_FULLY_CANCELED) + break; + } + } + + leave: + xfree (hexgrip); + xfree (cache_nonce); + xfree (passwd_nonce); + return err; +} + + + +/* Fix various problems in the keyblock. Returns true if the keyblock + was changed. Note that a pointer to the keyblock must be given and + the function may change it (i.e. replacing the first node). */ +static int +fix_keyblock (ctrl_t ctrl, kbnode_t *keyblockp) +{ + int changed = 0; + + if (collapse_uids (keyblockp)) + changed++; + if (key_check_all_keysigs (ctrl, 1, *keyblockp, 0, 1)) + changed++; + reorder_keyblock (*keyblockp); + /* If we modified the keyblock, make sure the flags are right. */ + if (changed) + merge_keys_and_selfsig (ctrl, *keyblockp); + + return changed; +} + + +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 certain selections we use node->mark MARKBIT_xxxx. + */ + +/* Need an SK for this command */ +#define KEYEDIT_NEED_SK 1 +/* Need an SUB KEY for this command */ +#define KEYEDIT_NEED_SUBSK 2 +/* 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, cmdCHANGEUSAGE, cmdBACKSIGN, +#ifndef NO_TRUST_MODELS + cmdENABLEKEY, cmdDISABLEKEY, +#endif /*!NO_TRUST_MODELS*/ + cmdSHOWPREF, + cmdSETPREF, cmdPREFKS, cmdNOTATION, cmdINVCMD, cmdSHOWPHOTO, cmdUPDTRUST, + cmdCHKTRUST, cmdADDCARDKEY, cmdKEYTOCARD, cmdBKUPTOCARD, + cmdCLEAN, cmdMINIMIZE, cmdGRIP, 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")}, + { "grip", cmdGRIP, 0, N_("show the keygrip")}, + { "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}, + { "change-usage", cmdCHANGEUSAGE, KEYEDIT_NEED_SK, NULL}, + { "cross-certify", cmdBACKSIGN, KEYEDIT_NEED_SK, NULL}, + { "backsign", cmdBACKSIGN, KEYEDIT_NEED_SK, NULL}, + { "sign", cmdSIGN, KEYEDIT_TAIL_MATCH, + N_("sign selected user IDs [* see below for related commands]")}, + { "s", cmdSIGN, 0, 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_NEED_SK, N_("add a user ID")}, + { "addphoto", cmdADDPHOTO, KEYEDIT_NEED_SK, + N_("add a photo ID")}, + { "deluid", cmdDELUID, 0, N_("delete selected user IDs")}, + /* delphoto is really deluid in disguise */ + { "delphoto", cmdDELUID, 0, NULL}, + { "addkey", cmdADDKEY, KEYEDIT_NEED_SK, N_("add a subkey")}, +#ifdef ENABLE_CARD_SUPPORT + { "addcardkey", cmdADDCARDKEY, KEYEDIT_NEED_SK, + N_("add a key to a smartcard")}, + { "keytocard", cmdKEYTOCARD, KEYEDIT_NEED_SK | KEYEDIT_NEED_SUBSK, + N_("move a key to a smartcard")}, + { "bkuptocard", cmdBKUPTOCARD, KEYEDIT_NEED_SK | KEYEDIT_NEED_SUBSK, + N_("move a backup key to a smartcard")}, +#endif /*ENABLE_CARD_SUPPORT */ + { "delkey", cmdDELKEY, 0, N_("delete selected subkeys")}, + { "addrevoker", cmdADDREVOKER, KEYEDIT_NEED_SK, + N_("add a revocation key")}, + { "delsig", cmdDELSIG, 0, + N_("delete signatures from the selected user IDs")}, + { "expire", cmdEXPIRE, KEYEDIT_NEED_SK | KEYEDIT_NEED_SUBSK, + N_("change the expiration date for the key or selected subkeys")}, + { "primary", cmdPRIMARY, KEYEDIT_NEED_SK, + N_("flag the selected user ID as primary")}, + { "toggle", cmdTOGGLE, KEYEDIT_NEED_SK, NULL}, /* Dummy command. */ + { "t", cmdTOGGLE, KEYEDIT_NEED_SK, NULL}, + { "pref", cmdPREF, 0, N_("list preferences (expert)")}, + { "showpref", cmdSHOWPREF, 0, N_("list preferences (verbose)")}, + { "setpref", cmdSETPREF, KEYEDIT_NEED_SK, + N_("set preference list for the selected user IDs")}, + { "updpref", cmdSETPREF, KEYEDIT_NEED_SK, NULL}, + { "keyserver", cmdPREFKS, KEYEDIT_NEED_SK, + N_("set the preferred keyserver URL for the selected user IDs")}, + { "notation", cmdNOTATION, KEYEDIT_NEED_SK, + N_("set a notation for the selected user IDs")}, + { "passwd", cmdPASSWD, KEYEDIT_NEED_SK | KEYEDIT_NEED_SUBSK, + N_("change the passphrase")}, + { "password", cmdPASSWD, KEYEDIT_NEED_SK | KEYEDIT_NEED_SUBSK, NULL}, +#ifndef NO_TRUST_MODELS + { "trust", cmdTRUST, 0, N_("change the ownertrust")}, +#endif /*!NO_TRUST_MODELS*/ + { "revsig", cmdREVSIG, 0, + N_("revoke signatures on the selected user IDs")}, + { "revuid", cmdREVUID, KEYEDIT_NEED_SK, + N_("revoke selected user IDs")}, + { "revphoto", cmdREVUID, KEYEDIT_NEED_SK, NULL}, + { "revkey", cmdREVKEY, KEYEDIT_NEED_SK, + N_("revoke key or selected subkeys")}, +#ifndef NO_TRUST_MODELS + { "enable", cmdENABLEKEY, 0, N_("enable key")}, + { "disable", cmdDISABLEKEY, 0, N_("disable key")}, +#endif /*!NO_TRUST_MODELS*/ + { "showphoto", cmdSHOWPHOTO, 0, N_("show selected photo IDs")}, + { "clean", cmdCLEAN, 0, + N_("compact unusable user IDs and remove unusable signatures from key")}, + { "minimize", cmdMINIMIZE, 0, + 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. */ + + (void) end; + + if (start == 0) + return rl_completion_matches (text, command_generator); + + rl_attempted_completion_over = 1; + + return NULL; +} +#endif /* HAVE_LIBREADLINE */ + + + +/* Main function of the menu driven key editor. */ +void +keyedit_menu (ctrl_t ctrl, const char *username, strlist_t locusr, + strlist_t commands, int quiet, int seckey_check) +{ + enum cmdids cmd = 0; + gpg_error_t err = 0; + KBNODE keyblock = NULL; + KEYDB_HANDLE kdbhd = NULL; + int have_seckey = 0; + int have_anyseckey = 0; + char *answer = NULL; + int redisplay = 1; + int modified = 0; + int sec_shadowing = 0; + int run_subkey_warnings = 0; + 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 (ctrl); +#endif + + /* Get the public key */ + err = get_pubkey_byname (ctrl, GET_PUBKEY_NO_AKL, + NULL, NULL, username, &keyblock, &kdbhd, 1); + if (err) + { + log_error (_("key \"%s\" not found: %s\n"), username, gpg_strerror (err)); + goto leave; + } + + if (fix_keyblock (ctrl, &keyblock)) + modified++; + + /* See whether we have a matching secret key. */ + if (seckey_check) + { + have_anyseckey = !agent_probe_any_secret_key (ctrl, keyblock); + if (have_anyseckey + && !agent_probe_secret_key (ctrl, keyblock->pkt->pkt.public_key)) + { + /* The primary key is also available. */ + have_seckey = 1; + } + + if (have_seckey && !quiet) + tty_printf (_("Secret key is available.\n")); + else if (have_anyseckey && !quiet) + tty_printf (_("Secret subkeys are available.\n")); + } + + /* Main command loop. */ + for (;;) + { + 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 using flags: with_revoker, with_subkeys. */ + show_key_with_all_names (ctrl, NULL, keyblock, 0, 1, 0, 1, 0, 0); + tty_printf ("\n"); + redisplay = 0; + } + + if (run_subkey_warnings) + { + run_subkey_warnings = 0; + if (!count_selected_keys (keyblock)) + subkey_expire_warning (keyblock); + } + + 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) + { +#ifdef HAVE_LIBREADLINE + tty_enable_completion (keyedit_completion); +#endif + answer = cpr_get_no_help ("keyedit.prompt", GPG_NAME "> "); + cpr_kill_prompt (); + tty_disable_completion (); + } + trim_spaces (answer); + } + while (*answer == '#'); + + arg_number = 0; /* Here is the init which egcc complains about. */ + photo = 0; /* Same here. */ + 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)) + { + answer[a - l] = '\0'; + break; + } + } + } + else if (!ascii_strcasecmp (answer, cmds[i].name)) + break; + } + if ((cmds[i].flags & (KEYEDIT_NEED_SK|KEYEDIT_NEED_SUBSK)) + && !(((cmds[i].flags & KEYEDIT_NEED_SK) && have_seckey) + || ((cmds[i].flags & KEYEDIT_NEED_SUBSK) && have_anyseckey))) + { + tty_printf (_("Need the secret key to do this.\n")); + cmd = cmdNOP; + } + else + cmd = cmds[i].id; + } + + /* Dispatch the command. */ + switch (cmd) + { + case cmdHELP: + for (i = 0; cmds[i].name; i++) + { + if ((cmds[i].flags & (KEYEDIT_NEED_SK|KEYEDIT_NEED_SUBSK)) + && !(((cmds[i].flags & KEYEDIT_NEED_SK) && have_seckey) + ||((cmds[i].flags&KEYEDIT_NEED_SUBSK)&&have_anyseckey))) + ; /* Skip those item 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 + (ctrl, + keyblock, (*arg_string == '*' + && (!arg_string[1] || spacep (arg_string + 1)))); + break; + + case cmdGRIP: + show_key_and_grip (keyblock); + break; + + case cmdSELUID: + if (strlen (arg_string) == NAMEHASH_LEN * 2) + redisplay = menu_select_uid_namehash (keyblock, arg_string); + else + { + if (*arg_string == '*' + && (!arg_string[1] || spacep (arg_string + 1))) + arg_number = -1; /* Select all. */ + redisplay = menu_select_uid (keyblock, arg_number); + } + break; + + case cmdSELKEY: + { + if (*arg_string == '*' + && (!arg_string[1] || spacep (arg_string + 1))) + arg_number = -1; /* Select all. */ + if (menu_select_key (keyblock, arg_number, p)) + redisplay = 1; + } + break; + + case cmdCHECK: + if (key_check_all_keysigs (ctrl, -1, keyblock, + count_selected_uids (keyblock), + !strcmp (arg_string, "selfsig"))) + modified = 1; + break; + + case cmdSIGN: + { + int localsig = 0, nonrevokesig = 0, trustsig = 0, interactive = 0; + + if (pk->flags.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)) + { + int result; + if (opt.only_sign_text_ids) + result = cpr_get_answer_is_yes + ("keyedit.sign_all.okay", + _("Really sign all text user IDs? (y/N) ")); + else + result = cpr_get_answer_is_yes + ("keyedit.sign_all.okay", + _("Really sign all user IDs? (y/N) ")); + + if (! result) + { + 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 (ctrl, NULL, keyblock, locusr, &modified, + localsig, nonrevokesig, trustsig, interactive, 0); + } + break; + + case cmdDEBUG: + dump_kbnode (keyblock); + break; + + case cmdTOGGLE: + /* The toggle command is a leftover from old gpg versions + where we worked with a secret and a public keyring. It + is not necessary anymore but we keep this command for the + sake of scripts using it. */ + redisplay = 1; + break; + + case cmdADDPHOTO: + if (RFC2440) + { + tty_printf (_("This command is not allowed while in %s mode.\n"), + gnupg_compliance_option_string (opt.compliance)); + break; + } + photo = 1; + /* fall through */ + case cmdADDUID: + if (menu_adduid (ctrl, keyblock, photo, arg_string, NULL)) + { + update_trust = 1; + redisplay = 1; + modified = 1; + merge_keys_and_selfsig (ctrl, keyblock); + } + break; + + case cmdDELUID: + { + int n1; + + if (!(n1 = count_selected_uids (keyblock))) + { + tty_printf (_("You must select at least one user ID.\n")); + if (!opt.expert) + tty_printf (_("(Use the '%s' command.)\n"), "uid"); + } + 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); + redisplay = 1; + modified = 1; + } + } + break; + + case cmdDELSIG: + { + int n1; + + if (!(n1 = count_selected_uids (keyblock))) + { + tty_printf (_("You must select at least one user ID.\n")); + if (!opt.expert) + tty_printf (_("(Use the '%s' command.)\n"), "uid"); + } + else if (menu_delsig (ctrl, keyblock)) + { + /* No redisplay here, because it may scroll away some + * of the status output of this command. */ + modified = 1; + } + } + break; + + case cmdADDKEY: + if (!generate_subkeypair (ctrl, keyblock, NULL, NULL, NULL)) + { + redisplay = 1; + modified = 1; + merge_keys_and_selfsig (ctrl, keyblock); + } + break; + +#ifdef ENABLE_CARD_SUPPORT + case cmdADDCARDKEY: + if (!card_generate_subkey (ctrl, keyblock)) + { + redisplay = 1; + modified = 1; + merge_keys_and_selfsig (ctrl, keyblock); + } + break; + + case cmdKEYTOCARD: + { + KBNODE node = NULL; + switch (count_selected_keys (keyblock)) + { + case 0: + if (cpr_get_answer_is_yes + ("keyedit.keytocard.use_primary", + /* TRANSLATORS: Please take care: This is about + moving the key and not about removing it. */ + _("Really move the primary key? (y/N) "))) + node = keyblock; + break; + case 1: + for (node = keyblock; node; node = node->next) + { + if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY + && node->flag & NODFLG_SELKEY) + break; + } + break; + default: + tty_printf (_("You must select exactly one key.\n")); + break; + } + if (node) + { + PKT_public_key *xxpk = node->pkt->pkt.public_key; + if (card_store_subkey (node, xxpk ? xxpk->pubkey_usage : 0)) + { + redisplay = 1; + sec_shadowing = 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; + char *fname; + PACKET *pkt; + IOBUF a; + struct parse_packet_ctx_s parsectx; + + if (!*arg_string) + { + tty_printf (_("Command expects a filename argument\n")); + break; + } + + if (*arg_string == DIRSEP_C) + fname = xstrdup (arg_string); + else if (*arg_string == '~') + fname = make_filename (arg_string, NULL); + else + fname = make_filename (gnupg_homedir (), arg_string, NULL); + + /* Open that file. */ + a = iobuf_open (fname); + if (a && is_secured_file (iobuf_get_fd (a))) + { + iobuf_close (a); + a = NULL; + gpg_err_set_errno (EPERM); + } + if (!a) + { + tty_printf (_("Can't open '%s': %s\n"), + fname, strerror (errno)); + xfree (fname); + break; + } + + /* Parse and check that file. */ + pkt = xmalloc (sizeof *pkt); + init_packet (pkt); + init_parse_packet (&parsectx, a); + err = parse_packet (&parsectx, pkt); + deinit_parse_packet (&parsectx); + iobuf_close (a); + iobuf_ioctl (NULL, IOBUF_IOCTL_INVALIDATE_CACHE, 0, (char *) fname); + if (!err && pkt->pkttype != PKT_SECRET_KEY + && pkt->pkttype != PKT_SECRET_SUBKEY) + err = GPG_ERR_NO_SECKEY; + if (err) + { + tty_printf (_("Error reading backup key from '%s': %s\n"), + fname, gpg_strerror (err)); + xfree (fname); + free_packet (pkt, NULL); + xfree (pkt); + break; + } + + xfree (fname); + node = new_kbnode (pkt); + + /* Transfer it to gpg-agent which handles secret keys. */ + err = transfer_secret_keys (ctrl, NULL, node, 1, 1, 0); + + /* Treat the pkt as a public key. */ + pkt->pkttype = PKT_PUBLIC_KEY; + + /* Ask gpg-agent to store the secret key to card. */ + if (card_store_subkey (node, 0)) + { + redisplay = 1; + sec_shadowing = 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")); + if (!opt.expert) + tty_printf (_("(Use the '%s' command.)\n"), "key"); + } + 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); + redisplay = 1; + modified = 1; + } + } + break; + + case cmdADDREVOKER: + { + int sensitive = 0; + + if (ascii_strcasecmp (arg_string, "sensitive") == 0) + sensitive = 1; + if (menu_addrevoker (ctrl, keyblock, sensitive)) + { + redisplay = 1; + modified = 1; + merge_keys_and_selfsig (ctrl, keyblock); + } + } + break; + + case cmdREVUID: + { + int n1; + + if (!(n1 = count_selected_uids (keyblock))) + { + tty_printf (_("You must select at least one user ID.\n")); + if (!opt.expert) + tty_printf (_("(Use the '%s' command.)\n"), "uid"); + } + 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 (ctrl, 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 (ctrl, 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 (ctrl, keyblock)) + modified = 1; + + redisplay = 1; + } + + if (modified) + merge_keys_and_selfsig (ctrl, keyblock); + } + break; + + case cmdEXPIRE: + if (gpg_err_code (menu_expire (ctrl, keyblock, 0, 0)) == GPG_ERR_TRUE) + { + merge_keys_and_selfsig (ctrl, keyblock); + run_subkey_warnings = 1; + modified = 1; + redisplay = 1; + } + break; + + case cmdCHANGEUSAGE: + if (menu_changeusage (ctrl, keyblock)) + { + merge_keys_and_selfsig (ctrl, keyblock); + modified = 1; + redisplay = 1; + } + break; + + case cmdBACKSIGN: + if (menu_backsign (ctrl, keyblock)) + { + modified = 1; + redisplay = 1; + } + break; + + case cmdPRIMARY: + if (menu_set_primary_uid (ctrl, keyblock)) + { + merge_keys_and_selfsig (ctrl, keyblock); + modified = 1; + redisplay = 1; + } + break; + + case cmdPASSWD: + change_passphrase (ctrl, keyblock); + break; + +#ifndef NO_TRUST_MODELS + case cmdTRUST: + if (opt.trust_model == TM_EXTERNAL) + { + tty_printf (_("Owner trust may not be set while " + "using a user provided trust database\n")); + break; + } + + show_key_with_all_names (ctrl, NULL, keyblock, 0, 0, 0, 1, 0, 0); + tty_printf ("\n"); + if (edit_ownertrust (ctrl, 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; +#endif /*!NO_TRUST_MODELS*/ + + case cmdPREF: + { + int count = count_selected_uids (keyblock); + log_assert (keyblock->pkt->pkttype == PKT_PUBLIC_KEY); + show_names (ctrl, NULL, keyblock, keyblock->pkt->pkt.public_key, + count ? NODFLG_SELUID : 0, 1); + } + break; + + case cmdSHOWPREF: + { + int count = count_selected_uids (keyblock); + log_assert (keyblock->pkt->pkttype == PKT_PUBLIC_KEY); + show_names (ctrl, NULL, 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 (ctrl, keyblock)) + { + merge_keys_and_selfsig (ctrl, keyblock); + modified = 1; + redisplay = 1; + } + } + } + break; + + case cmdPREFKS: + if (menu_set_keyserver_url (ctrl, *arg_string ? arg_string : NULL, + keyblock)) + { + merge_keys_and_selfsig (ctrl, keyblock); + modified = 1; + redisplay = 1; + } + break; + + case cmdNOTATION: + if (menu_set_notation (ctrl, *arg_string ? arg_string : NULL, + keyblock)) + { + merge_keys_and_selfsig (ctrl, keyblock); + modified = 1; + redisplay = 1; + } + break; + + case cmdNOP: + break; + + case cmdREVSIG: + if (menu_revsig (ctrl, keyblock)) + { + redisplay = 1; + modified = 1; + } + break; + +#ifndef NO_TRUST_MODELS + case cmdENABLEKEY: + case cmdDISABLEKEY: + if (enable_disable_key (ctrl, keyblock, cmd == cmdDISABLEKEY)) + { + redisplay = 1; + modified = 1; + } + break; +#endif /*!NO_TRUST_MODELS*/ + + case cmdSHOWPHOTO: + menu_showphoto (ctrl, keyblock); + break; + + case cmdCLEAN: + if (menu_clean (ctrl, keyblock, 0)) + redisplay = modified = 1; + break; + + case cmdMINIMIZE: + if (menu_clean (ctrl, keyblock, 1)) + redisplay = modified = 1; + break; + + case cmdQUIT: + if (have_commands) + goto leave; + if (!modified && !sec_shadowing) + 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 through */ + case cmdSAVE: + if (modified) + { + err = keydb_update_keyblock (ctrl, kdbhd, keyblock); + if (err) + { + log_error (_("update failed: %s\n"), gpg_strerror (err)); + break; + } + } + + if (sec_shadowing) + { + err = agent_scd_learn (NULL, 1); + if (err) + { + log_error (_("update failed: %s\n"), gpg_strerror (err)); + break; + } + } + + if (!modified && !sec_shadowing) + tty_printf (_("Key not changed so no update needed.\n")); + + if (update_trust) + { + revalidation_mark (ctrl); + update_trust = 0; + } + goto leave; + + case cmdINVCMD: + default: + tty_printf ("\n"); + tty_printf (_("Invalid command (try \"help\")\n")); + break; + } + } /* End of the main command loop. */ + + leave: + release_kbnode (keyblock); + keydb_release (kdbhd); + xfree (answer); +} + + +/* Change the passphrase of the secret key identified by USERNAME. */ +void +keyedit_passwd (ctrl_t ctrl, const char *username) +{ + gpg_error_t err; + PKT_public_key *pk; + kbnode_t keyblock = NULL; + + pk = xtrycalloc (1, sizeof *pk); + if (!pk) + { + err = gpg_error_from_syserror (); + goto leave; + } + err = getkey_byname (ctrl, NULL, pk, username, 1, &keyblock); + if (err) + goto leave; + + err = change_passphrase (ctrl, keyblock); + +leave: + release_kbnode (keyblock); + free_public_key (pk); + if (err) + { + log_info ("error changing the passphrase for '%s': %s\n", + username, gpg_strerror (err)); + write_status_error ("keyedit.passwd", err); + } + else + write_status_text (STATUS_SUCCESS, "keyedit.passwd"); +} + + +/* Helper for quick commands to find the keyblock for USERNAME. + * Returns on success the key database handle at R_KDBHD and the + * keyblock at R_KEYBLOCK. */ +static gpg_error_t +quick_find_keyblock (ctrl_t ctrl, const char *username, int want_secret, + KEYDB_HANDLE *r_kdbhd, kbnode_t *r_keyblock) +{ + gpg_error_t err; + KEYDB_HANDLE kdbhd = NULL; + kbnode_t keyblock = NULL; + KEYDB_SEARCH_DESC desc; + kbnode_t node; + + *r_kdbhd = NULL; + *r_keyblock = NULL; + + /* Search the key; we don't want the whole getkey stuff here. */ + kdbhd = keydb_new (); + if (!kdbhd) + { + /* Note that keydb_new has already used log_error. */ + err = gpg_error_from_syserror (); + goto leave; + } + + err = classify_user_id (username, &desc, 1); + if (!err) + err = keydb_search (kdbhd, &desc, 1, NULL); + if (!err) + { + err = keydb_get_keyblock (kdbhd, &keyblock); + if (err) + { + log_error (_("error reading keyblock: %s\n"), gpg_strerror (err)); + goto leave; + } + /* Now with the keyblock retrieved, search again to detect an + ambiguous specification. We need to save the found state so + that we can do an update later. */ + keydb_push_found_state (kdbhd); + err = keydb_search (kdbhd, &desc, 1, NULL); + if (!err) + err = gpg_error (GPG_ERR_AMBIGUOUS_NAME); + else if (gpg_err_code (err) == GPG_ERR_NOT_FOUND) + err = 0; + keydb_pop_found_state (kdbhd); + + if (!err && want_secret) + { + /* We require the secret primary key to set the primary UID. */ + node = find_kbnode (keyblock, PKT_PUBLIC_KEY); + log_assert (node); + err = agent_probe_secret_key (ctrl, node->pkt->pkt.public_key); + } + } + else if (gpg_err_code (err) == GPG_ERR_NOT_FOUND) + err = gpg_error (GPG_ERR_NO_PUBKEY); + + if (err) + { + log_error (_("key \"%s\" not found: %s\n"), + username, gpg_strerror (err)); + goto leave; + } + + fix_keyblock (ctrl, &keyblock); + merge_keys_and_selfsig (ctrl, keyblock); + + *r_keyblock = keyblock; + keyblock = NULL; + *r_kdbhd = kdbhd; + kdbhd = NULL; + + leave: + release_kbnode (keyblock); + keydb_release (kdbhd); + return err; +} + + +/* Unattended adding of a new keyid. USERNAME specifies the + key. NEWUID is the new user id to add to the key. */ +void +keyedit_quick_adduid (ctrl_t ctrl, const char *username, const char *newuid) +{ + gpg_error_t err; + KEYDB_HANDLE kdbhd = NULL; + kbnode_t keyblock = NULL; + char *uidstring = NULL; + + uidstring = xstrdup (newuid); + trim_spaces (uidstring); + if (!*uidstring) + { + log_error ("%s\n", gpg_strerror (GPG_ERR_INV_USER_ID)); + goto leave; + } + +#ifdef HAVE_W32_SYSTEM + /* See keyedit_menu for why we need this. */ + check_trustdb_stale (ctrl); +#endif + + /* Search the key; we don't want the whole getkey stuff here. */ + err = quick_find_keyblock (ctrl, username, 1, &kdbhd, &keyblock); + if (err) + goto leave; + + if (menu_adduid (ctrl, keyblock, 0, NULL, uidstring)) + { + err = keydb_update_keyblock (ctrl, kdbhd, keyblock); + if (err) + { + log_error (_("update failed: %s\n"), gpg_strerror (err)); + goto leave; + } + + if (update_trust) + revalidation_mark (ctrl); + } + + leave: + xfree (uidstring); + release_kbnode (keyblock); + keydb_release (kdbhd); +} + + +/* Helper to find the UID node for namehash. On success, returns the UID node. + Otherwise, return NULL. */ +kbnode_t +find_userid_by_namehash (kbnode_t keyblock, const char *namehash) +{ + byte hash[NAMEHASH_LEN]; + kbnode_t node = NULL; + + if (!namehash) + goto leave; + + if (strlen (namehash) != NAMEHASH_LEN * 2) + goto leave; + + if (hex2bin (namehash, hash, NAMEHASH_LEN) < 0) + goto leave; + + for (node = keyblock; 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)) + break; + } + } + + leave: + return node; +} + + +/* Helper to find the UID node for uid. On success, returns the UID node. + Otherwise, return NULL. */ +kbnode_t +find_userid (kbnode_t keyblock, const char *uid) +{ + kbnode_t node = NULL; + size_t uidlen; + + if (!keyblock || !uid) + goto leave; + + /* First try to find UID by namehash. */ + node = find_userid_by_namehash (keyblock, uid); + if (node) + goto leave; + + uidlen = strlen (uid); + for (node = keyblock; node; node = node->next) + { + if (node->pkt->pkttype == PKT_USER_ID + && uidlen == node->pkt->pkt.user_id->len + && !memcmp (node->pkt->pkt.user_id->name, uid, uidlen)) + break; + } + + leave: + return node; +} + + +/* Unattended revocation of a keyid. USERNAME specifies the + key. UIDTOREV is the user id revoke from the key. */ +void +keyedit_quick_revuid (ctrl_t ctrl, const char *username, const char *uidtorev) +{ + gpg_error_t err; + KEYDB_HANDLE kdbhd = NULL; + kbnode_t keyblock = NULL; + kbnode_t node; + int modified = 0; + size_t valid_uids; + +#ifdef HAVE_W32_SYSTEM + /* See keyedit_menu for why we need this. */ + check_trustdb_stale (ctrl); +#endif + + /* Search the key; we don't want the whole getkey stuff here. */ + err = quick_find_keyblock (ctrl, username, 1, &kdbhd, &keyblock); + if (err) + goto leave; + + /* To make sure that we do not revoke the last valid UID, we first + count how many valid UIDs there are. */ + valid_uids = 0; + for (node = keyblock; node; node = node->next) + valid_uids += (node->pkt->pkttype == PKT_USER_ID + && !node->pkt->pkt.user_id->flags.revoked + && !node->pkt->pkt.user_id->flags.expired); + + /* Find the right UID. */ + node = find_userid (keyblock, uidtorev); + if (node) + { + struct revocation_reason_info *reason; + + /* Make sure that we do not revoke the last valid UID. */ + if (valid_uids == 1 + && ! node->pkt->pkt.user_id->flags.revoked + && ! node->pkt->pkt.user_id->flags.expired) + { + log_error (_("cannot revoke the last valid user ID.\n")); + err = gpg_error (GPG_ERR_INV_USER_ID); + goto leave; + } + + reason = get_default_uid_revocation_reason (); + err = core_revuid (ctrl, keyblock, node, reason, &modified); + release_revocation_reason_info (reason); + if (err) + goto leave; + err = keydb_update_keyblock (ctrl, kdbhd, keyblock); + if (err) + { + log_error (_("update failed: %s\n"), gpg_strerror (err)); + goto leave; + } + + revalidation_mark (ctrl); + goto leave; + } + err = gpg_error (GPG_ERR_NO_USER_ID); + + + leave: + if (err) + { + log_error (_("revoking the user ID failed: %s\n"), gpg_strerror (err)); + write_status_error ("keyedit.revoke.uid", err); + } + release_kbnode (keyblock); + keydb_release (kdbhd); +} + + +/* Unattended setting of the primary uid. USERNAME specifies the key. + PRIMARYUID is the user id which shall be primary. */ +void +keyedit_quick_set_primary (ctrl_t ctrl, const char *username, + const char *primaryuid) +{ + gpg_error_t err; + KEYDB_HANDLE kdbhd = NULL; + kbnode_t keyblock = NULL; + kbnode_t node; + size_t primaryuidlen; + int any; + +#ifdef HAVE_W32_SYSTEM + /* See keyedit_menu for why we need this. */ + check_trustdb_stale (ctrl); +#endif + + err = quick_find_keyblock (ctrl, username, 1, &kdbhd, &keyblock); + if (err) + goto leave; + + /* Find and mark the UID - we mark only the first valid one. */ + primaryuidlen = strlen (primaryuid); + any = 0; + for (node = keyblock; node; node = node->next) + { + if (node->pkt->pkttype == PKT_USER_ID + && !any + && !node->pkt->pkt.user_id->flags.revoked + && !node->pkt->pkt.user_id->flags.expired + && primaryuidlen == node->pkt->pkt.user_id->len + && !memcmp (node->pkt->pkt.user_id->name, primaryuid, primaryuidlen)) + { + node->flag |= NODFLG_SELUID; + any = 1; + } + else + node->flag &= ~NODFLG_SELUID; + } + + if (!any) + err = gpg_error (GPG_ERR_NO_USER_ID); + else if (menu_set_primary_uid (ctrl, keyblock)) + { + merge_keys_and_selfsig (ctrl, keyblock); + err = keydb_update_keyblock (ctrl, kdbhd, keyblock); + if (err) + { + log_error (_("update failed: %s\n"), gpg_strerror (err)); + goto leave; + } + revalidation_mark (ctrl); + } + else + err = gpg_error (GPG_ERR_GENERAL); + + if (err) + log_error (_("setting the primary user ID failed: %s\n"), + gpg_strerror (err)); + + leave: + release_kbnode (keyblock); + keydb_release (kdbhd); +} + + +/* Find a keyblock by fingerprint because only this uniquely + * identifies a key and may thus be used to select a key for + * unattended subkey creation os key signing. */ +static gpg_error_t +find_by_primary_fpr (ctrl_t ctrl, const char *fpr, + kbnode_t *r_keyblock, KEYDB_HANDLE *r_kdbhd) +{ + gpg_error_t err; + kbnode_t keyblock = NULL; + KEYDB_HANDLE kdbhd = NULL; + KEYDB_SEARCH_DESC desc; + byte fprbin[MAX_FINGERPRINT_LEN]; + size_t fprlen; + + *r_keyblock = NULL; + *r_kdbhd = NULL; + + if (classify_user_id (fpr, &desc, 1) + || !(desc.mode == KEYDB_SEARCH_MODE_FPR + || desc.mode == KEYDB_SEARCH_MODE_FPR16 + || desc.mode == KEYDB_SEARCH_MODE_FPR20)) + { + log_error (_("\"%s\" is not a fingerprint\n"), fpr); + err = gpg_error (GPG_ERR_INV_NAME); + goto leave; + } + err = get_pubkey_byname (ctrl, GET_PUBKEY_NO_AKL, + NULL, NULL, fpr, &keyblock, &kdbhd, 1); + if (err) + { + log_error (_("key \"%s\" not found: %s\n"), fpr, gpg_strerror (err)); + goto leave; + } + + /* Check that the primary fingerprint has been given. */ + fingerprint_from_pk (keyblock->pkt->pkt.public_key, fprbin, &fprlen); + if (fprlen == 16 && desc.mode == KEYDB_SEARCH_MODE_FPR16 + && !memcmp (fprbin, desc.u.fpr, 16)) + ; + else if (fprlen == 16 && desc.mode == KEYDB_SEARCH_MODE_FPR + && !memcmp (fprbin, desc.u.fpr, 16) + && !desc.u.fpr[16] + && !desc.u.fpr[17] + && !desc.u.fpr[18] + && !desc.u.fpr[19]) + ; + else if (fprlen == 20 && (desc.mode == KEYDB_SEARCH_MODE_FPR20 + || desc.mode == KEYDB_SEARCH_MODE_FPR) + && !memcmp (fprbin, desc.u.fpr, 20)) + ; + else + { + log_error (_("\"%s\" is not the primary fingerprint\n"), fpr); + err = gpg_error (GPG_ERR_INV_NAME); + goto leave; + } + + *r_keyblock = keyblock; + keyblock = NULL; + *r_kdbhd = kdbhd; + kdbhd = NULL; + err = 0; + + leave: + release_kbnode (keyblock); + keydb_release (kdbhd); + return err; +} + + +/* Unattended key signing function. If the key specifified by FPR is + available and FPR is the primary fingerprint all user ids of the + key are signed using the default signing key. If UIDS is an empty + list all usable UIDs are signed, if it is not empty, only those + user ids matching one of the entries of the list are signed. With + LOCAL being true the signatures are marked as non-exportable. */ +void +keyedit_quick_sign (ctrl_t ctrl, const char *fpr, strlist_t uids, + strlist_t locusr, int local) +{ + gpg_error_t err; + kbnode_t keyblock = NULL; + KEYDB_HANDLE kdbhd = NULL; + int modified = 0; + PKT_public_key *pk; + kbnode_t node; + strlist_t sl; + int any; + +#ifdef HAVE_W32_SYSTEM + /* See keyedit_menu for why we need this. */ + check_trustdb_stale (ctrl); +#endif + + /* We require a fingerprint because only this uniquely identifies a + key and may thus be used to select a key for unattended key + signing. */ + if (find_by_primary_fpr (ctrl, fpr, &keyblock, &kdbhd)) + goto leave; + + if (fix_keyblock (ctrl, &keyblock)) + modified++; + + /* Give some info in verbose. */ + if (opt.verbose) + { + show_key_with_all_names (ctrl, es_stdout, keyblock, 0, + 1/*with_revoker*/, 1/*with_fingerprint*/, + 0, 0, 1); + es_fflush (es_stdout); + } + + pk = keyblock->pkt->pkt.public_key; + if (pk->flags.revoked) + { + if (!opt.verbose) + show_key_with_all_names (ctrl, es_stdout, keyblock, 0, 0, 0, 0, 0, 1); + log_error ("%s%s", _("Key is revoked."), _(" Unable to sign.\n")); + goto leave; + } + + /* Set the flags according to the UIDS list. Fixme: We may want to + use classify_user_id along with dedicated compare functions so + that we match the same way as in the key lookup. */ + any = 0; + menu_select_uid (keyblock, 0); /* Better clear the flags first. */ + for (sl=uids; sl; sl = sl->next) + { + const char *name = sl->d; + int count = 0; + + sl->flags &= ~(1|2); /* Clear flags used for error reporting. */ + + for (node = keyblock; node; node = node->next) + { + if (node->pkt->pkttype == PKT_USER_ID) + { + PKT_user_id *uid = node->pkt->pkt.user_id; + + if (uid->attrib_data) + ; + else if (*name == '=' + && strlen (name+1) == uid->len + && !memcmp (uid->name, name + 1, uid->len)) + { /* Exact match - we don't do a check for ambiguity + * in this case. */ + node->flag |= NODFLG_SELUID; + if (any != -1) + { + sl->flags |= 1; /* Report as found. */ + any = 1; + } + } + else if (ascii_memistr (uid->name, uid->len, + *name == '*'? name+1:name)) + { + node->flag |= NODFLG_SELUID; + if (any != -1) + { + sl->flags |= 1; /* Report as found. */ + any = 1; + } + count++; + } + } + } + + if (count > 1) + { + any = -1; /* Force failure at end. */ + sl->flags |= 2; /* Report as ambiguous. */ + } + } + + /* Check whether all given user ids were found. */ + for (sl=uids; sl; sl = sl->next) + if (!(sl->flags & 1)) + any = -1; /* That user id was not found. */ + + /* Print an error if there was a problem with the user ids. */ + if (uids && any < 1) + { + if (!opt.verbose) + show_key_with_all_names (ctrl, es_stdout, keyblock, 0, 0, 0, 0, 0, 1); + es_fflush (es_stdout); + for (sl=uids; sl; sl = sl->next) + { + if ((sl->flags & 2)) + log_info (_("Invalid user ID '%s': %s\n"), + sl->d, gpg_strerror (GPG_ERR_AMBIGUOUS_NAME)); + else if (!(sl->flags & 1)) + log_info (_("Invalid user ID '%s': %s\n"), + sl->d, gpg_strerror (GPG_ERR_NOT_FOUND)); + } + log_error ("%s %s", _("No matching user IDs."), _("Nothing to sign.\n")); + goto leave; + } + + /* Sign. */ + sign_uids (ctrl, es_stdout, keyblock, locusr, &modified, local, 0, 0, 0, 1); + es_fflush (es_stdout); + + if (modified) + { + err = keydb_update_keyblock (ctrl, kdbhd, keyblock); + if (err) + { + log_error (_("update failed: %s\n"), gpg_strerror (err)); + goto leave; + } + } + else + log_info (_("Key not changed so no update needed.\n")); + + if (update_trust) + revalidation_mark (ctrl); + + + leave: + release_kbnode (keyblock); + keydb_release (kdbhd); +} + + +/* Unattended revocation of a key signatures. USERNAME specifies the + * key; this should best be a fingerprint. SIGTOREV is the user-id of + * the key for which the key signature shall be removed. Only + * non-self-signatures can be removed with this functions. If + * AFFECTED_UIDS is not NULL only the key signatures on these user-ids + * are revoked. */ +void +keyedit_quick_revsig (ctrl_t ctrl, const char *username, const char *sigtorev, + strlist_t affected_uids) +{ + gpg_error_t err; + int no_signing_key = 0; + KEYDB_HANDLE kdbhd = NULL; + kbnode_t keyblock = NULL; + PKT_public_key *primarypk; /* Points into KEYBLOCK. */ + u32 *primarykid; + PKT_public_key *pksigtorev = NULL; + u32 *pksigtorevkid; + kbnode_t node, n; + int skip_remaining; + int consider_sig; + strlist_t sl; + struct sign_attrib attrib = { 0 }; + +#ifdef HAVE_W32_SYSTEM + /* See keyedit_menu for why we need this. */ + check_trustdb_stale (ctrl); +#endif + + /* Search the key; we don't want the whole getkey stuff here. Noet + * that we are looking for the public key here. */ + err = quick_find_keyblock (ctrl, username, 0, &kdbhd, &keyblock); + if (err) + goto leave; + log_assert (keyblock->pkt->pkttype == PKT_PUBLIC_KEY + || keyblock->pkt->pkttype == PKT_SECRET_KEY); + primarypk = keyblock->pkt->pkt.public_key; + primarykid = pk_keyid (primarypk); + + /* Get the signing key we want to revoke. This must be one of our + * signing keys. We will compare only the keyid because we don't + * assume that we have duplicated keyids on our own secret keys. If + * a there is a duplicated one we will notice this when creating the + * revocation. */ + pksigtorev = xtrycalloc (1, sizeof *pksigtorev); + if (!pksigtorev) + { + err = gpg_error_from_syserror (); + goto leave; + } + pksigtorev->req_usage = PUBKEY_USAGE_CERT; + err = getkey_byname (ctrl, NULL, pksigtorev, sigtorev, 1, NULL); + if (err) + { + no_signing_key = 1; + goto leave; + } + pksigtorevkid = pk_keyid (pksigtorev); + + /* Find the signatures we want to revoke and set a mark. */ + skip_remaining = consider_sig = 0; + for (node = keyblock; node; node = node->next) + { + node->flag &= ~NODFLG_MARK_A; + if (skip_remaining) + ; + else if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY) + skip_remaining = 1; + else if (node->pkt->pkttype == PKT_USER_ID) + { + PKT_user_id *uid = node->pkt->pkt.user_id; + + consider_sig = !affected_uids; + for (sl = affected_uids; !consider_sig && sl; sl = sl->next) + { + const char *name = sl->d; + + if (uid->attrib_data) + ; + else if (*name == '=' + && strlen (name+1) == uid->len + && !memcmp (uid->name, name + 1, uid->len)) + { /* Exact match. */ + consider_sig = 1; + } + else if (ascii_memistr (uid->name, uid->len, + *name == '*'? name+1:name)) + { /* Case-insensitive substring match. */ + consider_sig = 1; + } + } + } + else if (node->pkt->pkttype == PKT_SIGNATURE) + { + /* We need to sort the signatures so that we can figure out + * whether the key signature has been revoked or the + * revocation has been superseded by a new key + * signature. */ + PKT_signature *sig; + unsigned int sigcount = 0; + kbnode_t *sigarray; + + /* Allocate an array large enogh for all signatures. */ + for (n=node; n && n->pkt->pkttype == PKT_SIGNATURE; n = n->next) + sigcount++; + sigarray = xtrycalloc (sigcount, sizeof *sigarray); + if (!sigarray) + { + err = gpg_error_from_syserror (); + goto leave; + } + + /* Now fill the array with signatures we are interested in. + * We also move NODE forward to the end. */ + sigcount = 0; + for (n=node; n && n->pkt->pkttype == PKT_SIGNATURE; node=n, n=n->next) + { + sig = n->pkt->pkt.signature; + if (!keyid_cmp (primarykid, sig->keyid)) + continue; /* Ignore self-signatures. */ + + if (keyid_cmp (pksigtorevkid, sig->keyid)) + continue; /* Ignore non-matching signatures. */ + + n->flag &= ~NODFLG_MARK_B; /* Clear flag used by cmp_signode. */ + sigarray[sigcount++] = n; + } + + if (sigcount) + { + qsort (sigarray, sigcount, sizeof *sigarray, cmp_signodes); + + /* log_debug ("Sorted signatures:\n"); */ + /* for (idx=0; idx < sigcount; idx++) */ + /* { */ + /* sig = sigarray[idx]->pkt->pkt.signature; */ + /* log_debug ("%s 0x%02x %s\n", keystr (sig->keyid), */ + /* sig->sig_class, datestr_from_sig (sig)); */ + /* } */ + sig = sigarray[sigcount-1]->pkt->pkt.signature; + if ((consider_sig || !affected_uids) && IS_UID_REV (sig)) + { + if (!opt.quiet) + log_info ("sig by %s already revoked at %s\n", + keystr (sig->keyid), datestr_from_sig (sig)); + } + else if ((consider_sig && IS_UID_SIG (sig)) + || (!affected_uids && IS_KEY_SIG (sig))) + node->flag |= NODFLG_MARK_A; /* Select signature. */ + } + + xfree (sigarray); + } + } + + /* Check whether any signatures were done by the given key. We do + * not return an error if none were found. */ + for (node = keyblock; node; node = node->next) + if ((node->flag & NODFLG_MARK_A)) + break; + if (!node) + { + if (opt.verbose) + log_info (_("Not signed by you.\n")); + err = 0; + goto leave; + } + + /* Revoke all marked signatures. */ + attrib.reason = get_default_sig_revocation_reason (); + + reloop: /* (we must repeat because we are modifying the list) */ + for (node = keyblock; node; node = node->next) + { + kbnode_t unode; + PKT_signature *sig; + PACKET *pkt; + + if (!(node->flag & NODFLG_MARK_A)) + continue; + node->flag &= ~NODFLG_MARK_A; + + if (IS_KEY_SIG (node->pkt->pkt.signature)) + unode = NULL; + else + { + unode = find_prev_kbnode (keyblock, node, PKT_USER_ID); + log_assert (unode); + } + + attrib.non_exportable = !node->pkt->pkt.signature->flags.exportable; + + err = make_keysig_packet (ctrl, &sig, primarypk, + unode? unode->pkt->pkt.user_id : NULL, + NULL, pksigtorev, 0x30, 0, 0, 0, + sign_mk_attrib, &attrib, NULL); + if (err) + { + log_error ("signing failed: %s\n", gpg_strerror (err)); + goto leave; + } + + pkt = xmalloc_clear (sizeof *pkt); + pkt->pkttype = PKT_SIGNATURE; + pkt->pkt.signature = sig; + if (unode) + insert_kbnode (unode, new_kbnode (pkt), 0); + goto reloop; + } + + err = keydb_update_keyblock (ctrl, kdbhd, keyblock); + if (err) + { + log_error (_("update failed: %s\n"), gpg_strerror (err)); + goto leave; + } + revalidation_mark (ctrl); + + leave: + if (err) + { + log_error (_("revoking the key signature failed: %s\n"), + gpg_strerror (err)); + if (no_signing_key) + print_further_info ("error getting key used to make the key signature"); + write_status_error ("keyedit.revoke.sig", err); + } + release_revocation_reason_info (attrib.reason); + free_public_key (pksigtorev); + release_kbnode (keyblock); + keydb_release (kdbhd); +} + + +/* Unattended subkey creation function. + * + */ +void +keyedit_quick_addkey (ctrl_t ctrl, const char *fpr, const char *algostr, + const char *usagestr, const char *expirestr) +{ + gpg_error_t err; + kbnode_t keyblock; + KEYDB_HANDLE kdbhd; + int modified = 0; + PKT_public_key *pk; + +#ifdef HAVE_W32_SYSTEM + /* See keyedit_menu for why we need this. */ + check_trustdb_stale (ctrl); +#endif + + /* We require a fingerprint because only this uniquely identifies a + * key and may thus be used to select a key for unattended subkey + * creation. */ + if (find_by_primary_fpr (ctrl, fpr, &keyblock, &kdbhd)) + goto leave; + + if (fix_keyblock (ctrl, &keyblock)) + modified++; + + pk = keyblock->pkt->pkt.public_key; + if (pk->flags.revoked) + { + if (!opt.verbose) + show_key_with_all_names (ctrl, es_stdout, keyblock, 0, 0, 0, 0, 0, 1); + log_error ("%s%s", _("Key is revoked."), "\n"); + goto leave; + } + + /* Create the subkey. Note that the called function already prints + * an error message. */ + if (!generate_subkeypair (ctrl, keyblock, algostr, usagestr, expirestr)) + modified = 1; + es_fflush (es_stdout); + + /* Store. */ + if (modified) + { + err = keydb_update_keyblock (ctrl, kdbhd, keyblock); + if (err) + { + log_error (_("update failed: %s\n"), gpg_strerror (err)); + goto leave; + } + } + else + log_info (_("Key not changed so no update needed.\n")); + + leave: + release_kbnode (keyblock); + keydb_release (kdbhd); +} + + +/* Unattended expiration setting function for the main key. If + * SUBKEYFPRS is not NULL and SUBKEYSFPRS[0] is neither NULL, it is + * expected to be an array of fingerprints for subkeys to change. It + * may also be an array which just one item "*" to indicate that all + * keys shall be set to that expiration date. + */ +void +keyedit_quick_set_expire (ctrl_t ctrl, const char *fpr, const char *expirestr, + char **subkeyfprs) +{ + gpg_error_t err; + kbnode_t keyblock, node; + KEYDB_HANDLE kdbhd; + int modified = 0; + PKT_public_key *pk; + u32 expire; + int primary_only = 0; + int idx; + +#ifdef HAVE_W32_SYSTEM + /* See keyedit_menu for why we need this. */ + check_trustdb_stale (ctrl); +#endif + + /* We require a fingerprint because only this uniquely identifies a + * key and may thus be used to select a key for unattended + * expiration setting. */ + err = find_by_primary_fpr (ctrl, fpr, &keyblock, &kdbhd); + if (err) + goto leave; + + if (fix_keyblock (ctrl, &keyblock)) + modified++; + + pk = keyblock->pkt->pkt.public_key; + if (pk->flags.revoked) + { + if (!opt.verbose) + show_key_with_all_names (ctrl, es_stdout, keyblock, 0, 0, 0, 0, 0, 1); + log_error ("%s%s", _("Key is revoked."), "\n"); + err = gpg_error (GPG_ERR_CERT_REVOKED); + goto leave; + } + + expire = parse_expire_string (expirestr); + if (expire == (u32)-1 ) + { + log_error (_("'%s' is not a valid expiration time\n"), expirestr); + err = gpg_error (GPG_ERR_INV_VALUE); + goto leave; + } + if (expire) + expire += make_timestamp (); + + /* Check whether a subkey's expiration time shall be changed or the + * expiration time of all keys. */ + if (!subkeyfprs || !subkeyfprs[0]) + primary_only = 1; + else if ( !strcmp (subkeyfprs[0], "*") && !subkeyfprs[1]) + { + /* Change all subkeys keys which have not been revoked and are + * not yet expired. */ + merge_keys_and_selfsig (ctrl, keyblock); + for (node = keyblock; node; node = node->next) + { + if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY + && (pk = node->pkt->pkt.public_key) + && !pk->flags.revoked + && !pk->has_expired) + node->flag |= NODFLG_SELKEY; + } + } + else + { + /* Change specified subkeys. */ + KEYDB_SEARCH_DESC desc; + byte fprbin[MAX_FINGERPRINT_LEN]; + size_t fprlen; + + err = 0; + merge_keys_and_selfsig (ctrl, keyblock); + for (idx=0; subkeyfprs[idx]; idx++) + { + int any = 0; + + /* Parse the fingerprint. */ + if (classify_user_id (subkeyfprs[idx], &desc, 1) + || !(desc.mode == KEYDB_SEARCH_MODE_FPR + || desc.mode == KEYDB_SEARCH_MODE_FPR20)) + { + log_error (_("\"%s\" is not a proper fingerprint\n"), + subkeyfprs[idx] ); + if (!err) + err = gpg_error (GPG_ERR_INV_NAME); + continue; + } + + /* Set the flag for the matching non revoked subkey. */ + for (node = keyblock; node; node = node->next) + { + if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY + && (pk = node->pkt->pkt.public_key) + && !pk->flags.revoked ) + { + fingerprint_from_pk (pk, fprbin, &fprlen); + if (fprlen == 20 && !memcmp (fprbin, desc.u.fpr, 20)) + { + node->flag |= NODFLG_SELKEY; + any = 1; + } + } + } + if (!any) + { + log_error (_("subkey \"%s\" not found\n"), subkeyfprs[idx]); + if (!err) + err = gpg_error (GPG_ERR_NOT_FOUND); + } + } + + if (err) + goto leave; + } + + /* Set the new expiration date. */ + err = menu_expire (ctrl, keyblock, primary_only? 1 : 2, expire); + if (gpg_err_code (err) == GPG_ERR_TRUE) + modified = 1; + else if (err) + goto leave; + es_fflush (es_stdout); + + /* Store. */ + if (modified) + { + err = keydb_update_keyblock (ctrl, kdbhd, keyblock); + if (err) + { + log_error (_("update failed: %s\n"), gpg_strerror (err)); + goto leave; + } + if (update_trust) + revalidation_mark (ctrl); + } + else + log_info (_("Key not changed so no update needed.\n")); + + leave: + release_kbnode (keyblock); + keydb_release (kdbhd); + if (err) + write_status_error ("set_expire", err); +} + + + +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) + { + if (any) + tty_printf (", "); + any = 1; + /* We don't want to display strings for experimental algos */ + if (!openpgp_cipher_test_algo (prefs[i].value) + && prefs[i].value < 100) + tty_printf ("%s", openpgp_cipher_algo_name (prefs[i].value)); + 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", openpgp_cipher_algo_name (CIPHER_ALGO_3DES)); + } + tty_printf ("\n "); + tty_printf (_("AEAD: ")); + for (i = any = 0; prefs[i].type; i++) + { + if (prefs[i].type == PREFTYPE_AEAD) + { + if (any) + tty_printf (", "); + any = 1; + /* We don't want to display strings for experimental algos */ + if (!openpgp_aead_test_algo (prefs[i].value) + && prefs[i].value < 100) + tty_printf ("%s", openpgp_aead_algo_name (prefs[i].value)); + else + tty_printf ("[%d]", prefs[i].value); + } + } + tty_printf ("\n "); + tty_printf (_("Digest: ")); + for (i = any = 0; prefs[i].type; i++) + { + if (prefs[i].type == PREFTYPE_HASH) + { + if (any) + tty_printf (", "); + any = 1; + /* We don't want to display strings for experimental algos */ + if (!gcry_md_test_algo (prefs[i].value) && prefs[i].value < 100) + tty_printf ("%s", gcry_md_algo_name (prefs[i].value)); + 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", gcry_md_algo_name (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.aead || !uid->flags.ks_modify) + { + tty_printf ("\n "); + tty_printf (_("Features: ")); + any = 0; + if (uid->flags.mdc) + { + tty_printf ("MDC"); + any = 1; + } + if (!uid->flags.aead) + { + if (any) + tty_printf (", "); + tty_printf ("AEAD"); + } + 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_AEAD ? 'A' : + 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.aead) + tty_printf (" [aead]"); + 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 (ctrl_t ctrl, estream_t fp, kbnode_t keyblock) +{ + KBNODE node; + int i, j, ulti_hack = 0; + byte pk_version = 0; + PKT_public_key *primary = NULL; + int have_seckey; + + if (!fp) + fp = es_stdout; + + /* 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); + have_seckey = !agent_probe_secret_key (ctrl, pk); + + if (node->pkt->pkttype == PKT_PUBLIC_KEY) + es_fputs (have_seckey? "sec:" : "pub:", fp); + else + es_fputs (have_seckey? "ssb:" : "sub:", fp); + + if (!pk->flags.valid) + es_putc ('i', fp); + else if (pk->flags.revoked) + es_putc ('r', fp); + else if (pk->has_expired) + es_putc ('e', fp); + else if (!(opt.fast_list_mode || opt.no_expensive_trust_checks)) + { + int trust = get_validity_info (ctrl, keyblock, pk, NULL); + if (trust == 'u') + ulti_hack = 1; + es_putc (trust, fp); + } + + es_fprintf (fp, ":%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)) + es_putc (get_ownertrust_info (ctrl, pk, 0), fp); + es_putc (':', fp); + es_putc (':', fp); + es_putc (':', fp); + /* Print capabilities. */ + if ((pk->pubkey_usage & PUBKEY_USAGE_ENC)) + es_putc ('e', fp); + if ((pk->pubkey_usage & PUBKEY_USAGE_SIG)) + es_putc ('s', fp); + if ((pk->pubkey_usage & PUBKEY_USAGE_CERT)) + es_putc ('c', fp); + if ((pk->pubkey_usage & PUBKEY_USAGE_AUTH)) + es_putc ('a', fp); + es_putc ('\n', fp); + + print_fingerprint (ctrl, fp, pk, 0); + print_revokers (fp, 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) + es_fputs ("uat:", fp); + else + es_fputs ("uid:", fp); + + if (uid->flags.revoked) + es_fputs ("r::::::::", fp); + else if (uid->flags.expired) + es_fputs ("e::::::::", fp); + else if (opt.fast_list_mode || opt.no_expensive_trust_checks) + es_fputs ("::::::::", fp); + else + { + int uid_validity; + + if (primary && !ulti_hack) + uid_validity = get_validity_info (ctrl, keyblock, primary, uid); + else + uid_validity = 'u'; + es_fprintf (fp, "%c::::::::", uid_validity); + } + + if (uid->attrib_data) + es_fprintf (fp, "%u %lu", uid->numattribs, uid->attrib_len); + else + es_write_sanitized (fp, uid->name, uid->len, ":", NULL); + + es_putc (':', fp); + /* signature class */ + es_putc (':', fp); + /* capabilities */ + es_putc (':', fp); + /* preferences */ + if (pk_version > 3 || uid->selfsigversion > 3) + { + const prefitem_t *prefs = uid->prefs; + + for (j = 0; prefs && prefs[j].type; j++) + { + if (j) + es_putc (' ', fp); + es_fprintf (fp, + "%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) + es_fputs (",mdc", fp); + if (!uid->flags.ks_modify) + es_fputs (",no-ks-modify", fp); + } + es_putc (':', fp); + /* flags */ + es_fprintf (fp, "%d,", i); + if (uid->flags.primary) + es_putc ('p', fp); + if (uid->flags.revoked) + es_putc ('r', fp); + if (uid->flags.expired) + es_putc ('e', fp); + if ((node->flag & NODFLG_SELUID)) + es_putc ('s', fp); + if ((node->flag & NODFLG_MARK_A)) + es_putc ('m', fp); + es_putc (':', fp); + if (opt.trust_model == TM_TOFU || opt.trust_model == TM_TOFU_PGP) + { +#ifdef USE_TOFU + enum tofu_policy policy; + if (! tofu_get_policy (ctrl, primary, uid, &policy) + && policy != TOFU_POLICY_NONE) + es_fprintf (fp, "%s", tofu_policy_str (policy)); +#endif /*USE_TOFU*/ + } + es_putc (':', fp); + es_putc ('\n', fp); + } + } +} + + +static void +show_names (ctrl_t ctrl, estream_t fp, + kbnode_t 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_fprintf (fp, "%s ", uid_trust_string_fixed (ctrl, pk, uid)); + + if (flag & NODFLG_MARK_A) + tty_fprintf (fp, " "); + else if (node->flag & NODFLG_SELUID) + tty_fprintf (fp, "(%d)* ", i); + else if (uid->flags.primary) + tty_fprintf (fp, "(%d). ", i); + else + tty_fprintf (fp, "(%d) ", i); + tty_print_utf8_string2 (fp, uid->name, uid->len, 0); + tty_fprintf (fp, "\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_fprintf (fp, _("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 do not display the index + * number. If FP is not NULL print to the given stream and not to the + * tty (ignored in with-colons mode). + */ +static void +show_key_with_all_names (ctrl_t ctrl, estream_t fp, + KBNODE keyblock, int only_marked, int with_revoker, + int with_fpr, int with_subkeys, int with_prefs, + int nowarn) +{ + gpg_error_t err; + kbnode_t node; + int i; + int do_warn = 0; + int have_seckey = 0; + char *serialno = NULL; + PKT_public_key *primary = NULL; + char pkstrbuf[PUBKEY_STRING_SIZE]; + + if (opt.with_colons) + { + show_key_with_all_names_colon (ctrl, fp, 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"; + const char *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 (ctrl, pk, NULL); + otrust = get_ownertrust_string (ctrl, pk, 0); + + /* Show a warning once */ + if (!did_warn + && (get_validity (ctrl, keyblock, pk, NULL, NULL, 0) + & TRUST_FLAG_PENDING_CHECK)) + { + did_warn = 1; + do_warn = 1; + } + + primary = pk; + } + + if (pk->flags.revoked) + { + char *user = get_user_id_string_native (ctrl, pk->revoked.keyid); + tty_fprintf (fp, + _("The following key was revoked on" + " %s by %s key %s\n"), + revokestr_from_pk (pk), + gcry_pk_algo_name (pk->revoked.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; + + algo = gcry_pk_algo_name (pk->revkey[i].algid); + keyid_from_fingerprint (ctrl, pk->revkey[i].fpr, + MAX_FINGERPRINT_LEN, r_keyid); + + user = get_user_id_string_native (ctrl, r_keyid); + tty_fprintf (fp, + _("This key may be revoked by %s key %s"), + algo ? algo : "?", user); + + if (pk->revkey[i].class & 0x40) + { + tty_fprintf (fp, " "); + tty_fprintf (fp, _("(sensitive)")); + } + + tty_fprintf (fp, "\n"); + xfree (user); + } + } + + keyid_from_pk (pk, NULL); + + xfree (serialno); + serialno = NULL; + { + char *hexgrip; + + err = hexkeygrip_from_pk (pk, &hexgrip); + if (err) + { + log_error ("error computing a keygrip: %s\n", + gpg_strerror (err)); + have_seckey = 0; + } + else + have_seckey = !agent_get_keyinfo (ctrl, hexgrip, &serialno, NULL); + xfree (hexgrip); + } + + tty_fprintf + (fp, "%s%c %s/%s", + node->pkt->pkttype == PKT_PUBLIC_KEY && have_seckey? "sec" : + node->pkt->pkttype == PKT_PUBLIC_KEY ? "pub" : + have_seckey ? "ssb" : + "sub", + (node->flag & NODFLG_SELKEY) ? '*' : ' ', + pubkey_string (pk, pkstrbuf, sizeof pkstrbuf), + keystr (pk->keyid)); + + if (opt.legacy_list_mode) + tty_fprintf (fp, " "); + else + tty_fprintf (fp, "\n "); + + tty_fprintf (fp, _("created: %s"), datestr_from_pk (pk)); + tty_fprintf (fp, " "); + if (pk->flags.revoked) + tty_fprintf (fp, _("revoked: %s"), revokestr_from_pk (pk)); + else if (pk->has_expired) + tty_fprintf (fp, _("expired: %s"), expirestr_from_pk (pk)); + else + tty_fprintf (fp, _("expires: %s"), expirestr_from_pk (pk)); + tty_fprintf (fp, " "); + tty_fprintf (fp, _("usage: %s"), usagestr_from_pk (pk, 1)); + tty_fprintf (fp, "\n"); + + if (serialno) + { + /* The agent told us that a secret key is available and + that it has been stored on a card. */ + tty_fprintf (fp, "%*s%s", opt.legacy_list_mode? 21:5, "", + _("card-no: ")); + if (strlen (serialno) == 32 + && !strncmp (serialno, "D27600012401", 12)) + { + /* This is an OpenPGP card. Print the relevant part. */ + /* Example: D2760001240101010001000003470000 */ + /* xxxxyyyyyyyy */ + tty_fprintf (fp, "%.*s %.*s\n", + 4, serialno+16, 8, serialno+20); + } + else + tty_fprintf (fp, "%s\n", serialno); + + } + else if (pk->seckey_info + && pk->seckey_info->is_protected + && pk->seckey_info->s2k.mode == 1002) + { + /* FIXME: Check whether this code path is still used. */ + tty_fprintf (fp, "%*s%s", opt.legacy_list_mode? 21:5, "", + _("card-no: ")); + if (pk->seckey_info->ivlen == 16 + && !memcmp (pk->seckey_info->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", pk->seckey_info->iv[i]); + } + } + else + { + /* Unknown card: Print all. */ + for (i = 0; i < pk->seckey_info->ivlen; i++) + tty_fprintf (fp, "%02X", pk->seckey_info->iv[i]); + } + tty_fprintf (fp, "\n"); + } + + if (node->pkt->pkttype == PKT_PUBLIC_KEY + || node->pkt->pkttype == PKT_SECRET_KEY) + { + if (opt.trust_model != TM_ALWAYS) + { + tty_fprintf (fp, "%*s", + opt.legacy_list_mode? + ((int) keystrlen () + 13):5, ""); + /* Ownertrust is only meaningful for the PGP or + classic trust models, or PGP combined with TOFU */ + if (opt.trust_model == TM_PGP + || opt.trust_model == TM_CLASSIC + || opt.trust_model == TM_TOFU_PGP) + { + int width = 14 - strlen (otrust); + if (width <= 0) + width = 1; + tty_fprintf (fp, _("trust: %s"), otrust); + tty_fprintf (fp, "%*s", width, ""); + } + + tty_fprintf (fp, _("validity: %s"), trust); + tty_fprintf (fp, "\n"); + } + if (node->pkt->pkttype == PKT_PUBLIC_KEY + && (get_ownertrust (ctrl, pk) & TRUST_FLAG_DISABLED)) + { + tty_fprintf (fp, "*** "); + tty_fprintf (fp, _("This key has been disabled")); + tty_fprintf (fp, "\n"); + } + } + + if ((node->pkt->pkttype == PKT_PUBLIC_KEY + || node->pkt->pkttype == PKT_SECRET_KEY) && with_fpr) + { + print_fingerprint (ctrl, fp, pk, 2); + tty_fprintf (fp, "\n"); + } + } + } + + show_names (ctrl, fp, + keyblock, primary, only_marked ? NODFLG_MARK_A : 0, with_prefs); + + if (do_warn && !nowarn) + tty_fprintf (fp, _("Please note that the shown key validity" + " is not necessarily correct\n" + "unless you restart the program.\n")); + + xfree (serialno); +} + + +/* 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. This function may be called with KEYBLOCK containing + * secret keys and thus the printing of "pub" vs. "sec" does only + * depend on the packet type and not by checking with gpg-agent. If + * PRINT_SEC ist set "sec" is printed instead of "pub". */ +void +show_basic_key_info (ctrl_t ctrl, kbnode_t keyblock, int print_sec) +{ + KBNODE node; + int i; + char pkstrbuf[PUBKEY_STRING_SIZE]; + + /* The primary key */ + for (node = keyblock; node; node = node->next) + { + if (node->pkt->pkttype == PKT_PUBLIC_KEY + || node->pkt->pkttype == PKT_SECRET_KEY) + { + PKT_public_key *pk = node->pkt->pkt.public_key; + const char *tag; + + if (node->pkt->pkttype == PKT_SECRET_KEY || print_sec) + tag = "sec"; + else + tag = "pub"; + + /* Note, we use the same format string as in other show + functions to make the translation job easier. */ + tty_printf ("%s %s/%s ", + tag, + pubkey_string (pk, pkstrbuf, sizeof pkstrbuf), + 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 (ctrl, NULL, pk, 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->flags.revoked) + tty_printf ("[%s] ", _("revoked")); + else if (uid->flags.expired) + tty_printf ("[%s] ", _("expired")); + tty_print_utf8_string (uid->name, uid->len); + tty_printf ("\n"); + } + } +} + + +static void +show_key_and_fingerprint (ctrl_t ctrl, kbnode_t keyblock, int with_subkeys) +{ + kbnode_t node; + PKT_public_key *pk = NULL; + char pkstrbuf[PUBKEY_STRING_SIZE]; + + for (node = keyblock; node; node = node->next) + { + if (node->pkt->pkttype == PKT_PUBLIC_KEY) + { + pk = node->pkt->pkt.public_key; + tty_printf ("pub %s/%s %s ", + pubkey_string (pk, pkstrbuf, sizeof pkstrbuf), + 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 (ctrl, NULL, pk, 2); + if (with_subkeys) + { + for (node = keyblock; node; node = node->next) + { + if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY) + { + pk = node->pkt->pkt.public_key; + tty_printf ("sub %s/%s %s [%s]\n", + pubkey_string (pk, pkstrbuf, sizeof pkstrbuf), + keystr_from_pk(pk), + datestr_from_pk (pk), + usagestr_from_pk (pk, 0)); + + print_fingerprint (ctrl, NULL, pk, 4); + } + } + } +} + + +/* Show a listing of the primary and its subkeys along with their + keygrips. */ +static void +show_key_and_grip (kbnode_t keyblock) +{ + kbnode_t node; + PKT_public_key *pk = NULL; + char pkstrbuf[PUBKEY_STRING_SIZE]; + char *hexgrip; + + for (node = keyblock; node; node = node->next) + { + if (node->pkt->pkttype == PKT_PUBLIC_KEY + || node->pkt->pkttype == PKT_PUBLIC_SUBKEY) + { + pk = node->pkt->pkt.public_key; + tty_printf ("%s %s/%s %s [%s]\n", + node->pkt->pkttype == PKT_PUBLIC_KEY? "pub":"sub", + pubkey_string (pk, pkstrbuf, sizeof pkstrbuf), + keystr_from_pk(pk), + datestr_from_pk (pk), + usagestr_from_pk (pk, 0)); + + if (!hexkeygrip_from_pk (pk, &hexgrip)) + { + tty_printf (" Keygrip: %s\n", hexgrip); + xfree (hexgrip); + } + } + } +} + + +/* 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->flags.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")); +} + + +/* Print a warning if the latest encryption subkey expires soon. This + function is called after the expire data of the primary key has + been changed. */ +static void +subkey_expire_warning (kbnode_t keyblock) +{ + u32 curtime = make_timestamp (); + kbnode_t node; + PKT_public_key *pk; + /* u32 mainexpire = 0; */ + u32 subexpire = 0; + u32 latest_date = 0; + + for (node = keyblock; node; node = node->next) + { + /* if (node->pkt->pkttype == PKT_PUBLIC_KEY) */ + /* { */ + /* pk = node->pkt->pkt.public_key; */ + /* mainexpire = pk->expiredate; */ + /* } */ + + if (node->pkt->pkttype != PKT_PUBLIC_SUBKEY) + continue; + pk = node->pkt->pkt.public_key; + + if (!pk->flags.valid) + continue; + if (pk->flags.revoked) + continue; + if (pk->timestamp > curtime) + continue; /* Ignore future keys. */ + if (!(pk->pubkey_usage & PUBKEY_USAGE_ENC)) + continue; /* Not an encryption key. */ + + if (pk->timestamp > latest_date || (!pk->timestamp && !latest_date)) + { + latest_date = pk->timestamp; + subexpire = pk->expiredate; + } + } + + if (!subexpire) + return; /* No valid subkey with an expiration time. */ + + if (curtime + (10*86400) > subexpire) + { + log_info (_("WARNING: Your encryption subkey expires soon.\n")); + log_info (_("You may want to change its expiration date too.\n")); + } +} + + +/* + * Ask for a new user id, add the self-signature, and update the + * keyblock. If UIDSTRING is not NULL the user ID is generated + * unattended using that string. UIDSTRING is expected to be utf-8 + * encoded and white space trimmed. Returns true if there is a new + * user id. + */ +static int +menu_adduid (ctrl_t ctrl, kbnode_t pub_keyblock, + int photo, const char *photo_name, const char *uidstring) +{ + PKT_user_id *uid; + PKT_public_key *pk = NULL; + PKT_signature *sig = NULL; + PACKET *pkt; + KBNODE node; + KBNODE pub_where = NULL; + gpg_error_t err; + + if (photo && uidstring) + return 0; /* Not allowed. */ + + 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; + log_assert (pk); + + 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 (ctrl, pk, photo_name); + } + else + uid = generate_user_id (pub_keyblock, uidstring); + if (!uid) + { + if (uidstring) + { + write_status_error ("adduid", gpg_error (304)); + log_error ("%s", _("Such a user ID already exists on this key!\n")); + } + return 0; + } + + err = make_keysig_packet (ctrl, &sig, pk, uid, NULL, pk, 0x13, 0, 0, 0, + keygen_add_std_prefs, pk, NULL); + if (err) + { + write_status_error ("keysig", err); + log_error ("signing failed: %s\n", gpg_strerror (err)); + free_user_id (uid); + return 0; + } + + /* 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 = 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 keyring + */ +static void +menu_deluid (KBNODE pub_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->flags.revoked) + update_trust = 1; + delete_kbnode (node); + } + } + 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); +} + + +static int +menu_delsig (ctrl_t ctrl, kbnode_t 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 (ctrl, pub_keyblock, node, + &inv_sig, &no_key, + &other_err, &selfsig, 1); + else + valid = print_and_check_one_sig (ctrl, pub_keyblock, node, + &inv_sig, &no_key, &other_err, + &selfsig, 1, 0); + + 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 (ngettext("Deleted %d signature.\n", + "Deleted %d signatures.\n", changed), changed); + } + else + tty_printf (_("Nothing deleted.\n")); + + return changed; +} + + +static int +menu_clean (ctrl_t ctrl, kbnode_t 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 (ctrl, keyblock, uidnode, opt.verbose, self_only, &uids, + &sigs); + if (uids) + { + const char *reason; + + if (uidnode->pkt->pkt.user_id->flags.revoked) + reason = _("revoked"); + else if (uidnode->pkt->pkt.user_id->flags.expired) + reason = _("expired"); + else + reason = _("invalid"); + + tty_printf (_("User ID \"%s\" compacted: %s\n"), user, reason); + + modified = 1; + } + else if (sigs) + { + tty_printf (ngettext("User ID \"%s\": %d signature removed\n", + "User ID \"%s\": %d signatures removed\n", + sigs), 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 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); + } + else if (selected && node->pkt->pkttype == PKT_SIGNATURE) + delete_kbnode (node); + else + selected = 0; + } + commit_kbnode (&pub_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, create the self-signature and put it into + * the keyblock. Returns true if there is a new revoker. + */ +static int +menu_addrevoker (ctrl_t ctrl, kbnode_t pub_keyblock, int sensitive) +{ + PKT_public_key *pk = NULL; + PKT_public_key *revoker_pk = NULL; + PKT_signature *sig = NULL; + PACKET *pkt; + struct revocation_key revkey; + size_t fprlen; + int rc; + + log_assert (pub_keyblock->pkt->pkttype == PKT_PUBLIC_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; + } + } + + for (;;) + { + char *answer; + + 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] == CONTROL_D) + { + 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 revocation from a subkey. */ + revoker_pk->req_usage = PUBKEY_USAGE_CERT; + rc = get_pubkey_byname (ctrl, GET_PUBKEY_NO_AKL, + NULL, revoker_pk, answer, NULL, NULL, 1); + if (rc) + { + log_error (_("key \"%s\" not found: %s\n"), answer, + gpg_strerror (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")); + + format_keyid (pk_keyid (pk), KF_LONG, buf, sizeof (buf)); + write_status_text (STATUS_ALREADY_SIGNED, buf); + + break; + } + } + + if (i < pk->numrevkeys) + continue; + } + + print_pubkey_info (ctrl, NULL, revoker_pk); + print_fingerprint (ctrl, NULL, revoker_pk, 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; + } + + rc = make_keysig_packet (ctrl, &sig, pk, NULL, NULL, pk, 0x1F, 0, 0, 0, + keygen_add_revkey, &revkey, NULL); + if (rc) + { + write_status_error ("keysig", rc); + log_error ("signing failed: %s\n", gpg_strerror (rc)); + goto fail; + } + + /* 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 (sig) + free_seckey_enc (sig); + free_public_key (revoker_pk); + + return 0; +} + + +/* With FORCE_MAINKEY cleared this function handles the interactive + * menu option "expire". With UNATTENDED set to 1 this function only + * sets the expiration date of the primary key to NEWEXPIRATION and + * avoid all interactivity; with a value of 2 only the flagged subkeys + * are set to NEWEXPIRATION. Returns 0 if nothing was done, + * GPG_ERR_TRUE if the key was modified, or any other error code. */ +static gpg_error_t +menu_expire (ctrl_t ctrl, kbnode_t pub_keyblock, + int unattended, u32 newexpiration) +{ + int signumber, rc; + u32 expiredate; + int only_mainkey; /* Set if only the mainkey is to be updated. */ + PKT_public_key *main_pk, *sub_pk; + PKT_user_id *uid; + kbnode_t node; + u32 keyid[2]; + + if (unattended) + { + only_mainkey = (unattended == 1); + expiredate = newexpiration; + } + else + { + int n1; + + only_mainkey = 0; + n1 = count_selected_keys (pub_keyblock); + if (n1 > 1) + { + if (!cpr_get_answer_is_yes + ("keyedit.expire_multiple_subkeys.okay", + _("Are you sure you want to change the" + " expiration time for multiple subkeys? (y/N) "))) + return gpg_error (GPG_ERR_CANCELED);; + } + else if (n1) + tty_printf (_("Changing expiration time for a subkey.\n")); + else + { + tty_printf (_("Changing expiration time for the primary key.\n")); + only_mainkey = 1; + no_primary_warning (pub_keyblock); + } + + expiredate = ask_expiredate (); + } + + + /* 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) + { + if ((node->flag & NODFLG_SELKEY) && unattended != 1) + { + /* The flag is set and we do not want to set the + * expiration date only for the main key. */ + sub_pk = node->pkt->pkt.public_key; + sub_pk->expiredate = expiredate; + } + else + sub_pk = NULL; + } + else if (node->pkt->pkttype == PKT_USER_ID) + uid = node->pkt->pkt.user_id; + else if (main_pk && node->pkt->pkttype == PKT_SIGNATURE + && (only_mainkey || sub_pk)) + { + PKT_signature *sig = node->pkt->pkt.signature; + + if (keyid[0] == sig->keyid[0] && keyid[1] == sig->keyid[1] + && ((only_mainkey && uid + && uid->created && (sig->sig_class & ~3) == 0x10) + || (!only_mainkey && sig->sig_class == 0x18)) + && sig->flags.chosen_selfsig) + { + /* This is a self-signature which is to be replaced. */ + PKT_signature *newsig; + PACKET *newpkt; + + signumber++; + + if ((only_mainkey && main_pk->version < 4) + || (!only_mainkey && sub_pk->version < 4)) + { + log_info + (_("You can't change the expiration date of a v3 key\n")); + return gpg_error (GPG_ERR_LEGACY_KEY); + } + + if (only_mainkey) + rc = update_keysig_packet (ctrl, + &newsig, sig, main_pk, uid, NULL, + main_pk, keygen_add_key_expire, + main_pk); + else + rc = + update_keysig_packet (ctrl, + &newsig, sig, main_pk, NULL, sub_pk, + main_pk, keygen_add_key_expire, sub_pk); + if (rc) + { + log_error ("make_keysig_packet failed: %s\n", + gpg_strerror (rc)); + if (gpg_err_code (rc) == GPG_ERR_TRUE) + rc = GPG_ERR_GENERAL; + return rc; + } + + /* Replace the packet. */ + newpkt = xmalloc_clear (sizeof *newpkt); + newpkt->pkttype = PKT_SIGNATURE; + newpkt->pkt.signature = newsig; + free_packet (node->pkt, NULL); + xfree (node->pkt); + node->pkt = newpkt; + sub_pk = NULL; + } + } + } + + update_trust = 1; + return gpg_error (GPG_ERR_TRUE); +} + + +/* Change the capability of a selected key. This command should only + * be used to rectify badly created keys and as such is not suggested + * for general use. */ +static int +menu_changeusage (ctrl_t ctrl, kbnode_t keyblock) +{ + int n1, rc; + int mainkey = 0; + PKT_public_key *main_pk, *sub_pk; + PKT_user_id *uid; + kbnode_t node; + u32 keyid[2]; + + n1 = count_selected_keys (keyblock); + if (n1 > 1) + { + tty_printf (_("You must select exactly one key.\n")); + return 0; + } + else if (n1) + tty_printf (_("Changing usage of a subkey.\n")); + else + { + tty_printf (_("Changing usage of the primary key.\n")); + mainkey = 1; + } + + /* Now we can actually change the self-signature(s) */ + main_pk = sub_pk = NULL; + uid = NULL; + for (node = 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); + } + else if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY) + { + if (node->flag & NODFLG_SELKEY) + sub_pk = node->pkt->pkt.public_key; + else + sub_pk = NULL; + } + 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 the self-signature which is to be replaced. */ + PKT_signature *newsig; + PACKET *newpkt; + + if ((mainkey && main_pk->version < 4) + || (!mainkey && sub_pk->version < 4)) + { + /* Note: This won't happen because we don't support + * v3 keys anymore. */ + log_info ("You can't change the capabilities of a v3 key\n"); + return 0; + } + + if (mainkey) + main_pk->pubkey_usage = ask_key_flags (main_pk->pubkey_algo, 0, + main_pk->pubkey_usage); + else + sub_pk->pubkey_usage = ask_key_flags (sub_pk->pubkey_algo, 1, + sub_pk->pubkey_usage); + + if (mainkey) + rc = update_keysig_packet (ctrl, + &newsig, sig, main_pk, uid, NULL, + main_pk, keygen_add_key_flags, + main_pk); + else + rc = + update_keysig_packet (ctrl, + &newsig, sig, main_pk, NULL, sub_pk, + main_pk, keygen_add_key_flags, sub_pk); + if (rc) + { + log_error ("make_keysig_packet failed: %s\n", + gpg_strerror (rc)); + return 0; + } + + /* Replace the packet. */ + newpkt = xmalloc_clear (sizeof *newpkt); + newpkt->pkttype = PKT_SIGNATURE; + newpkt->pkt.signature = newsig; + free_packet (node->pkt, NULL); + xfree (node->pkt); + node->pkt = newpkt; + sub_pk = NULL; + break; + } + } + } + + return 1; +} + + +static int +menu_backsign (ctrl_t ctrl, kbnode_t pub_keyblock) +{ + int rc, modified = 0; + PKT_public_key *main_pk; + KBNODE node; + u32 timestamp; + + log_assert (pub_keyblock->pkt->pkttype == PKT_PUBLIC_KEY); + + merge_keys_and_selfsig (ctrl, pub_keyblock); + main_pk = pub_keyblock->pkt->pkt.public_key; + keyid_from_pk (main_pk, NULL); + + /* We use the same timestamp for all backsigs so that we don't + reveal information about the used machine. */ + timestamp = make_timestamp (); + + for (node = pub_keyblock; node; node = node->next) + { + PKT_public_key *sub_pk = NULL; + KBNODE node2, sig_pk = NULL /*,sig_sk = NULL*/; + /* char *passphrase; */ + + /* 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->flags.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 */ + log_debug ("FIXME: Check whether a secret subkey is available.\n"); + /* if (!sub_sk) */ + /* { */ + /* tty_printf (_("no secret subkey for public subkey %s - ignoring\n"), */ + /* keystr_from_pk (sub_pk)); */ + /* continue; */ + /* } */ + + + /* Now we can get to work. */ + + rc = make_backsig (ctrl, + sig_pk->pkt->pkt.signature, main_pk, sub_pk, sub_pk, + timestamp, NULL); + if (!rc) + { + PKT_signature *newsig; + PACKET *newpkt; + + rc = update_keysig_packet (ctrl, + &newsig, sig_pk->pkt->pkt.signature, + main_pk, NULL, sub_pk, main_pk, + NULL, NULL); + if (!rc) + { + /* 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, NULL); + xfree (sig_pk->pkt); + sig_pk->pkt = newpkt; + + modified = 1; + } + else + { + log_error ("update_keysig_packet failed: %s\n", + gpg_strerror (rc)); + break; + } + } + else + { + log_error ("make_backsig failed: %s\n", gpg_strerror (rc)); + break; + } + } + + 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 we 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 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 (ctrl_t ctrl, kbnode_t pub_keyblock) +{ + 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; + } + + 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; /* No more user ids expected - 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 (ctrl, &newsig, sig, + main_pk, uid, NULL, + main_pk, + change_primary_uid_cb, + action > 0 ? "x" : NULL); + if (rc) + { + log_error ("update_keysig_packet failed: %s\n", + gpg_strerror (rc)); + return 0; + } + /* replace the packet */ + newpkt = xmalloc_clear (sizeof *newpkt); + newpkt->pkttype = PKT_SIGNATURE; + newpkt->pkt.signature = newsig; + free_packet (node->pkt, NULL); + xfree (node->pkt); + node->pkt = newpkt; + modified = 1; + } + } + } + } + } + + return modified; +} + + +/* + * Set preferences to new values for the selected user IDs + */ +static int +menu_set_preferences (ctrl_t ctrl, kbnode_t pub_keyblock) +{ + 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); + + /* 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; /* No more user-ids expected - 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 (ctrl, &newsig, sig, + main_pk, uid, NULL, main_pk, + keygen_upd_std_prefs, NULL); + if (rc) + { + log_error ("update_keysig_packet failed: %s\n", + gpg_strerror (rc)); + return 0; + } + /* replace the packet */ + newpkt = xmalloc_clear (sizeof *newpkt); + newpkt->pkttype = PKT_SIGNATURE; + newpkt->pkt.signature = newsig; + free_packet (node->pkt, NULL); + xfree (node->pkt); + node->pkt = newpkt; + modified = 1; + } + } + } + } + + return modified; +} + + +static int +menu_set_keyserver_url (ctrl_t ctrl, const char *url, kbnode_t pub_keyblock) +{ + 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] == CONTROL_D) + { + 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); + 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); + + /* 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 (ctrl, &newsig, sig, + main_pk, uid, NULL, + main_pk, + keygen_add_keyserver_url, uri); + if (rc) + { + log_error ("update_keysig_packet failed: %s\n", + gpg_strerror (rc)); + xfree (uri); + return 0; + } + /* replace the packet */ + newpkt = xmalloc_clear (sizeof *newpkt); + newpkt->pkttype = PKT_SIGNATURE; + newpkt->pkt.signature = newsig; + free_packet (node->pkt, NULL); + xfree (node->pkt); + node->pkt = newpkt; + modified = 1; + } + + xfree (user); + } + } + } + + xfree (uri); + return modified; +} + + +static int +menu_set_notation (ctrl_t ctrl, const char *string, KBNODE pub_keyblock) +{ + 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] == CONTROL_D) + { + xfree (answer); + return 0; + } + } + + if (!ascii_strcasecmp (answer, "none") + || !ascii_strcasecmp (answer, "-")) + 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); + + /* 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 (ctrl, &newsig, sig, + main_pk, uid, NULL, + main_pk, + keygen_add_notations, notation); + if (rc) + { + log_error ("update_keysig_packet failed: %s\n", + gpg_strerror (rc)); + 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, NULL); + 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); + return modified; +} + + +/* + * Select one user id or remove all selection if IDX is 0 or select + * all if IDX is -1. Returns: True if the selection changed. + */ +static int +menu_select_uid (KBNODE keyblock, int idx) +{ + KBNODE node; + int i; + + if (idx == -1) /* Select all. */ + { + for (node = keyblock; node; node = node->next) + if (node->pkt->pkttype == PKT_USER_ID) + node->flag |= NODFLG_SELUID; + return 1; + } + else if (idx) /* Toggle. */ + { + 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; + } + + 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; + } + } + } + } + else /* Unselect all */ + { + for (node = keyblock; node; node = node->next) + if (node->pkt->pkttype == PKT_USER_ID) + 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; + + log_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, char *p) +{ + KBNODE node; + int i, j; + int is_hex_digits; + + is_hex_digits = p && strlen (p) >= 8; + if (is_hex_digits) + { + /* Skip initial spaces. */ + while (spacep (p)) + p ++; + /* If the id starts with 0x accept and ignore it. */ + if (p[0] == '0' && p[1] == 'x') + p += 2; + + for (i = 0, j = 0; p[i]; i ++) + if (hexdigitp (&p[i])) + { + p[j] = toupper (p[i]); + j ++; + } + else if (spacep (&p[i])) + /* Skip spaces. */ + { + } + else + { + is_hex_digits = 0; + break; + } + if (is_hex_digits) + /* In case we skipped some spaces, add a new NUL terminator. */ + { + p[j] = 0; + /* If we skipped some spaces, make sure that we still have + at least 8 characters. */ + is_hex_digits = (/* Short keyid. */ + strlen (p) == 8 + /* Long keyid. */ + || strlen (p) == 16 + /* Fingerprints are (currently) 32 or 40 + characters. */ + || strlen (p) >= 32); + } + } + + if (is_hex_digits) + { + int found_one = 0; + for (node = keyblock; node; node = node->next) + if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY + || node->pkt->pkttype == PKT_SECRET_SUBKEY) + { + int match = 0; + if (strlen (p) == 8 || strlen (p) == 16) + { + u32 kid[2]; + char kid_str[17]; + keyid_from_pk (node->pkt->pkt.public_key, kid); + format_keyid (kid, strlen (p) == 8 ? KF_SHORT : KF_LONG, + kid_str, sizeof (kid_str)); + + if (strcmp (p, kid_str) == 0) + match = 1; + } + else + { + char fp[2*MAX_FINGERPRINT_LEN + 1]; + hexfingerprint (node->pkt->pkt.public_key, fp, sizeof (fp)); + if (strcmp (fp, p) == 0) + match = 1; + } + + if (match) + { + if ((node->flag & NODFLG_SELKEY)) + node->flag &= ~NODFLG_SELKEY; + else + node->flag |= NODFLG_SELKEY; + + found_one = 1; + } + } + + if (found_one) + return 1; + + tty_printf (_("No subkey with key ID '%s'.\n"), p); + return 0; + } + + if (idx == -1) /* Select all. */ + { + for (node = keyblock; node; node = node->next) + if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY + || node->pkt->pkttype == PKT_SECRET_SUBKEY) + node->flag |= NODFLG_SELKEY; + } + else if (idx) /* Toggle selection. */ + { + 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; + } + + 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; + } + } + } + else /* Unselect all. */ + { + for (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; +} + + +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 (ctrl_t ctrl, kbnode_t keyblock, kbnode_t 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 + { + es_printf ("uid:::::::::"); + es_write_sanitized (es_stdout, uid->name, uid->len, ":", NULL); + } + + es_printf ("\n"); + + print_and_check_one_sig_colon (ctrl, 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 (ctrl_t ctrl, kbnode_t 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; + + log_assert (keyblock->pkt->pkttype == PKT_PUBLIC_KEY); + + /* First check whether we have any signatures at all. */ + any = 0; + 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) + skip = 0; + else + skip = 1; + } + else if (!skip && node->pkt->pkttype == PKT_SIGNATURE + && ((sig = node->pkt->pkt.signature), + have_secret_key_with_kid (sig->keyid))) + { + if ((sig->sig_class & ~3) == 0x10) + { + any = 1; + break; + } + } + } + + if (!any) + { + tty_printf (_("Not signed by you.\n")); + return 0; + } + + + /* 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), + have_secret_key_with_kid (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 (ctrl, 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 modifying 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_public_key *signerkey; + + if (!(node->flag & NODFLG_MARK_A) + || node->pkt->pkttype != PKT_SIGNATURE) + continue; + unode = find_prev_kbnode (keyblock, node, PKT_USER_ID); + log_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; + signerkey = xmalloc_secure_clear (sizeof *signerkey); + if (get_seckey (ctrl, signerkey, node->pkt->pkt.signature->keyid)) + { + log_info (_("no secret key\n")); + free_public_key (signerkey); + continue; + } + rc = make_keysig_packet (ctrl, &sig, primary_pk, + unode->pkt->pkt.user_id, + NULL, signerkey, 0x30, 0, 0, 0, + sign_mk_attrib, &attrib, NULL); + free_public_key (signerkey); + if (rc) + { + write_status_error ("keysig", rc); + log_error (_("signing failed: %s\n"), gpg_strerror (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->flags.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; +} + + +/* return 0 if revocation of NODE (which must be a User ID) was + successful, non-zero if there was an error. *modified will be set + to 1 if a change was made. */ +static int +core_revuid (ctrl_t ctrl, kbnode_t keyblock, KBNODE node, + const struct revocation_reason_info *reason, int *modified) +{ + PKT_public_key *pk = keyblock->pkt->pkt.public_key; + gpg_error_t rc; + + if (node->pkt->pkttype != PKT_USER_ID) + { + rc = gpg_error (GPG_ERR_NO_USER_ID); + write_status_error ("keysig", rc); + log_error (_("tried to revoke a non-user ID: %s\n"), gpg_strerror (rc)); + return 1; + } + else + { + PKT_user_id *uid = node->pkt->pkt.user_id; + + if (uid->flags.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); + /* should not need to cast away const here; but + revocation_reason_build_cb needs to take a non-const + void* in order to meet the function signature for the + mksubpkt argument to make_keysig_packet */ + attrib.reason = (struct revocation_reason_info *)reason; + + rc = make_keysig_packet (ctrl, &sig, pk, uid, NULL, pk, 0x30, 0, + timestamp, 0, + sign_mk_attrib, &attrib, NULL); + if (rc) + { + write_status_error ("keysig", rc); + log_error (_("signing failed: %s\n"), gpg_strerror (rc)); + return 1; + } + else + { + pkt = xmalloc_clear (sizeof *pkt); + pkt->pkttype = PKT_SIGNATURE; + pkt->pkt.signature = sig; + insert_kbnode (node, new_kbnode (pkt), 0); + +#ifndef NO_TRUST_MODELS + /* If the trustdb has an entry for this key+uid then the + trustdb needs an update. */ + if (!update_trust + && ((get_validity (ctrl, keyblock, pk, uid, NULL, 0) + & TRUST_MASK) + >= TRUST_UNDEFINED)) + update_trust = 1; +#endif /*!NO_TRUST_MODELS*/ + + node->pkt->pkt.user_id->flags.revoked = 1; + if (modified) + *modified = 1; + } + } + return 0; + } +} + +/* Revoke a user ID (i.e. revoke a user ID selfsig). Return true if + keyblock changed. */ +static int +menu_revuid (ctrl_t ctrl, kbnode_t pub_keyblock) +{ + PKT_public_key *pk = pub_keyblock->pkt->pkt.public_key; + KBNODE node; + int changed = 0; + int rc; + struct revocation_reason_info *reason = NULL; + size_t valid_uids; + + /* 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; + } + + /* Too make sure that we do not revoke the last valid UID, we first + count how many valid UIDs there are. */ + valid_uids = 0; + for (node = pub_keyblock; node; node = node->next) + valid_uids += + node->pkt->pkttype == PKT_USER_ID + && ! node->pkt->pkt.user_id->flags.revoked + && ! node->pkt->pkt.user_id->flags.expired; + + reloop: /* (better this way because we are modifying the keyring) */ + for (node = pub_keyblock; node; node = node->next) + if (node->pkt->pkttype == PKT_USER_ID && (node->flag & NODFLG_SELUID)) + { + int modified = 0; + + /* Make sure that we do not revoke the last valid UID. */ + if (valid_uids == 1 + && ! node->pkt->pkt.user_id->flags.revoked + && ! node->pkt->pkt.user_id->flags.expired) + { + log_error (_("Cannot revoke the last valid user ID.\n")); + goto leave; + } + + rc = core_revuid (ctrl, pub_keyblock, node, reason, &modified); + if (rc) + goto leave; + if (modified) + { + node->flag &= ~NODFLG_SELUID; + changed = 1; + goto reloop; + } + } + + if (changed) + commit_kbnode (&pub_keyblock); + +leave: + release_revocation_reason_info (reason); + return changed; +} + + +/* + * Revoke the whole key. + */ +static int +menu_revkey (ctrl_t ctrl, kbnode_t pub_keyblock) +{ + PKT_public_key *pk = pub_keyblock->pkt->pkt.public_key; + int rc, changed = 0; + struct revocation_reason_info *reason; + PACKET *pkt; + PKT_signature *sig; + + if (pk->flags.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; + + rc = make_keysig_packet (ctrl, &sig, pk, NULL, NULL, pk, + 0x20, 0, 0, 0, + revocation_reason_build_cb, reason, NULL); + if (rc) + { + write_status_error ("keysig", rc); + log_error (_("signing failed: %s\n"), gpg_strerror (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 (ctrl_t ctrl, kbnode_t pub_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) + return 0; /* User decided to cancel. */ + + reloop: /* (better this way because we are modifying 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_public_key *subpk = node->pkt->pkt.public_key; + struct sign_attrib attrib; + + if (subpk->flags.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; + rc = make_keysig_packet (ctrl, &sig, mainpk, NULL, subpk, mainpk, + 0x28, 0, 0, 0, sign_mk_attrib, &attrib, + NULL); + if (rc) + { + write_status_error ("keysig", rc); + log_error (_("signing failed: %s\n"), gpg_strerror (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); + + /* 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 */ +#ifndef NO_TRUST_MODELS +static int +enable_disable_key (ctrl_t ctrl, kbnode_t 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 (ctrl, pk); + newtrust &= ~TRUST_FLAG_DISABLED; + if (disable) + newtrust |= TRUST_FLAG_DISABLED; + if (trust == newtrust) + return 0; /* already in that state */ + update_ownertrust (ctrl, pk, newtrust); + return 0; +} +#endif /*!NO_TRUST_MODELS*/ + + +static void +menu_showphoto (ctrl_t ctrl, kbnode_t 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 (ctrl, &uid->attribs[i], 1, pk, uid); + } + } + } + } + } +} diff --git a/g10/keyedit.h b/g10/keyedit.h new file mode 100644 index 0000000..1aa95c1 --- /dev/null +++ b/g10/keyedit.h @@ -0,0 +1,64 @@ +/* keyedit.h - Edit properties of a key + * Copyright (C) 1998-2010 Free Software Foundation, Inc. + * Copyright (C) 1998-2017 Werner Koch + * Copyright (C) 2015-2017 g10 Code GmbH + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#ifndef GNUPG_G10_KEYEDIT_H +#define GNUPG_G10_KEYEDIT_H + +#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. */ + +#define NODFLG_MARK_B (1<<11) /* Temporary mark in key listing code. */ + +/*-- keyedit.c --*/ +void keyedit_menu (ctrl_t ctrl, const char *username, strlist_t locusr, + strlist_t commands, int quiet, int seckey_check ); +void keyedit_passwd (ctrl_t ctrl, const char *username); +void keyedit_quick_adduid (ctrl_t ctrl, const char *username, + const char *newuid); +void keyedit_quick_addkey (ctrl_t ctrl, const char *fpr, const char *algostr, + const char *usagestr, const char *expirestr); +void keyedit_quick_revuid (ctrl_t ctrl, const char *username, + const char *uidtorev); +void keyedit_quick_sign (ctrl_t ctrl, const char *fpr, + strlist_t uids, strlist_t locusr, int local); +void keyedit_quick_revsig (ctrl_t ctrl, const char *username, + const char *sigtorev, strlist_t affected_uids); +void keyedit_quick_set_expire (ctrl_t ctrl, + const char *fpr, const char *expirestr, + char **subkeyfprs); +void keyedit_quick_set_primary (ctrl_t ctrl, const char *username, + const char *primaryuid); +void show_basic_key_info (ctrl_t ctrl, kbnode_t keyblock, int print_sec); +int keyedit_print_one_sig (ctrl_t ctrl, estream_t fp, + int rc, kbnode_t keyblock, + kbnode_t node, int *inv_sigs, int *no_key, + int *oth_err, int is_selfsig, + int print_without_key, int extended); + +#endif /* GNUPG_G10_KEYEDIT_H */ diff --git a/g10/keygen.c b/g10/keygen.c new file mode 100644 index 0000000..80d65c4 --- /dev/null +++ b/g10/keygen.c @@ -0,0 +1,5766 @@ +/* keygen.c - Generate a key pair + * Copyright (C) 1998-2007, 2009-2011 Free Software Foundation, Inc. + * Copyright (C) 2014, 2015, 2016 Werner Koch + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gpg.h" +#include "../common/util.h" +#include "main.h" +#include "packet.h" +#include "../common/ttyio.h" +#include "options.h" +#include "keydb.h" +#include "trustdb.h" +#include "../common/status.h" +#include "../common/i18n.h" +#include "keyserver-internal.h" +#include "call-agent.h" +#include "pkglue.h" +#include "../common/shareddefs.h" +#include "../common/host2net.h" +#include "../common/mbox-util.h" + + +/* The default algorithms. You should also check that the value + is inside the bounds enforced by ask_keysize and gen_xxx. See also + get_keysize_range which encodes the allowed ranges. */ +#define DEFAULT_STD_KEY_PARAM "rsa3072/cert,sign+rsa3072/encr" +#define FUTURE_STD_KEY_PARAM "ed25519/cert,sign+cv25519/encr" + +/* When generating keys using the streamlined key generation dialog, + use this as a default expiration interval. */ +const char *default_expiration_interval = "2y"; + +/* Flag bits used during key generation. */ +#define KEYGEN_FLAG_NO_PROTECTION 1 +#define KEYGEN_FLAG_TRANSIENT_KEY 2 + +/* Maximum number of supported algorithm preferences. */ +#define MAX_PREFS 30 + +enum para_name { + pKEYTYPE, + pKEYLENGTH, + pKEYCURVE, + pKEYUSAGE, + pSUBKEYTYPE, + pSUBKEYLENGTH, + pSUBKEYCURVE, + pSUBKEYUSAGE, + pAUTHKEYTYPE, + pNAMEREAL, + pNAMEEMAIL, + pNAMECOMMENT, + pPREFERENCES, + pREVOKER, + pUSERID, + pCREATIONDATE, + pKEYCREATIONDATE, /* Same in seconds since epoch. */ + pEXPIREDATE, + pKEYEXPIRE, /* in n seconds */ + pSUBKEYEXPIRE, /* in n seconds */ + pPASSPHRASE, + pSERIALNO, + pCARDBACKUPKEY, + pHANDLE, + pKEYSERVER, + pKEYGRIP, + pSUBKEYGRIP, +}; + +struct para_data_s { + struct para_data_s *next; + int lnr; + enum para_name key; + union { + u32 expire; + u32 creation; + unsigned int usage; + struct revocation_key revkey; + char value[1]; + } u; +}; + +struct output_control_s +{ + int lnr; + int dryrun; + unsigned int keygen_flags; + int use_files; + struct { + char *fname; + char *newfname; + IOBUF stream; + armor_filter_context_t *afx; + } pub; +}; + + +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 gpg_error_t parse_algo_usage_expire (ctrl_t ctrl, int for_subkey, + const char *algostr, const char *usagestr, + const char *expirestr, + int *r_algo, unsigned int *r_usage, + u32 *r_expire, unsigned int *r_nbits, + const char **r_curve, + char **r_keygrip); +static void do_generate_keypair (ctrl_t ctrl, struct para_data_s *para, + struct output_control_s *outctrl, int card ); +static int write_keyblock (iobuf_t out, kbnode_t node); +static gpg_error_t gen_card_key (int keyno, int algo, int is_primary, + kbnode_t pub_root, u32 *timestamp, + u32 expireval); +static unsigned int get_keysize_range (int algo, + unsigned int *min, unsigned int *max); + + + +/* Return the algo string for a default new key. */ +const char * +get_default_pubkey_algo (void) +{ + if (opt.def_new_key_algo) + { + if (*opt.def_new_key_algo && !strchr (opt.def_new_key_algo, ':')) + return opt.def_new_key_algo; + /* To avoid checking that option every time we delay that until + * here. The only thing we really need to make sure is that + * there is no colon in the string so that the --gpgconf-list + * command won't mess up its output. */ + log_info (_("invalid value for option '%s'\n"), "--default-new-key-algo"); + } + return DEFAULT_STD_KEY_PARAM; +} + + +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; + if (pk) + { + *p++ = ' '; + fingerprint_from_pk (pk, array, &n); + s = array; + /* Fixme: Use bin2hex */ + for (i=0; i < n ; i++, s++, p += 2) + snprintf (p, 3, "%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 gpg_error_t +write_uid (kbnode_t root, const char *s) +{ + PACKET *pkt = xmalloc_clear (sizeof *pkt); + size_t n = strlen (s); + + if (n > MAX_UID_PACKET_LENGTH - 10) + return gpg_error (GPG_ERR_INV_USER_ID); + + pkt->pkttype = PKT_USER_ID; + pkt->pkt.user_id = xmalloc_clear (sizeof *pkt->pkt.user_id + n); + 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)); + return 0; +} + +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; + + 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; +} + + +/* Add the key usage (i.e. key flags) in SIG from the public keys + * pubkey_usage field. OPAQUE has the public key. */ +int +keygen_add_key_flags (PKT_signature *sig, void *opaque) +{ + PKT_public_key *pk = opaque; + + do_add_key_flags (sig, pk->pubkey_usage); + 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[20*4+1]; /* Enough for 20 items. */ + + if (!string || !ascii_strcasecmp (string, "default")) + { + if (opt.def_preference_list) + string=opt.def_preference_list; + else + { + int any_compress = 0; + 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 ( !openpgp_cipher_test_algo (CIPHER_ALGO_AES256) ) + strcat(dummy_string,"S9 "); + if ( !openpgp_cipher_test_algo (CIPHER_ALGO_AES192) ) + strcat(dummy_string,"S8 "); + if ( !openpgp_cipher_test_algo (CIPHER_ALGO_AES) ) + strcat(dummy_string,"S7 "); + strcat(dummy_string,"S2 "); /* 3DES */ + + if (personal) + { + /* The default internal hash algo order is: + * SHA-256, SHA-384, SHA-512, SHA-224, SHA-1. + */ + if (!openpgp_md_test_algo (DIGEST_ALGO_SHA256)) + strcat (dummy_string, "H8 "); + + if (!openpgp_md_test_algo (DIGEST_ALGO_SHA384)) + strcat (dummy_string, "H9 "); + + if (!openpgp_md_test_algo (DIGEST_ALGO_SHA512)) + strcat (dummy_string, "H10 "); + } + else + { + /* The default advertised hash algo order is: + * SHA-512, SHA-384, SHA-256, SHA-224, SHA-1. + */ + if (!openpgp_md_test_algo (DIGEST_ALGO_SHA512)) + strcat (dummy_string, "H10 "); + + if (!openpgp_md_test_algo (DIGEST_ALGO_SHA384)) + strcat (dummy_string, "H9 "); + + if (!openpgp_md_test_algo (DIGEST_ALGO_SHA256)) + strcat (dummy_string, "H8 "); + } + + if (!openpgp_md_test_algo (DIGEST_ALGO_SHA224)) + strcat (dummy_string, "H11 "); + + strcat (dummy_string, "H2 "); /* SHA-1 */ + + if(!check_compress_algo(COMPRESS_ALGO_ZLIB)) + { + strcat(dummy_string,"Z2 "); + any_compress = 1; + } + + if(!check_compress_algo(COMPRESS_ALGO_BZIP2)) + { + strcat(dummy_string,"Z3 "); + any_compress = 1; + } + + if(!check_compress_algo(COMPRESS_ALGO_ZIP)) + { + strcat(dummy_string,"Z1 "); + any_compress = 1; + } + + /* In case we have no compress algo at all, declare that + we prefer no compresssion. */ + if (!any_compress) + strcat(dummy_string,"Z0 "); + + /* Remove the trailing space. */ + if (*dummy_string && dummy_string[strlen (dummy_string)-1] == ' ') + dummy_string[strlen (dummy_string)-1] = 0; + + string=dummy_string; + } + } + else if (!ascii_strcasecmp (string, "none")) + string = ""; + + if(strlen(string)) + { + char *prefstringbuf; + char *tok, *prefstring; + + /* We need a writable string. */ + prefstring = prefstringbuf = xstrdup (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); + rc=-1; + } + } + + xfree (prefstringbuf); + } + + 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; iref=1; + + uid->prefs=xmalloc((sizeof(prefitem_t *)* + (nsym_prefs+nhash_prefs+nzip_prefs+1))); + + for(i=0;iprefs[j].type=PREFTYPE_SYM; + uid->prefs[j].value=sym_prefs[i]; + } + + for(i=0;iprefs[j].type=PREFTYPE_HASH; + uid->prefs[j].value=hash_prefs[i]; + } + + for(i=0;iprefs[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;ihashed, 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;ihashed, 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) +{ + (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; +} + + + +/* Create a back-signature. If TIMESTAMP is not NULL, use it for the + signature creation time. */ +gpg_error_t +make_backsig (ctrl_t ctrl, PKT_signature *sig, PKT_public_key *pk, + PKT_public_key *sub_pk, PKT_public_key *sub_psk, + u32 timestamp, const char *cache_nonce) +{ + gpg_error_t err; + PKT_signature *backsig; + + cache_public_key (sub_pk); + + err = make_keysig_packet (ctrl, &backsig, pk, NULL, sub_pk, sub_psk, 0x19, + 0, timestamp, 0, NULL, NULL, cache_nonce); + if (err) + log_error ("make_keysig_packet failed for backsig: %s\n", + gpg_strerror (err)); + 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; + err = build_packet (backsig_out, &backsig_pkt); + free_packet (&backsig_pkt, NULL); + if (err) + log_error ("build_packet failed for backsig: %s\n", gpg_strerror (err)); + 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 = buf32_to_size_t (buf+2); + buf += 6; + } + else + BUG (); + } + else + { + int mark = 1; + + switch (buf[0]&3) + { + case 3: + BUG (); + break; + + case 2: + pktlen = (size_t)buf[mark++] << 24; + pktlen |= buf[mark++] << 16; + /* fall through */ + case 1: + pktlen |= buf[mark++] << 8; + /* fall through */ + 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 err; +} + + +/* Write a direct key signature to the first key in ROOT using the key + PSK. REVKEY is describes the direct key signature and TIMESTAMP is + the timestamp to set on the signature. */ +static gpg_error_t +write_direct_sig (ctrl_t ctrl, kbnode_t root, PKT_public_key *psk, + struct revocation_key *revkey, u32 timestamp, + const char *cache_nonce) +{ + gpg_error_t err; + PACKET *pkt; + PKT_signature *sig; + 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 (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); + + /* Make the signature. */ + err = make_keysig_packet (ctrl, &sig, pk, NULL,NULL, psk, 0x1F, + 0, timestamp, 0, + keygen_add_revkey, revkey, cache_nonce); + if (err) + { + log_error ("make_keysig_packet failed: %s\n", gpg_strerror (err) ); + return err; + } + + pkt = xmalloc_clear (sizeof *pkt); + pkt->pkttype = PKT_SIGNATURE; + pkt->pkt.signature = sig; + add_kbnode (root, new_kbnode (pkt)); + return err; +} + + + +/* Write a self-signature to the first user id in ROOT using the key + PSK. USE and TIMESTAMP give the extra data we need for the + signature. */ +static gpg_error_t +write_selfsigs (ctrl_t ctrl, kbnode_t root, PKT_public_key *psk, + unsigned int use, u32 timestamp, const char *cache_nonce) +{ + gpg_error_t err; + PACKET *pkt; + PKT_signature *sig; + PKT_user_id *uid; + 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 (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 (root, PKT_PUBLIC_KEY); + if (!node) + BUG(); + pk = node->pkt->pkt.public_key; + + /* The usage has not yet been set - do it now. */ + 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); + + /* Make the signature. */ + err = make_keysig_packet (ctrl, &sig, pk, uid, NULL, psk, 0x13, + 0, timestamp, 0, + keygen_add_std_prefs, pk, cache_nonce); + if (err) + { + log_error ("make_keysig_packet failed: %s\n", gpg_strerror (err)); + return err; + } + + pkt = xmalloc_clear (sizeof *pkt); + pkt->pkttype = PKT_SIGNATURE; + pkt->pkt.signature = sig; + add_kbnode (root, new_kbnode (pkt)); + + return err; +} + + +/* Write the key binding signature. If TIMESTAMP is not NULL use the + signature creation time. PRI_PSK is the key use for signing. + SUB_PSK is a key used to create a back-signature; that one is only + used if USE has the PUBKEY_USAGE_SIG capability. */ +static int +write_keybinding (ctrl_t ctrl, kbnode_t root, + PKT_public_key *pri_psk, PKT_public_key *sub_psk, + unsigned int use, u32 timestamp, const char *cache_nonce) +{ + gpg_error_t err; + PACKET *pkt; + PKT_signature *sig; + 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 primary pk packet from the tree. */ + node = find_kbnode (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 = root; node; node = node->next ) + { + if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY) + sub_pk = node->pkt->pkt.public_key; + } + if (!sub_pk) + BUG(); + + /* Make the signature. */ + oduap.usage = use; + oduap.pk = sub_pk; + err = make_keysig_packet (ctrl, &sig, pri_pk, NULL, sub_pk, pri_psk, 0x18, + 0, timestamp, 0, + keygen_add_key_flags_and_expire, &oduap, + cache_nonce); + if (err) + { + log_error ("make_keysig_packeto failed: %s\n", gpg_strerror (err)); + return err; + } + + /* Make a backsig. */ + if (use & PUBKEY_USAGE_SIG) + { + err = make_backsig (ctrl, + sig, pri_pk, sub_pk, sub_psk, timestamp, cache_nonce); + if (err) + return err; + } + + pkt = xmalloc_clear ( sizeof *pkt ); + pkt->pkttype = PKT_SIGNATURE; + pkt->pkt.signature = sig; + add_kbnode (root, new_kbnode (pkt) ); + return err; +} + + +static gpg_error_t +ecckey_from_sexp (gcry_mpi_t *array, gcry_sexp_t sexp, int algo) +{ + gpg_error_t err; + gcry_sexp_t list, l2; + char *curve = NULL; + int i; + const char *oidstr; + unsigned int nbits; + + array[0] = NULL; + array[1] = NULL; + array[2] = NULL; + + list = gcry_sexp_find_token (sexp, "public-key", 0); + if (!list) + return gpg_error (GPG_ERR_INV_OBJ); + l2 = gcry_sexp_cadr (list); + gcry_sexp_release (list); + list = l2; + if (!list) + return gpg_error (GPG_ERR_NO_OBJ); + + l2 = gcry_sexp_find_token (list, "curve", 0); + if (!l2) + { + err = gpg_error (GPG_ERR_NO_OBJ); + goto leave; + } + curve = gcry_sexp_nth_string (l2, 1); + if (!curve) + { + err = gpg_error (GPG_ERR_NO_OBJ); + goto leave; + } + gcry_sexp_release (l2); + oidstr = openpgp_curve_to_oid (curve, &nbits, NULL); + if (!oidstr) + { + /* That can't happen because we used one of the curves + gpg_curve_to_oid knows about. */ + err = gpg_error (GPG_ERR_INV_OBJ); + goto leave; + } + err = openpgp_oid_from_str (oidstr, &array[0]); + if (err) + goto leave; + + l2 = gcry_sexp_find_token (list, "q", 0); + if (!l2) + { + err = gpg_error (GPG_ERR_NO_OBJ); + goto leave; + } + array[1] = gcry_sexp_nth_mpi (l2, 1, GCRYMPI_FMT_USG); + gcry_sexp_release (l2); + if (!array[1]) + { + err = gpg_error (GPG_ERR_INV_OBJ); + goto leave; + } + gcry_sexp_release (list); + + if (algo == PUBKEY_ALGO_ECDH) + { + array[2] = pk_ecdh_default_params (nbits); + if (!array[2]) + { + err = gpg_error_from_syserror (); + goto leave; + } + } + + leave: + xfree (curve); + if (err) + { + for (i=0; i < 3; i++) + { + gcry_mpi_release (array[i]); + array[i] = NULL; + } + } + return err; +} + + +/* Extract key parameters from SEXP and store them in ARRAY. ELEMS is + a string where each character denotes a parameter name. TOPNAME is + the name of the top element above the elements. */ +static int +key_from_sexp (gcry_mpi_t *array, gcry_sexp_t sexp, + const char *topname, const char *elems) +{ + gcry_sexp_t list, l2; + const char *s; + int i, idx; + int rc = 0; + + list = gcry_sexp_find_token (sexp, topname, 0); + if (!list) + return gpg_error (GPG_ERR_INV_OBJ); + l2 = gcry_sexp_cadr (list); + gcry_sexp_release (list); + list = l2; + if (!list) + return gpg_error (GPG_ERR_NO_OBJ); + + for (idx=0,s=elems; *s; s++, idx++) + { + l2 = gcry_sexp_find_token (list, s, 1); + if (!l2) + { + rc = gpg_error (GPG_ERR_NO_OBJ); /* required parameter not found */ + goto leave; + } + array[idx] = gcry_sexp_nth_mpi (l2, 1, GCRYMPI_FMT_USG); + gcry_sexp_release (l2); + if (!array[idx]) + { + rc = gpg_error (GPG_ERR_INV_OBJ); /* required parameter invalid */ + goto leave; + } + } + gcry_sexp_release (list); + + leave: + if (rc) + { + for (i=0; itimestamp = timestamp; + pk->version = 4; + if (expireval) + pk->expiredate = pk->timestamp + expireval; + pk->pubkey_algo = algo; + + if (algo == PUBKEY_ALGO_ECDSA + || algo == PUBKEY_ALGO_EDDSA + || algo == PUBKEY_ALGO_ECDH ) + err = ecckey_from_sexp (pk->pkey, s_key, algo); + else + err = key_from_sexp (pk->pkey, s_key, "public-key", algoelem); + if (err) + { + log_error ("key_from_sexp failed: %s\n", gpg_strerror (err) ); + gcry_sexp_release (s_key); + free_public_key (pk); + return err; + } + gcry_sexp_release (s_key); + + pkt = xtrycalloc (1, sizeof *pkt); + if (!pkt) + { + err = gpg_error_from_syserror (); + free_public_key (pk); + return err; + } + + pkt->pkttype = is_subkey ? PKT_PUBLIC_SUBKEY : PKT_PUBLIC_KEY; + pkt->pkt.public_key = pk; + add_kbnode (pub_root, new_kbnode (pkt)); + + return 0; +} + + +/* Common code for the key generation function gen_xxx. */ +static int +common_gen (const char *keyparms, int algo, const char *algoelem, + kbnode_t pub_root, u32 timestamp, u32 expireval, int is_subkey, + int keygen_flags, const char *passphrase, + char **cache_nonce_addr, char **passwd_nonce_addr) +{ + int err; + PACKET *pkt; + PKT_public_key *pk; + gcry_sexp_t s_key; + + err = agent_genkey (NULL, cache_nonce_addr, passwd_nonce_addr, keyparms, + !!(keygen_flags & KEYGEN_FLAG_NO_PROTECTION), + passphrase, timestamp, + &s_key); + if (err) + { + log_error ("agent_genkey failed: %s\n", gpg_strerror (err) ); + return err; + } + + pk = xtrycalloc (1, sizeof *pk); + if (!pk) + { + err = gpg_error_from_syserror (); + gcry_sexp_release (s_key); + return err; + } + + pk->timestamp = timestamp; + pk->version = 4; + if (expireval) + pk->expiredate = pk->timestamp + expireval; + pk->pubkey_algo = algo; + + if (algo == PUBKEY_ALGO_ECDSA + || algo == PUBKEY_ALGO_EDDSA + || algo == PUBKEY_ALGO_ECDH ) + err = ecckey_from_sexp (pk->pkey, s_key, algo); + else + err = key_from_sexp (pk->pkey, s_key, "public-key", algoelem); + if (err) + { + log_error ("key_from_sexp failed: %s\n", gpg_strerror (err) ); + gcry_sexp_release (s_key); + free_public_key (pk); + return err; + } + gcry_sexp_release (s_key); + + pkt = xtrycalloc (1, sizeof *pkt); + if (!pkt) + { + err = gpg_error_from_syserror (); + free_public_key (pk); + return err; + } + + pkt->pkttype = is_subkey ? PKT_PUBLIC_SUBKEY : PKT_PUBLIC_KEY; + pkt->pkt.public_key = pk; + add_kbnode (pub_root, new_kbnode (pkt)); + + return 0; +} + + +/* + * Generate an Elgamal key. + */ +static int +gen_elg (int algo, unsigned int nbits, KBNODE pub_root, + u32 timestamp, u32 expireval, int is_subkey, + int keygen_flags, const char *passphrase, + char **cache_nonce_addr, char **passwd_nonce_addr) +{ + int err; + char *keyparms; + char nbitsstr[35]; + + log_assert (is_ELGAMAL (algo)); + + if (nbits < 1024) + { + nbits = 2048; + log_info (_("keysize invalid; using %u bits\n"), nbits ); + } + else if (nbits > 4096) + { + nbits = 4096; + 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 ); + } + + /* Note that we use transient-key only if no-protection has also + been enabled. */ + snprintf (nbitsstr, sizeof nbitsstr, "%u", nbits); + keyparms = xtryasprintf ("(genkey(%s(nbits %zu:%s)%s))", + algo == GCRY_PK_ELG_E ? "openpgp-elg" : + algo == GCRY_PK_ELG ? "elg" : "x-oops" , + strlen (nbitsstr), nbitsstr, + ((keygen_flags & KEYGEN_FLAG_TRANSIENT_KEY) + && (keygen_flags & KEYGEN_FLAG_NO_PROTECTION))? + "(transient-key)" : "" ); + if (!keyparms) + err = gpg_error_from_syserror (); + else + { + err = common_gen (keyparms, algo, "pgy", + pub_root, timestamp, expireval, is_subkey, + keygen_flags, passphrase, + cache_nonce_addr, passwd_nonce_addr); + xfree (keyparms); + } + + return err; +} + + +/* + * Generate an DSA key + */ +static gpg_error_t +gen_dsa (unsigned int nbits, KBNODE pub_root, + u32 timestamp, u32 expireval, int is_subkey, + int keygen_flags, const char *passphrase, + char **cache_nonce_addr, char **passwd_nonce_addr) +{ + int err; + unsigned int qbits; + char *keyparms; + char nbitsstr[35]; + char qbitsstr[35]; + + if (nbits < 768) + { + nbits = 2048; + 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 ); + } + + /* To comply with FIPS rules we round up to the next value unless in + expert mode. */ + if (!opt.expert && nbits > 1024 && (nbits % 1024)) + { + nbits = ((nbits + 1023) / 1024) * 1024; + 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 2047, 224 for nbits over 1024 + but less than 2048, and 160 for 1024 (DSA1). + */ + + if (nbits > 2047) + 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")); + + snprintf (nbitsstr, sizeof nbitsstr, "%u", nbits); + snprintf (qbitsstr, sizeof qbitsstr, "%u", qbits); + keyparms = xtryasprintf ("(genkey(dsa(nbits %zu:%s)(qbits %zu:%s)%s))", + strlen (nbitsstr), nbitsstr, + strlen (qbitsstr), qbitsstr, + ((keygen_flags & KEYGEN_FLAG_TRANSIENT_KEY) + && (keygen_flags & KEYGEN_FLAG_NO_PROTECTION))? + "(transient-key)" : "" ); + if (!keyparms) + err = gpg_error_from_syserror (); + else + { + err = common_gen (keyparms, PUBKEY_ALGO_DSA, "pqgy", + pub_root, timestamp, expireval, is_subkey, + keygen_flags, passphrase, + cache_nonce_addr, passwd_nonce_addr); + xfree (keyparms); + } + + return err; +} + + + +/* + * Generate an ECC key + */ +static gpg_error_t +gen_ecc (int algo, const char *curve, kbnode_t pub_root, + u32 timestamp, u32 expireval, int is_subkey, + int keygen_flags, const char *passphrase, + char **cache_nonce_addr, char **passwd_nonce_addr) +{ + gpg_error_t err; + char *keyparms; + + log_assert (algo == PUBKEY_ALGO_ECDSA + || algo == PUBKEY_ALGO_EDDSA + || algo == PUBKEY_ALGO_ECDH); + + if (!curve || !*curve) + return gpg_error (GPG_ERR_UNKNOWN_CURVE); + + /* Map the displayed short forms of some curves to their canonical + * names. */ + if (!ascii_strcasecmp (curve, "cv25519")) + curve = "Curve25519"; + else if (!ascii_strcasecmp (curve, "ed25519")) + curve = "Ed25519"; + + /* Note that we use the "comp" flag with EdDSA to request the use of + a 0x40 compression prefix octet. */ + if (algo == PUBKEY_ALGO_EDDSA) + keyparms = xtryasprintf + ("(genkey(ecc(curve %zu:%s)(flags eddsa comp%s)))", + strlen (curve), curve, + (((keygen_flags & KEYGEN_FLAG_TRANSIENT_KEY) + && (keygen_flags & KEYGEN_FLAG_NO_PROTECTION))? + " transient-key" : "")); + else if (algo == PUBKEY_ALGO_ECDH && !strcmp (curve, "Curve25519")) + keyparms = xtryasprintf + ("(genkey(ecc(curve %zu:%s)(flags djb-tweak comp%s)))", + strlen (curve), curve, + (((keygen_flags & KEYGEN_FLAG_TRANSIENT_KEY) + && (keygen_flags & KEYGEN_FLAG_NO_PROTECTION))? + " transient-key" : "")); + else + keyparms = xtryasprintf + ("(genkey(ecc(curve %zu:%s)(flags nocomp%s)))", + strlen (curve), curve, + (((keygen_flags & KEYGEN_FLAG_TRANSIENT_KEY) + && (keygen_flags & KEYGEN_FLAG_NO_PROTECTION))? + " transient-key" : "")); + + if (!keyparms) + err = gpg_error_from_syserror (); + else + { + err = common_gen (keyparms, algo, "", + pub_root, timestamp, expireval, is_subkey, + keygen_flags, passphrase, + cache_nonce_addr, passwd_nonce_addr); + xfree (keyparms); + } + + return err; +} + + +/* + * Generate an RSA key. + */ +static int +gen_rsa (int algo, unsigned int nbits, KBNODE pub_root, + u32 timestamp, u32 expireval, int is_subkey, + int keygen_flags, const char *passphrase, + char **cache_nonce_addr, char **passwd_nonce_addr) +{ + int err; + char *keyparms; + char nbitsstr[35]; + const unsigned maxsize = (opt.flags.large_rsa ? 8192 : 4096); + + log_assert (is_RSA(algo)); + + if (!nbits) + nbits = get_keysize_range (algo, NULL, NULL); + + if (nbits < 1024) + { + nbits = 3072; + log_info (_("keysize invalid; using %u bits\n"), nbits ); + } + else if (nbits > maxsize) + { + nbits = maxsize; + 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 ); + } + + snprintf (nbitsstr, sizeof nbitsstr, "%u", nbits); + keyparms = xtryasprintf ("(genkey(rsa(nbits %zu:%s)%s))", + strlen (nbitsstr), nbitsstr, + ((keygen_flags & KEYGEN_FLAG_TRANSIENT_KEY) + && (keygen_flags & KEYGEN_FLAG_NO_PROTECTION))? + "(transient-key)" : "" ); + if (!keyparms) + err = gpg_error_from_syserror (); + else + { + err = common_gen (keyparms, algo, "ne", + pub_root, timestamp, expireval, is_subkey, + keygen_flags, passphrase, + cache_nonce_addr, passwd_nonce_addr); + xfree (keyparms); + } + + return err; +} + + +/**************** + * 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")); +} + + +/* Ask for the key flags and return them. CURRENT gives the current + * usage which should normally be given as 0. MASK gives the allowed + * flags. */ +unsigned int +ask_key_flags_with_mask (int algo, int subkey, unsigned int current, + unsigned int mask) +{ + /* TRANSLATORS: Please use only plain ASCII characters for the + * translation. If this is not possible use single digits. The + * string needs to 8 bytes long. Here is a description of the + * functions: + * + * s = Toggle signing capability + * e = Toggle encryption capability + * a = Toggle authentication capability + * q = Finish + */ + const char *togglers = _("SsEeAaQq"); + char *answer = NULL; + const char *s; + unsigned int possible; + + if ( strlen(togglers) != 8 ) + { + tty_printf ("NOTE: Bad translation at %s:%d. " + "Please report.\n", __FILE__, __LINE__); + togglers = "11223300"; + } + + /* Mask the possible usage flags. This is for example used for a + * card based key. */ + possible = (openpgp_pk_algo_usage (algo) & mask); + + /* However, only primary keys may certify. */ + if (subkey) + possible &= ~PUBKEY_USAGE_CERT; + + /* Preload the current set with the possible set, without + * authentication if CURRENT is 0. If CURRENT is non-zero we mask + * with all possible usages. */ + if (current) + current &= possible; + else + current = (possible&~PUBKEY_USAGE_AUTH); + + for (;;) + { + tty_printf("\n"); + tty_printf(_("Possible actions for a %s key: "), + (algo == PUBKEY_ALGO_ECDSA + || algo == PUBKEY_ALGO_EDDSA) + ? "ECDSA/EdDSA" : openpgp_pk_algo_name (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 (*answer == '=') + { + /* Hack to allow direct entry of the capabilities. */ + current = 0; + for (s=answer+1; *s; s++) + { + if ((*s == 's' || *s == 'S') && (possible&PUBKEY_USAGE_SIG)) + current |= PUBKEY_USAGE_SIG; + else if ((*s == 'e' || *s == 'E') && (possible&PUBKEY_USAGE_ENC)) + current |= PUBKEY_USAGE_ENC; + else if ((*s == 'a' || *s == 'A') && (possible&PUBKEY_USAGE_AUTH)) + current |= PUBKEY_USAGE_AUTH; + else if (!subkey && *s == 'c') + { + /* Accept 'c' for the primary key because USAGE_CERT + will be set anyway. This is for folks who + want to experiment with a cert-only primary key. */ + current |= PUBKEY_USAGE_CERT; + } + } + break; + } + else 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; +} + + +unsigned int +ask_key_flags (int algo, int subkey, unsigned int current) +{ + return ask_key_flags_with_mask (algo, subkey, current, ~0); +} + + +/* Check whether we have a key for the key with HEXGRIP. Returns 0 if + there is no such key or the OpenPGP algo number for the key. */ +static int +check_keygrip (ctrl_t ctrl, const char *hexgrip) +{ + gpg_error_t err; + unsigned char *public; + size_t publiclen; + int algo; + + if (hexgrip[0] == '&') + hexgrip++; + + err = agent_readkey (ctrl, 0, hexgrip, &public); + if (err) + return 0; + publiclen = gcry_sexp_canon_len (public, 0, NULL, NULL); + + algo = get_pk_algo_from_canon_sexp (public, publiclen); + xfree (public); + + return map_pk_gcry_to_openpgp (algo); +} + + + +/* Ask for an algorithm. The function returns the algorithm id to + * create. If ADDMODE is false the function won't show an option to + * create the primary and subkey combined and won't set R_USAGE + * either. If a combined algorithm has been selected, the subkey + * algorithm is stored at R_SUBKEY_ALGO. If R_KEYGRIP is given, the + * user has the choice to enter the keygrip of an existing key. That + * keygrip is then stored at this address. The caller needs to free + * it. */ +static int +ask_algo (ctrl_t ctrl, int addmode, int *r_subkey_algo, unsigned int *r_usage, + char **r_keygrip) +{ + gpg_error_t err; + char *keygrip = NULL; + char *answer = NULL; + int algo; + int dummy_algo; + char *p; + + if (!r_subkey_algo) + r_subkey_algo = &dummy_algo; + + tty_printf (_("Please select what kind of key you want:\n")); + +#if GPG_USE_RSA + if (!addmode) + tty_printf (_(" (%d) RSA and RSA (default)\n"), 1 ); +#endif + + if (!addmode && opt.compliance != CO_DE_VS) + tty_printf (_(" (%d) DSA and Elgamal\n"), 2 ); + + if (opt.compliance != CO_DE_VS) + tty_printf (_(" (%d) DSA (sign only)\n"), 3 ); +#if GPG_USE_RSA + tty_printf (_(" (%d) RSA (sign only)\n"), 4 ); +#endif + + if (addmode) + { + if (opt.compliance != CO_DE_VS) + tty_printf (_(" (%d) Elgamal (encrypt only)\n"), 5 ); +#if GPG_USE_RSA + tty_printf (_(" (%d) RSA (encrypt only)\n"), 6 ); +#endif + } + if (opt.expert) + { + if (opt.compliance != CO_DE_VS) + tty_printf (_(" (%d) DSA (set your own capabilities)\n"), 7 ); +#if GPG_USE_RSA + tty_printf (_(" (%d) RSA (set your own capabilities)\n"), 8 ); +#endif + } + +#if GPG_USE_ECDSA || GPG_USE_ECDH || GPG_USE_EDDSA + if (opt.expert && !addmode) + tty_printf (_(" (%d) ECC and ECC\n"), 9 ); + if (opt.expert) + tty_printf (_(" (%d) ECC (sign only)\n"), 10 ); + if (opt.expert) + tty_printf (_(" (%d) ECC (set your own capabilities)\n"), 11 ); + if (opt.expert && addmode) + tty_printf (_(" (%d) ECC (encrypt only)\n"), 12 ); +#endif + + if (opt.expert && r_keygrip) + tty_printf (_(" (%d) Existing key\n"), 13 ); + if (r_keygrip) + tty_printf (_(" (%d) Existing key from card\n"), 14 ); + + for (;;) + { + *r_usage = 0; + *r_subkey_algo = 0; + xfree (answer); + answer = cpr_get ("keygen.algo", _("Your selection? ")); + cpr_kill_prompt (); + algo = *answer? atoi (answer) : 1; + + if (opt.compliance == CO_DE_VS + && (algo == 2 || algo == 3 || algo == 5 || algo == 7)) + { + tty_printf (_("Invalid selection.\n")); + } + else if ((algo == 1 || !strcmp (answer, "rsa+rsa")) && !addmode) + { + algo = PUBKEY_ALGO_RSA; + *r_subkey_algo = PUBKEY_ALGO_RSA; + break; + } + else if ((algo == 2 || !strcmp (answer, "dsa+elg")) && !addmode) + { + algo = PUBKEY_ALGO_DSA; + *r_subkey_algo = PUBKEY_ALGO_ELGAMAL_E; + break; + } + else if (algo == 3 || !strcmp (answer, "dsa")) + { + algo = PUBKEY_ALGO_DSA; + *r_usage = PUBKEY_USAGE_SIG; + break; + } + else if (algo == 4 || !strcmp (answer, "rsa/s")) + { + algo = PUBKEY_ALGO_RSA; + *r_usage = PUBKEY_USAGE_SIG; + break; + } + else if ((algo == 5 || !strcmp (answer, "elg")) && addmode) + { + algo = PUBKEY_ALGO_ELGAMAL_E; + *r_usage = PUBKEY_USAGE_ENC; + break; + } + else if ((algo == 6 || !strcmp (answer, "rsa/e")) && addmode) + { + algo = PUBKEY_ALGO_RSA; + *r_usage = PUBKEY_USAGE_ENC; + break; + } + else if ((algo == 7 || !strcmp (answer, "dsa/*")) && opt.expert) + { + algo = PUBKEY_ALGO_DSA; + *r_usage = ask_key_flags (algo, addmode, 0); + break; + } + else if ((algo == 8 || !strcmp (answer, "rsa/*")) && opt.expert) + { + algo = PUBKEY_ALGO_RSA; + *r_usage = ask_key_flags (algo, addmode, 0); + break; + } + else if ((algo == 9 || !strcmp (answer, "ecc+ecc")) + && opt.expert && !addmode) + { + algo = PUBKEY_ALGO_ECDSA; + *r_subkey_algo = PUBKEY_ALGO_ECDH; + break; + } + else if ((algo == 10 || !strcmp (answer, "ecc/s")) && opt.expert) + { + algo = PUBKEY_ALGO_ECDSA; + *r_usage = PUBKEY_USAGE_SIG; + break; + } + else if ((algo == 11 || !strcmp (answer, "ecc/*")) && opt.expert) + { + algo = PUBKEY_ALGO_ECDSA; + *r_usage = ask_key_flags (algo, addmode, 0); + break; + } + else if ((algo == 12 || !strcmp (answer, "ecc/e")) + && opt.expert && addmode) + { + algo = PUBKEY_ALGO_ECDH; + *r_usage = PUBKEY_USAGE_ENC; + break; + } + else if ((algo == 13 || !strcmp (answer, "keygrip")) + && opt.expert && r_keygrip) + { + for (;;) + { + xfree (answer); + answer = cpr_get ("keygen.keygrip", _("Enter the keygrip: ")); + cpr_kill_prompt (); + trim_spaces (answer); + if (!*answer) + { + xfree (answer); + answer = NULL; + continue; + } + + if (strlen (answer) != 40 && + !(answer[0] == '&' && strlen (answer+1) == 40)) + tty_printf + (_("Not a valid keygrip (expecting 40 hex digits)\n")); + else if (!(algo = check_keygrip (ctrl, answer)) ) + tty_printf (_("No key with this keygrip\n")); + else + break; /* Okay. */ + } + xfree (keygrip); + keygrip = answer; + answer = NULL; + *r_usage = ask_key_flags (algo, addmode, 0); + break; + } + else if ((algo == 14 || !strcmp (answer, "cardkey")) && r_keygrip) + { + char *serialno; + strlist_t keypairlist, sl; + int count, selection; + + err = agent_scd_serialno (&serialno, NULL); + if (err) + { + tty_printf (_("error reading the card: %s\n"), + gpg_strerror (err)); + goto ask_again; + } + tty_printf (_("Serial number of the card: %s\n"), serialno); + xfree (serialno); + + err = agent_scd_keypairinfo (ctrl, &keypairlist); + if (err) + { + tty_printf (_("error reading the card: %s\n"), + gpg_strerror (err)); + goto ask_again; + } + + do + { + tty_printf (_("Available keys:\n")); + for (count=1,sl=keypairlist; sl; sl = sl->next, count++) + { + gcry_sexp_t s_pkey; + char *algostr = NULL; + enum gcry_pk_algos algoid = 0; + const char *keyref; + int any = 0; + + keyref = strchr (sl->d, ' '); + if (keyref) + { + keyref++; + if (!agent_scd_readkey (keyref, &s_pkey)) + { + algostr = pubkey_algo_string (s_pkey, &algoid); + gcry_sexp_release (s_pkey); + } + } + /* We use the flags also encode the algo for use + * below. We need to tweak the algo in case + * GCRY_PK_ECC is returned becuase pubkey_algo_string + * is not aware of the OpenPGP algo mapping. + * FIXME: This is an ugly hack. */ + sl->flags &= 0xff; + if (algoid == GCRY_PK_ECC + && algostr && !strncmp (algostr, "nistp", 5) + && !(sl->flags & GCRY_PK_USAGE_ENCR)) + sl->flags |= (PUBKEY_ALGO_ECDSA << 8); + else if (algoid == GCRY_PK_ECC + && algostr && !strncmp (algostr, "brainpool", 9) + && !(sl->flags & GCRY_PK_USAGE_ENCR)) + sl->flags |= (PUBKEY_ALGO_ECDSA << 8); + else if (algoid == GCRY_PK_ECC + && algostr && !strcmp (algostr, "ed25519") + && !(sl->flags & GCRY_PK_USAGE_ENCR)) + sl->flags = (PUBKEY_ALGO_EDDSA << 8); + else + sl->flags |= (map_pk_gcry_to_openpgp (algoid) << 8); + + tty_printf (" (%d) %s %s", count, sl->d, algostr); + if ((sl->flags & GCRY_PK_USAGE_CERT)) + { + tty_printf ("%scert", any?",":" ("); + any = 1; + } + if ((sl->flags & GCRY_PK_USAGE_SIGN)) + { + tty_printf ("%ssign", any?",":" ("); + any = 1; + } + if ((sl->flags & GCRY_PK_USAGE_AUTH)) + { + tty_printf ("%sauth", any?",":" ("); + any = 1; + } + if ((sl->flags & GCRY_PK_USAGE_ENCR)) + { + tty_printf ("%sencr", any?",":" ("); + any = 1; + } + tty_printf ("%s\n", any?")":""); + xfree (algostr); + } + + xfree (answer); + answer = cpr_get ("keygen.cardkey", _("Your selection? ")); + cpr_kill_prompt (); + trim_spaces (answer); + selection = atoi (answer); + } + while (!(selection > 0 && selection < count)); + + for (count=1,sl=keypairlist; sl; sl = sl->next, count++) + if (count == selection) + break; + if (!sl) + { + /* Just in case COUNT is zero (no keys). */ + free_strlist (keypairlist); + goto ask_again; + } + + xfree (keygrip); + keygrip = xstrdup (sl->d); + if ((p = strchr (keygrip, ' '))) + *p = 0; + algo = (sl->flags >>8); + if (opt.expert) + *r_usage = ask_key_flags_with_mask (algo, addmode, + (sl->flags & 0xff), + (sl->flags & 0xff)); + else + { + *r_usage = (sl->flags & 0xff); + if (addmode) + *r_usage &= ~GCRY_PK_USAGE_CERT; + } + free_strlist (keypairlist); + break; + } + else + tty_printf (_("Invalid selection.\n")); + + ask_again: + ; + } + + xfree(answer); + if (r_keygrip) + *r_keygrip = keygrip; + return algo; +} + + +static unsigned int +get_keysize_range (int algo, unsigned int *min, unsigned int *max) +{ + unsigned int def; + unsigned int dummy1, dummy2; + + if (!min) + min = &dummy1; + if (!max) + max = &dummy2; + + switch(algo) + { + case PUBKEY_ALGO_DSA: + *min = opt.expert? 768 : 1024; + *max=3072; + def=2048; + break; + + case PUBKEY_ALGO_ECDSA: + case PUBKEY_ALGO_ECDH: + *min=256; + *max=521; + def=256; + break; + + case PUBKEY_ALGO_EDDSA: + *min=255; + *max=441; + def=255; + break; + + default: + *min = opt.compliance == CO_DE_VS ? 2048: 1024; + *max = 4096; + def = 3072; + break; + } + + return def; +} + + +/* Return a fixed up keysize depending on ALGO. */ +static unsigned int +fixup_keysize (unsigned int nbits, int algo, int silent) +{ + if (algo == PUBKEY_ALGO_DSA && (nbits % 64)) + { + nbits = ((nbits + 63) / 64) * 64; + if (!silent) + tty_printf (_("rounded up to %u bits\n"), nbits); + } + else if (algo == PUBKEY_ALGO_EDDSA) + { + if (nbits != 255 && nbits != 441) + { + if (nbits < 256) + nbits = 255; + else + nbits = 441; + if (!silent) + tty_printf (_("rounded to %u bits\n"), nbits); + } + } + else if (algo == PUBKEY_ALGO_ECDH || algo == PUBKEY_ALGO_ECDSA) + { + if (nbits != 256 && nbits != 384 && nbits != 521) + { + if (nbits < 256) + nbits = 256; + else if (nbits < 384) + nbits = 384; + else + nbits = 521; + if (!silent) + tty_printf (_("rounded to %u bits\n"), nbits); + } + } + else if ((nbits % 32)) + { + nbits = ((nbits + 31) / 32) * 32; + if (!silent) + tty_printf (_("rounded up to %u bits\n"), nbits ); + } + + return nbits; +} + + +/* Ask for the key size. ALGO is the algorithm. If PRIMARY_KEYSIZE + is not 0, the function asks for the size of the encryption + subkey. */ +static unsigned +ask_keysize (int algo, unsigned int primary_keysize) +{ + unsigned int nbits; + unsigned int min, def, max; + int for_subkey = !!primary_keysize; + int autocomp = 0; + + def = get_keysize_range (algo, &min, &max); + + if (primary_keysize && !opt.expert) + { + /* Deduce the subkey size from the primary key size. */ + if (algo == PUBKEY_ALGO_DSA && primary_keysize > 3072) + nbits = 3072; /* For performance reasons we don't support more + than 3072 bit DSA. However we won't see this + case anyway because DSA can't be used as an + encryption subkey ;-). */ + else + nbits = primary_keysize; + autocomp = 1; + goto leave; + } + + tty_printf(_("%s keys may be between %u and %u bits long.\n"), + openpgp_pk_algo_name (algo), min, max); + + for (;;) + { + char *prompt, *answer; + + if (for_subkey) + prompt = xasprintf (_("What keysize do you want " + "for the subkey? (%u) "), def); + else + prompt = xasprintf (_("What keysize do you want? (%u) "), def); + answer = cpr_get ("keygen.size", prompt); + cpr_kill_prompt (); + nbits = *answer? atoi (answer): def; + xfree(prompt); + xfree(answer); + + if(nbitsmax) + tty_printf(_("%s keysizes must be in the range %u-%u\n"), + openpgp_pk_algo_name (algo), min, max); + else + break; + } + + tty_printf (_("Requested keysize is %u bits\n"), nbits); + + leave: + nbits = fixup_keysize (nbits, algo, autocomp); + return nbits; +} + + +/* Ask for the curve. ALGO is the selected algorithm which this + function may adjust. Returns a const string of the name of the + curve. */ +const char * +ask_curve (int *algo, int *subkey_algo, const char *current) +{ + /* NB: We always use a complete algo list so that we have stable + numbers in the menu regardless on how Gpg was configured. */ + struct { + const char *name; + const char* eddsa_curve; /* Corresponding EdDSA curve. */ + const char *pretty_name; + unsigned int supported : 1; /* Supported by gpg. */ + unsigned int de_vs : 1; /* Allowed in CO_DE_VS. */ + unsigned int expert_only : 1; /* Only with --expert */ + unsigned int available : 1; /* Available in Libycrypt (runtime checked) */ + } curves[] = { +#if GPG_USE_ECDSA || GPG_USE_ECDH +# define MY_USE_ECDSADH 1 +#else +# define MY_USE_ECDSADH 0 +#endif + { "Curve25519", "Ed25519", "Curve 25519", !!GPG_USE_EDDSA, 0, 0, 0 }, + { "Curve448", "Ed448", "Curve 448", 0/*reserved*/ , 0, 1, 0 }, + { "NIST P-256", NULL, NULL, MY_USE_ECDSADH, 0, 1, 0 }, + { "NIST P-384", NULL, NULL, MY_USE_ECDSADH, 0, 0, 0 }, + { "NIST P-521", NULL, NULL, MY_USE_ECDSADH, 0, 1, 0 }, + { "brainpoolP256r1", NULL, "Brainpool P-256", MY_USE_ECDSADH, 1, 1, 0 }, + { "brainpoolP384r1", NULL, "Brainpool P-384", MY_USE_ECDSADH, 1, 1, 0 }, + { "brainpoolP512r1", NULL, "Brainpool P-512", MY_USE_ECDSADH, 1, 1, 0 }, + { "secp256k1", NULL, NULL, MY_USE_ECDSADH, 0, 1, 0 }, + }; +#undef MY_USE_ECDSADH + int idx; + char *answer; + const char *result = NULL; + gcry_sexp_t keyparms; + + tty_printf (_("Please select which elliptic curve you want:\n")); + + keyparms = NULL; + for (idx=0; idx < DIM(curves); idx++) + { + int rc; + + curves[idx].available = 0; + if (!curves[idx].supported) + continue; + + if (opt.compliance==CO_DE_VS) + { + if (!curves[idx].de_vs) + continue; /* Not allowed. */ + } + else if (!opt.expert && curves[idx].expert_only) + continue; + + /* We need to switch from the ECDH name of the curve to the + EDDSA name of the curve if we want a signing key. */ + gcry_sexp_release (keyparms); + rc = gcry_sexp_build (&keyparms, NULL, + "(public-key(ecc(curve %s)))", + curves[idx].eddsa_curve? curves[idx].eddsa_curve + /**/ : curves[idx].name); + if (rc) + continue; + if (!gcry_pk_get_curve (keyparms, 0, NULL)) + continue; + if (subkey_algo && curves[idx].eddsa_curve) + { + /* Both Curve 25519 (or 448) keys are to be created. Check that + Libgcrypt also supports the real Curve25519 (or 448). */ + gcry_sexp_release (keyparms); + rc = gcry_sexp_build (&keyparms, NULL, + "(public-key(ecc(curve %s)))", + curves[idx].name); + if (rc) + continue; + if (!gcry_pk_get_curve (keyparms, 0, NULL)) + continue; + } + + curves[idx].available = 1; + tty_printf (" (%d) %s\n", idx + 1, + curves[idx].pretty_name? + curves[idx].pretty_name:curves[idx].name); + } + gcry_sexp_release (keyparms); + + + for (;;) + { + answer = cpr_get ("keygen.curve", _("Your selection? ")); + cpr_kill_prompt (); + idx = *answer? atoi (answer) : 1; + if (!*answer && current) + { + xfree(answer); + return NULL; + } + else if (*answer && !idx) + { + /* See whether the user entered the name of the curve. */ + for (idx=0; idx < DIM(curves); idx++) + { + if (!opt.expert && curves[idx].expert_only) + continue; + if (!stricmp (curves[idx].name, answer) + || (curves[idx].pretty_name + && !stricmp (curves[idx].pretty_name, answer))) + break; + } + if (idx == DIM(curves)) + idx = -1; + } + else + idx--; + xfree(answer); + answer = NULL; + if (idx < 0 || idx >= DIM (curves) || !curves[idx].available) + tty_printf (_("Invalid selection.\n")); + else + { + /* If the user selected a signing algorithm and Curve25519 + we need to set the algo to EdDSA and update the curve name. + If switching away from EdDSA, we need to set the algo back + to ECDSA. */ + if (*algo == PUBKEY_ALGO_ECDSA || *algo == PUBKEY_ALGO_EDDSA) + { + if (curves[idx].eddsa_curve) + { + if (subkey_algo && *subkey_algo == PUBKEY_ALGO_ECDSA) + *subkey_algo = PUBKEY_ALGO_EDDSA; + *algo = PUBKEY_ALGO_EDDSA; + result = curves[idx].eddsa_curve; + } + else + { + if (subkey_algo && *subkey_algo == PUBKEY_ALGO_EDDSA) + *subkey_algo = PUBKEY_ALGO_ECDSA; + *algo = PUBKEY_ALGO_ECDSA; + result = curves[idx].name; + } + } + else + result = curves[idx].name; + break; + } + } + + if (!result) + result = curves[0].name; + + return result; +} + + +/**************** + * 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; + u32 abs_date = 0; + u32 curtime = make_timestamp (); + time_t tt; + + if (!string || !*string || !strcmp (string, "none") + || !strcmp (string, "never") || !strcmp (string, "-")) + seconds = 0; + else if (!strncmp (string, "seconds=", 8)) + seconds = atoi (string+8); + else if ((abs_date = scan_isodatestr(string)) + && (abs_date+86400/2) > curtime) + seconds = (abs_date+86400/2) - curtime; + else if ((tt = isotime2epoch (string)) != (time_t)(-1)) + seconds = (u32)tt - curtime; + else if ((mult = check_valid_days (string))) + seconds = atoi (string) * 86400L * mult; + else + seconds = (u32)(-1); + + return seconds; +} + +/* Parse a Creation-Date string which is either "1986-04-26" or + "19860426T042640". Returns 0 on error. */ +static u32 +parse_creation_string (const char *string) +{ + u32 seconds; + + if (!*string) + seconds = 0; + else if ( !strncmp (string, "seconds=", 8) ) + seconds = atoi (string+8); + else if ( !(seconds = scan_isodatestr (string))) + { + time_t tmp = isotime2epoch (string); + seconds = (tmp == (time_t)(-1))? 0 : tmp; + } + 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" + " = key expires in n days\n" + " w = key expires in n weeks\n" + " m = key expires in n months\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" + " = signature expires in n days\n" + " w = signature expires in n weeks\n" + " m = signature expires in n months\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; + + xfree(answer); + if(object==0) + answer = cpr_get("keygen.valid",_("Key is valid for? (0) ")); + else + { + char *prompt; + + prompt = xasprintf (_("Signature is valid for? (%s) "), def_expire); + answer = cpr_get("siggen.valid",prompt); + xfree(prompt); + + if(*answer=='\0') + answer=xstrdup(def_expire); + } + cpr_kill_prompt(); + trim_spaces(answer); + curtime = make_timestamp (); + 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) ) ); +#if SIZEOF_TIME_T <= 4 && !defined (HAVE_UNSIGNED_TIME_T) + 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")); + else +#endif /*SIZEOF_TIME_T*/ + if ( (time_t)((unsigned long)(curtime+interval)) < curtime ) + { + tty_printf (_("invalid value\n")); + continue; + } + } + + 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 PKT_user_id * +uid_from_string (const char *string) +{ + size_t n; + PKT_user_id *uid; + + n = strlen (string); + uid = xmalloc_clear (sizeof *uid + n); + uid->len = n; + strcpy (uid->name, string); + uid->ref = 1; + return uid; +} + + +/* Return true if the user id UID already exists in the keyblock. */ +static int +uid_already_in_keyblock (kbnode_t keyblock, const char *uid) +{ + PKT_user_id *uidpkt = uid_from_string (uid); + kbnode_t node; + int result = 0; + + for (node=keyblock; node && !result; node=node->next) + if (!is_deleted_kbnode (node) + && node->pkt->pkttype == PKT_USER_ID + && !cmp_user_ids (uidpkt, node->pkt->pkt.user_id)) + result = 1; + free_user_id (uidpkt); + return result; +} + + +/* Ask for a user ID. With a MODE of 1 an extra help prompt is + printed for use during a new key creation. If KEYBLOCK is not NULL + the function prevents the creation of an already existing user + ID. IF FULL is not set some prompts are not shown. */ +static char * +ask_user_id (int mode, int full, KBNODE keyblock) +{ + char *answer; + char *aname, *acomment, *amail, *uid; + + if ( !mode ) + { + /* TRANSLATORS: This is the new string telling the user what + gpg is now going to do (i.e. ask for the parts of the user + ID). Note that if you do not translate this string, a + different string will be used, which might still have + a correct translation. */ + const char *s1 = + N_("\n" + "GnuPG needs to construct a user ID to identify your key.\n" + "\n"); + const char *s2 = _(s1); + + if (!strcmp (s1, s2)) + { + /* There is no translation for the string thus we to use + the old info text. gettext has no way to tell whether + a translation is actually available, thus we need to + to compare again. */ + /* TRANSLATORS: This string is in general not anymore used + but you should keep your existing translation. In case + the new string is not translated this old string will + be used. */ + const char *s3 = N_("\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) \"\n\n"); + const char *s4 = _(s3); + if (strcmp (s3, s4)) + s2 = s3; /* A translation exists - use it. */ + } + tty_printf ("%s", s2) ; + } + 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")); + tty_printf(_("The characters '%s' and '%s' may not " + "appear in name\n"), "<", ">"); + } + else if( digitp(aname) ) + tty_printf(_("Name may not start with a digit\n")); + else if (*aname && strlen (aname) < 5) + { + tty_printf(_("Name must be at least 5 characters long\n")); + /* However, we allow an empty name. */ + } + 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) { + if (full) { + 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; + } + } + else { + xfree (acomment); + acomment = xstrdup (""); + } + } + + + xfree(uid); + uid = p = xmalloc(strlen(aname)+strlen(amail)+strlen(acomment)+12+10); + if (!*aname && *amail && !*acomment && !random_is_faked ()) + { /* Empty name and comment but with mail address. Use + simplified form with only the non-angle-bracketed mail + address. */ + p = stpcpy (p, amail); + } + else + { + p = stpcpy (p, aname ); + if (*acomment) + p = stpcpy(stpcpy(stpcpy(p," ("), acomment),")"); + if (*amail) + p = stpcpy(stpcpy(stpcpy(p," <"), amail),">"); + } + + /* Append a warning if the RNG is switched into fake mode. */ + if ( random_is_faked () ) + 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); + + 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") ); + } + + if (!fail && keyblock) + { + if (uid_already_in_keyblock (keyblock, uid)) + { + tty_printf (_("Such a user ID already exists on this key!\n")); + fail = 1; + } + } + + 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 + (fail?8:6)); + answer[1] = 0; + } + else if (full) { + 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(); + } + else { + answer = cpr_get("keygen.userid.cmd", fail? + _("Change (N)ame, (E)mail, or (Q)uit? ") : + _("Change (N)ame, (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) + break; + xfree(uid); uid = NULL; + } + if( uid ) { + char *p = native_to_utf8( uid ); + xfree( uid ); + uid = p; + } + return uid; +} + + +/* Basic key generation. Here we divert to the actual generation + routines based on the requested algorithm. */ +static int +do_create (int algo, unsigned int nbits, const char *curve, KBNODE pub_root, + u32 timestamp, u32 expiredate, int is_subkey, + int keygen_flags, const char *passphrase, + char **cache_nonce_addr, char **passwd_nonce_addr) +{ + gpg_error_t err; + + /* Fixme: The entropy collecting message should be moved to a + libgcrypt progress handler. */ + 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) + err = gen_elg (algo, nbits, pub_root, timestamp, expiredate, is_subkey, + keygen_flags, passphrase, + cache_nonce_addr, passwd_nonce_addr); + else if (algo == PUBKEY_ALGO_DSA) + err = gen_dsa (nbits, pub_root, timestamp, expiredate, is_subkey, + keygen_flags, passphrase, + cache_nonce_addr, passwd_nonce_addr); + else if (algo == PUBKEY_ALGO_ECDSA + || algo == PUBKEY_ALGO_EDDSA + || algo == PUBKEY_ALGO_ECDH) + err = gen_ecc (algo, curve, pub_root, timestamp, expiredate, is_subkey, + keygen_flags, passphrase, + cache_nonce_addr, passwd_nonce_addr); + else if (algo == PUBKEY_ALGO_RSA) + err = gen_rsa (algo, nbits, pub_root, timestamp, expiredate, is_subkey, + keygen_flags, passphrase, + cache_nonce_addr, passwd_nonce_addr); + else + BUG(); + + return err; +} + + +/* Generate a new user id packet or return NULL if canceled. If + KEYBLOCK is not NULL the function prevents the creation of an + already existing user ID. If UIDSTR is not NULL the user is not + asked but UIDSTR is used to create the user id packet; if the user + id already exists NULL is returned. UIDSTR is expected to be utf-8 + encoded and should have already been checked for a valid length + etc. */ +PKT_user_id * +generate_user_id (KBNODE keyblock, const char *uidstr) +{ + PKT_user_id *uid; + char *p; + + if (uidstr) + { + if (uid_already_in_keyblock (keyblock, uidstr)) + return NULL; /* Already exists. */ + uid = uid_from_string (uidstr); + } + else + { + p = ask_user_id (1, 1, keyblock); + if (!p) + return NULL; /* Canceled. */ + uid = uid_from_string (p); + xfree (p); + } + return uid; +} + + +/* Helper for parse_key_parameter_string for one part of the + * specification string; i.e. ALGO/FLAGS. If STRING is NULL or empty + * success is returned. On error an error code is returned. Note + * that STRING may be modified by this function. NULL may be passed + * for any parameter. FOR_SUBKEY shall be true if this is used as a + * subkey. If CLEAR_CERT is set a default CERT usage will be cleared; + * this is useful if for example the default algorithm is used for a + * subkey. */ +static gpg_error_t +parse_key_parameter_part (ctrl_t ctrl, + char *string, int for_subkey, int clear_cert, + int *r_algo, unsigned int *r_size, + unsigned int *r_keyuse, + char const **r_curve, + char **r_keygrip) +{ + gpg_error_t err; + char *flags; + int algo; + char *endp; + const char *curve = NULL; + int ecdh_or_ecdsa = 0; + unsigned int size; + int keyuse; + int i; + const char *s; + int from_card = 0; + char *keygrip = NULL; + + if (!string || !*string) + return 0; /* Success. */ + + flags = strchr (string, '/'); + if (flags) + *flags++ = 0; + + algo = 0; + if (!ascii_strcasecmp (string, "card")) + from_card = 1; + else if (strlen (string) >= 3 && (digitp (string+3) || !string[3])) + { + if (!ascii_memcasecmp (string, "rsa", 3)) + algo = PUBKEY_ALGO_RSA; + else if (!ascii_memcasecmp (string, "dsa", 3)) + algo = PUBKEY_ALGO_DSA; + else if (!ascii_memcasecmp (string, "elg", 3)) + algo = PUBKEY_ALGO_ELGAMAL_E; + } + + if (from_card) + ; /* We need the flags before we can figure out the key to use. */ + else if (algo) + { + if (!string[3]) + size = get_keysize_range (algo, NULL, NULL); + else + { + size = strtoul (string+3, &endp, 10); + if (size < 512 || size > 16384 || *endp) + return gpg_error (GPG_ERR_INV_VALUE); + } + } + else if ((curve = openpgp_is_curve_supported (string, &algo, &size))) + { + if (!algo) + { + algo = PUBKEY_ALGO_ECDH; /* Default ECC algorithm. */ + ecdh_or_ecdsa = 1; /* We may need to switch the algo. */ + } + } + else + return gpg_error (GPG_ERR_UNKNOWN_CURVE); + + /* Parse the flags. */ + keyuse = 0; + if (flags) + { + char **tokens = NULL; + + tokens = strtokenize (flags, ","); + if (!tokens) + return gpg_error_from_syserror (); + + for (i=0; (s = tokens[i]); i++) + { + if (!*s) + ; + else if (!ascii_strcasecmp (s, "sign")) + keyuse |= PUBKEY_USAGE_SIG; + else if (!ascii_strcasecmp (s, "encrypt") + || !ascii_strcasecmp (s, "encr")) + keyuse |= PUBKEY_USAGE_ENC; + else if (!ascii_strcasecmp (s, "auth")) + keyuse |= PUBKEY_USAGE_AUTH; + else if (!ascii_strcasecmp (s, "cert")) + keyuse |= PUBKEY_USAGE_CERT; + else if (!ascii_strcasecmp (s, "ecdsa") && !from_card) + { + if (algo == PUBKEY_ALGO_ECDH || algo == PUBKEY_ALGO_ECDSA) + algo = PUBKEY_ALGO_ECDSA; + else + { + xfree (tokens); + return gpg_error (GPG_ERR_INV_FLAG); + } + ecdh_or_ecdsa = 0; + } + else if (!ascii_strcasecmp (s, "ecdh") && !from_card) + { + if (algo == PUBKEY_ALGO_ECDH || algo == PUBKEY_ALGO_ECDSA) + algo = PUBKEY_ALGO_ECDH; + else + { + xfree (tokens); + return gpg_error (GPG_ERR_INV_FLAG); + } + ecdh_or_ecdsa = 0; + } + else if (!ascii_strcasecmp (s, "eddsa") && !from_card) + { + /* Not required but we allow it for consistency. */ + if (algo == PUBKEY_ALGO_EDDSA) + ; + else + { + xfree (tokens); + return gpg_error (GPG_ERR_INV_FLAG); + } + } + else + { + xfree (tokens); + return gpg_error (GPG_ERR_UNKNOWN_FLAG); + } + } + + xfree (tokens); + } + + /* If not yet decided switch between ecdh and ecdsa unless we want + * to read the algo from the current card. */ + if (from_card) + { + strlist_t keypairlist, sl; + char *reqkeyref; + + if (!keyuse) + keyuse = (for_subkey? PUBKEY_USAGE_ENC + /* */ : (PUBKEY_USAGE_CERT|PUBKEY_USAGE_SIG)); + + /* Access the card to make sure we have one and to show the S/N. */ + { + char *serialno; + + err = agent_scd_serialno (&serialno, NULL); + if (err) + { + log_error (_("error reading the card: %s\n"), gpg_strerror (err)); + return err; + } + if (!opt.quiet) + log_info (_("Serial number of the card: %s\n"), serialno); + xfree (serialno); + } + + err = agent_scd_keypairinfo (ctrl, &keypairlist); + if (err) + { + log_error (_("error reading the card: %s\n"), gpg_strerror (err)); + return err; + } + agent_scd_getattr_one ((keyuse & (PUBKEY_USAGE_SIG|PUBKEY_USAGE_CERT)) + ? "$SIGNKEYID":"$ENCRKEYID", &reqkeyref); + + algo = 0; /* Should already be the case. */ + for (sl=keypairlist; sl && !algo; sl = sl->next) + { + gcry_sexp_t s_pkey; + char *algostr = NULL; + enum gcry_pk_algos algoid = 0; + const char *keyref; + + if (!reqkeyref) + continue; /* Card does not provide the info (skip all). */ + + keyref = strchr (sl->d, ' '); + if (!keyref) + continue; /* Ooops. */ + keyref++; + if (strcmp (reqkeyref, keyref)) + continue; /* This is not the requested keyref. */ + + if ((keyuse & (PUBKEY_USAGE_SIG|PUBKEY_USAGE_CERT)) + && (sl->flags & (GCRY_PK_USAGE_SIGN|GCRY_PK_USAGE_CERT))) + ; /* Okay */ + else if ((keyuse & PUBKEY_USAGE_ENC) + && (sl->flags & GCRY_PK_USAGE_ENCR)) + ; /* Okay */ + else + continue; /* Not usable for us. */ + + if (agent_scd_readkey (keyref, &s_pkey)) + continue; /* Could not read the key. */ + + algostr = pubkey_algo_string (s_pkey, &algoid); + gcry_sexp_release (s_pkey); + + + /* Map to OpenPGP algo number. + * We need to tweak the algo in case GCRY_PK_ECC is returned + * because pubkey_algo_string is not aware of the OpenPGP + * algo mapping. FIXME: This is an ugly hack. */ + if (algoid == GCRY_PK_ECC + && algostr && !strncmp (algostr, "nistp", 5) + && !(sl->flags & GCRY_PK_USAGE_ENCR)) + algo = PUBKEY_ALGO_ECDSA; + else if (algoid == GCRY_PK_ECC + && algostr && !strcmp (algostr, "ed25519") + && !(sl->flags & GCRY_PK_USAGE_ENCR)) + algo = PUBKEY_ALGO_EDDSA; + else + algo = map_pk_gcry_to_openpgp (algoid); + + xfree (algostr); + xfree (keygrip); + keygrip = xtrystrdup (sl->d); + if (!keygrip) + { + err = gpg_error_from_syserror (); + xfree (reqkeyref); + free_strlist (keypairlist); + return err; + } + if ((endp = strchr (keygrip, ' '))) + *endp = 0; + } + + xfree (reqkeyref); + free_strlist (keypairlist); + if (!algo || !keygrip) + { + err = gpg_error (GPG_ERR_PUBKEY_ALGO); + log_error ("no usable key on the card: %s\n", gpg_strerror (err)); + xfree (keygrip); + return err; + } + } + else if (ecdh_or_ecdsa && keyuse) + algo = (keyuse & PUBKEY_USAGE_ENC)? PUBKEY_ALGO_ECDH : PUBKEY_ALGO_ECDSA; + else if (ecdh_or_ecdsa) + algo = for_subkey? PUBKEY_ALGO_ECDH : PUBKEY_ALGO_ECDSA; + + /* Set or fix key usage. */ + if (!keyuse) + { + if (algo == PUBKEY_ALGO_ECDSA || algo == PUBKEY_ALGO_EDDSA + || algo == PUBKEY_ALGO_DSA) + keyuse = PUBKEY_USAGE_SIG; + else if (algo == PUBKEY_ALGO_RSA) + keyuse = for_subkey? PUBKEY_USAGE_ENC : PUBKEY_USAGE_SIG; + else + keyuse = PUBKEY_USAGE_ENC; + } + else if (algo == PUBKEY_ALGO_ECDSA || algo == PUBKEY_ALGO_EDDSA + || algo == PUBKEY_ALGO_DSA) + { + keyuse &= ~PUBKEY_USAGE_ENC; /* Forbid encryption. */ + } + else if (algo == PUBKEY_ALGO_ECDH || algo == PUBKEY_ALGO_ELGAMAL_E) + { + keyuse = PUBKEY_USAGE_ENC; /* Allow only encryption. */ + } + + /* Make sure a primary key can certify. */ + if (!for_subkey) + keyuse |= PUBKEY_USAGE_CERT; + + /* But if requested remove th cert usage. */ + if (clear_cert) + keyuse &= ~PUBKEY_USAGE_CERT; + + /* Check that usage is actually possible. */ + if (/**/((keyuse & (PUBKEY_USAGE_SIG|PUBKEY_USAGE_AUTH|PUBKEY_USAGE_CERT)) + && !pubkey_get_nsig (algo)) + || ((keyuse & PUBKEY_USAGE_ENC) + && !pubkey_get_nenc (algo)) + || (for_subkey && (keyuse & PUBKEY_USAGE_CERT))) + { + xfree (keygrip); + return gpg_error (GPG_ERR_WRONG_KEY_USAGE); + } + + /* Return values. */ + if (r_algo) + *r_algo = algo; + if (r_size) + { + unsigned int min, def, max; + + /* Make sure the keysize is in the allowed range. */ + def = get_keysize_range (algo, &min, &max); + if (!size) + size = def; + else if (size < min) + size = min; + else if (size > max) + size = max; + + *r_size = fixup_keysize (size, algo, 1); + } + + if (r_keyuse) + *r_keyuse = keyuse; + if (r_curve) + *r_curve = curve; + + if (r_keygrip) + *r_keygrip = keygrip; + else + xfree (keygrip); + + return 0; +} + +/* Parse and return the standard key generation parameter. + * The string is expected to be in this format: + * + * ALGO[/FLAGS][+SUBALGO[/FLAGS]] + * + * Here ALGO is a string in the same format as printed by the + * keylisting. For example: + * + * rsa3072 := RSA with 3072 bit. + * dsa2048 := DSA with 2048 bit. + * elg2048 := Elgamal with 2048 bit. + * ed25519 := EDDSA using curve Ed25519. + * cv25519 := ECDH using curve Curve25519. + * nistp256:= ECDSA or ECDH using curve NIST P-256 + * + * All strings with an unknown prefix are considered an elliptic + * curve. Curves which have no implicit algorithm require that FLAGS + * is given to select whether ECDSA or ECDH is used; this can either + * be done using an algorithm keyword or usage keywords. + * + * FLAGS is a comma delimited string of keywords: + * + * cert := Allow usage Certify + * sign := Allow usage Sign + * encr := Allow usage Encrypt + * auth := Allow usage Authentication + * encrypt := Alias for "encr" + * ecdsa := Use algorithm ECDSA. + * eddsa := Use algorithm EdDSA. + * ecdh := Use algorithm ECDH. + * + * There are several defaults and fallbacks depending on the + * algorithm. PART can be used to select which part of STRING is + * used: + * -1 := Both parts + * 0 := Only the part of the primary key + * 1 := If there is one part parse that one, if there are + * two parts parse the part which best matches the + * SUGGESTED_USE or in case that can't be evaluated the second part. + * Always return using the args for the primary key (R_ALGO,....). + * + */ +gpg_error_t +parse_key_parameter_string (ctrl_t ctrl, + const char *string, int part, + unsigned int suggested_use, + int *r_algo, unsigned int *r_size, + unsigned int *r_keyuse, + char const **r_curve, + char **r_keygrip, + int *r_subalgo, unsigned int *r_subsize, + unsigned int *r_subkeyuse, + char const **r_subcurve, + char **r_subkeygrip) +{ + gpg_error_t err = 0; + char *primary, *secondary; + + if (r_algo) + *r_algo = 0; + if (r_size) + *r_size = 0; + if (r_keyuse) + *r_keyuse = 0; + if (r_curve) + *r_curve = NULL; + if (r_keygrip) + *r_keygrip = NULL; + if (r_subalgo) + *r_subalgo = 0; + if (r_subsize) + *r_subsize = 0; + if (r_subkeyuse) + *r_subkeyuse = 0; + if (r_subcurve) + *r_subcurve = NULL; + if (r_subkeygrip) + *r_subkeygrip = NULL; + + if (!string || !*string + || !ascii_strcasecmp (string, "default") || !strcmp (string, "-")) + string = get_default_pubkey_algo (); + else if (!ascii_strcasecmp (string, "future-default") + || !ascii_strcasecmp (string, "futuredefault")) + string = FUTURE_STD_KEY_PARAM; + else if (!ascii_strcasecmp (string, "card")) + string = "card/cert,sign+card/encr"; + + primary = xstrdup (string); + secondary = strchr (primary, '+'); + if (secondary) + *secondary++ = 0; + if (part == -1 || part == 0) + { + err = parse_key_parameter_part (ctrl, primary, + 0, 0, r_algo, r_size, + r_keyuse, r_curve, r_keygrip); + if (!err && part == -1) + err = parse_key_parameter_part (ctrl, secondary, + 1, 0, r_subalgo, r_subsize, + r_subkeyuse, r_subcurve, + r_subkeygrip); + } + else if (part == 1) + { + /* If we have SECONDARY, use that part. If there is only one + * part consider this to be the subkey algo. In case a + * SUGGESTED_USE has been given and the usage of the secondary + * part does not match SUGGESTED_USE try again using the primary + * part. Noet thar when falling back to the primary key we need + * to force clearing the cert usage. */ + if (secondary) + { + err = parse_key_parameter_part (ctrl, secondary, + 1, 0, + r_algo, r_size, r_keyuse, r_curve, + r_keygrip); + if (!err && suggested_use && r_keyuse && !(suggested_use & *r_keyuse)) + err = parse_key_parameter_part (ctrl, primary, + 1, 1 /*(clear cert)*/, + r_algo, r_size, r_keyuse, r_curve, + r_keygrip); + } + else + err = parse_key_parameter_part (ctrl, primary, + 1, 0, + r_algo, r_size, r_keyuse, r_curve, + r_keygrip); + } + + xfree (primary); + + return err; +} + + + +/* Append R to the linked list PARA. */ +static void +append_to_parameter (struct para_data_s *para, struct para_data_s *r) +{ + log_assert (para); + while (para->next) + para = para->next; + para->next = r; +} + +/* Release the parameter list R. */ +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 && *r->u.value) + wipememory (r->u.value, strlen (r->u.value)); + 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; +} + + +/* This is similar to get_parameter_value but also returns the empty + string. This is required so that quick_generate_keypair can use an + empty Passphrase to specify no-protection. */ +static const char * +get_parameter_passphrase (struct para_data_s *para) +{ + struct para_data_s *r = get_parameter (para, pPASSPHRASE); + return r ? r->u.value : NULL; +} + + +static int +get_parameter_algo (ctrl_t ctrl, struct para_data_s *para, enum para_name key, + int *r_default) +{ + int i; + struct para_data_s *r = get_parameter( para, key ); + + if (r_default) + *r_default = 0; + + if (!r) + return -1; + + /* Note that we need to handle the ECC algorithms specified as + strings directly because Libgcrypt folds them all to ECC. */ + if (!ascii_strcasecmp (r->u.value, "default")) + { + /* Note: If you change this default algo, remember to change it + * also in gpg.c:gpgconf_list. */ + /* FIXME: We only allow the algo here and have a separate thing + * for the curve etc. That is a ugly but demanded for backward + * compatibility with the batch key generation. It would be + * better to make full use of parse_key_parameter_string. */ + parse_key_parameter_string (ctrl, NULL, 0, 0, + &i, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL); + if (r_default) + *r_default = 1; + } + else if (digitp (r->u.value)) + i = atoi( r->u.value ); + else if (!strcmp (r->u.value, "ELG-E") + || !strcmp (r->u.value, "ELG")) + i = PUBKEY_ALGO_ELGAMAL_E; + else if (!ascii_strcasecmp (r->u.value, "EdDSA")) + i = PUBKEY_ALGO_EDDSA; + else if (!ascii_strcasecmp (r->u.value, "ECDSA")) + i = PUBKEY_ALGO_ECDSA; + else if (!ascii_strcasecmp (r->u.value, "ECDH")) + i = PUBKEY_ALGO_ECDH; + else + i = map_pk_gcry_to_openpgp (gcry_pk_map_name (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 a usage string. The usage keywords "auth", "sign", "encr" + * may be delimited by space, tab, or comma. On error -1 is returned + * instead of the usage flags. */ +static int +parse_usagestr (const char *usagestr) +{ + gpg_error_t err; + char **tokens = NULL; + const char *s; + int i; + unsigned int use = 0; + + tokens = strtokenize (usagestr, " \t,"); + if (!tokens) + { + err = gpg_error_from_syserror (); + log_error ("strtokenize failed: %s\n", gpg_strerror (err)); + return -1; + } + + for (i=0; (s = tokens[i]); i++) + { + if (!*s) + ; + else if (!ascii_strcasecmp (s, "sign")) + use |= PUBKEY_USAGE_SIG; + else if (!ascii_strcasecmp (s, "encrypt") + || !ascii_strcasecmp (s, "encr")) + use |= PUBKEY_USAGE_ENC; + else if (!ascii_strcasecmp (s, "auth")) + use |= PUBKEY_USAGE_AUTH; + else if (!ascii_strcasecmp (s, "cert")) + use |= PUBKEY_USAGE_CERT; + else + { + xfree (tokens); + return -1; /* error */ + } + } + + xfree (tokens); + return use; +} + + +/* + * Parse the usage parameter and set the keyflags. Returns -1 on + * error, 0 for no usage given or 1 for usage available. + */ +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 ); + int i; + + if (!r) + return 0; /* none (this is an optional parameter)*/ + + i = parse_usagestr (r->u.value); + if (i == -1) + { + log_error ("%s:%d: invalid usage list\n", fname, r->lnr ); + return -1; /* error */ + } + + r->u.usage = i; + 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;iu.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 == pKEYCREATIONDATE ) + return r->u.creation; + 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 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 (ctrl_t ctrl, 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 is_default = 0; + int have_user_id = 0; + int err, algo; + + /* Check that we have all required parameters. */ + r = get_parameter( para, pKEYTYPE ); + if(r) + { + algo = get_parameter_algo (ctrl, para, pKEYTYPE, &is_default); + if (openpgp_pk_test_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) + { + /* Default to algo capabilities if key-usage is not provided and + no default algorithm has been requested. */ + r = xmalloc_clear(sizeof(*r)); + r->key = pKEYUSAGE; + r->u.usage = (is_default + ? (PUBKEY_USAGE_CERT | PUBKEY_USAGE_SIG) + : openpgp_pk_algo_usage(algo)); + append_to_parameter (para, r); + } + else if (err == -1) + return -1; + else + { + r = get_parameter (para, pKEYUSAGE); + if (r && (r->u.usage & ~openpgp_pk_algo_usage (algo))) + { + log_error ("%s:%d: specified Key-Usage not allowed for algo %d\n", + fname, r->lnr, algo); + return -1; + } + } + + is_default = 0; + r = get_parameter( para, pSUBKEYTYPE ); + if(r) + { + algo = get_parameter_algo (ctrl, para, pSUBKEYTYPE, &is_default); + if (openpgp_pk_test_algo (algo)) + { + log_error ("%s:%d: invalid algorithm\n", fname, r->lnr ); + return -1; + } + + err = parse_parameter_usage (fname, para, pSUBKEYUSAGE); + if (!err) + { + /* Default to algo capabilities if subkey-usage is not + provided */ + r = xmalloc_clear (sizeof(*r)); + r->key = pSUBKEYUSAGE; + r->u.usage = (is_default + ? PUBKEY_USAGE_ENC + : openpgp_pk_algo_usage (algo)); + append_to_parameter (para, r); + } + else if (err == -1) + return -1; + else + { + r = get_parameter (para, pSUBKEYUSAGE); + if (r && (r->u.usage & ~openpgp_pk_algo_usage (algo))) + { + log_error ("%s:%d: specified Subkey-Usage not allowed" + " for algo %d\n", fname, r->lnr, algo); + 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 ) + { + /* If we have only the email part, do not add the space + * and the angle brackets. */ + if (*r->u.value) + p = stpcpy(stpcpy(stpcpy(p," <"), s3 ),">"); + else + p = stpcpy (p, s3); + } + append_to_parameter (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); + if(spec) + { + free_keyserver_spec(spec); + opt.def_keyserver_url=s1; + } + else + { + r = get_parameter (para, pKEYSERVER); + 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 KEYCREATIONDATE from Creation-Date. */ + r = get_parameter (para, pCREATIONDATE); + if (r && *r->u.value) + { + u32 seconds; + + seconds = parse_creation_string (r->u.value); + if (!seconds) + { + log_error ("%s:%d: invalid creation date\n", fname, r->lnr ); + return -1; + } + r->u.creation = seconds; + r->key = pKEYCREATIONDATE; /* Change that entry. */ + } + + /* 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; + append_to_parameter (para, r); + } + + do_generate_keypair (ctrl, 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 (ctrl_t ctrl, const char *fname ) +{ + static struct { const char *name; + enum para_name key; + } keywords[] = { + { "Key-Type", pKEYTYPE}, + { "Key-Length", pKEYLENGTH }, + { "Key-Curve", pKEYCURVE }, + { "Key-Usage", pKEYUSAGE }, + { "Subkey-Type", pSUBKEYTYPE }, + { "Subkey-Length", pSUBKEYLENGTH }, + { "Subkey-Curve", pSUBKEYCURVE }, + { "Subkey-Usage", pSUBKEYUSAGE }, + { "Name-Real", pNAMEREAL }, + { "Name-Email", pNAMEEMAIL }, + { "Name-Comment", pNAMECOMMENT }, + { "Expire-Date", pEXPIREDATE }, + { "Creation-Date", pCREATIONDATE }, + { "Passphrase", pPASSPHRASE }, + { "Preferences", pPREFERENCES }, + { "Revoker", pREVOKER }, + { "Handle", pHANDLE }, + { "Keyserver", pKEYSERVER }, + { "Keygrip", pKEYGRIP }, + { "Key-Grip", pKEYGRIP }, + { "Subkey-grip", pSUBKEYGRIP }, + { 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 ) ); + outctrl.pub.afx = new_armor_context (); + + if( !fname || !*fname) + fname = "-"; + + fp = iobuf_open (fname); + if (fp && is_secured_file (iobuf_get_fd (fp))) + { + iobuf_close (fp); + fp = NULL; + gpg_err_set_errno (EPERM); + } + if (!fp) { + log_error (_("can't open '%s': %s\n"), fname, strerror(errno) ); + return; + } + iobuf_ioctl (fp, IOBUF_IOCTL_NO_CACHE, 1, NULL); + + lnr = 0; + err = NULL; + para = NULL; + maxlen = 1024; + line = NULL; + nline = 0; + 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, "%ask-passphrase" ) ) + ; /* Dummy for backward compatibility. */ + else if( !ascii_strcasecmp( keyword, "%no-ask-passphrase" ) ) + ; /* Dummy for backward compatibility. */ + else if( !ascii_strcasecmp( keyword, "%no-protection" ) ) + outctrl.keygen_flags |= KEYGEN_FLAG_NO_PROTECTION; + else if( !ascii_strcasecmp( keyword, "%transient-key" ) ) + outctrl.keygen_flags |= KEYGEN_FLAG_TRANSIENT_KEY; + else if( !ascii_strcasecmp( keyword, "%commit" ) ) { + outctrl.lnr = lnr; + if (proc_parameter_file (ctrl, 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" ) ) { + /* Ignore this command. */ + } + 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 (ctrl, 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 (ctrl, 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 ); + + /* Must invalidate that ugly cache to actually close it. */ + if (outctrl.pub.fname) + iobuf_ioctl (NULL, IOBUF_IOCTL_INVALIDATE_CACHE, + 0, (char*)outctrl.pub.fname); + + xfree( outctrl.pub.fname ); + xfree( outctrl.pub.newfname ); + } + + xfree (line); + release_parameter_list( para ); + iobuf_close (fp); + release_armor_context (outctrl.pub.afx); +} + + +/* Helper for quick_generate_keypair. */ +static struct para_data_s * +quickgen_set_para (struct para_data_s *para, int for_subkey, + int algo, int nbits, const char *curve, unsigned int use, + const char *keygrip) +{ + struct para_data_s *r; + + r = xmalloc_clear (sizeof *r + 30); + r->key = for_subkey? pSUBKEYUSAGE : pKEYUSAGE; + if (use) + snprintf (r->u.value, 30, "%s%s%s%s", + (use & PUBKEY_USAGE_ENC)? "encr " : "", + (use & PUBKEY_USAGE_SIG)? "sign " : "", + (use & PUBKEY_USAGE_AUTH)? "auth " : "", + (use & PUBKEY_USAGE_CERT)? "cert " : ""); + else + strcpy (r->u.value, for_subkey ? "encr" : "sign"); + r->next = para; + para = r; + r = xmalloc_clear (sizeof *r + 20); + r->key = for_subkey? pSUBKEYTYPE : pKEYTYPE; + snprintf (r->u.value, 20, "%d", algo); + r->next = para; + para = r; + + if (keygrip) + { + r = xmalloc_clear (sizeof *r + strlen (keygrip)); + r->key = for_subkey? pSUBKEYGRIP : pKEYGRIP; + strcpy (r->u.value, keygrip); + r->next = para; + para = r; + } + else if (curve) + { + r = xmalloc_clear (sizeof *r + strlen (curve)); + r->key = for_subkey? pSUBKEYCURVE : pKEYCURVE; + strcpy (r->u.value, curve); + r->next = para; + para = r; + } + else + { + r = xmalloc_clear (sizeof *r + 20); + r->key = for_subkey? pSUBKEYLENGTH : pKEYLENGTH; + sprintf (r->u.value, "%u", nbits); + r->next = para; + para = r; + } + + return para; +} + + +/* + * Unattended generation of a standard key. + */ +void +quick_generate_keypair (ctrl_t ctrl, const char *uid, const char *algostr, + const char *usagestr, const char *expirestr) +{ + gpg_error_t err; + struct para_data_s *para = NULL; + struct para_data_s *r; + struct output_control_s outctrl; + int use_tty; + + memset (&outctrl, 0, sizeof outctrl); + + use_tty = (!opt.batch && !opt.answer_yes + && !*algostr && !*usagestr && !*expirestr + && !cpr_enabled () + && gnupg_isatty (fileno (stdin)) + && gnupg_isatty (fileno (stdout)) + && gnupg_isatty (fileno (stderr))); + + r = xmalloc_clear (sizeof *r + strlen (uid)); + r->key = pUSERID; + strcpy (r->u.value, uid); + r->next = para; + para = r; + + uid = trim_spaces (r->u.value); + if (!*uid || (!opt.allow_freeform_uid && !is_valid_user_id (uid))) + { + log_error (_("Key generation failed: %s\n"), + gpg_strerror (GPG_ERR_INV_USER_ID)); + goto leave; + } + + /* If gpg is directly used on the console ask whether a key with the + given user id shall really be created. */ + if (use_tty) + { + tty_printf (_("About to create a key for:\n \"%s\"\n\n"), uid); + if (!cpr_get_answer_is_yes_def ("quick_keygen.okay", + _("Continue? (Y/n) "), 1)) + goto leave; + } + + /* Check whether such a user ID already exists. */ + { + KEYDB_HANDLE kdbhd; + KEYDB_SEARCH_DESC desc; + + memset (&desc, 0, sizeof desc); + desc.mode = KEYDB_SEARCH_MODE_EXACT; + desc.u.name = uid; + + kdbhd = keydb_new (); + if (!kdbhd) + goto leave; + + err = keydb_search (kdbhd, &desc, 1, NULL); + keydb_release (kdbhd); + if (gpg_err_code (err) != GPG_ERR_NOT_FOUND) + { + log_info (_("A key for \"%s\" already exists\n"), uid); + if (opt.answer_yes) + ; + else if (!use_tty + || !cpr_get_answer_is_yes_def ("quick_keygen.force", + _("Create anyway? (y/N) "), 0)) + { + write_status_error ("genkey", gpg_error (304)); + log_inc_errorcount (); /* we used log_info */ + goto leave; + } + log_info (_("creating anyway\n")); + } + } + + if (!*expirestr || strcmp (expirestr, "-") == 0) + expirestr = default_expiration_interval; + + if ((!*algostr || !ascii_strcasecmp (algostr, "default") + || !ascii_strcasecmp (algostr, "future-default") + || !ascii_strcasecmp (algostr, "futuredefault") + || !ascii_strcasecmp (algostr, "card")) + && (!*usagestr || !ascii_strcasecmp (usagestr, "default") + || !strcmp (usagestr, "-"))) + { + /* Use default key parameters. */ + int algo, subalgo; + unsigned int size, subsize; + unsigned int keyuse, subkeyuse; + const char *curve, *subcurve; + char *keygrip, *subkeygrip; + + err = parse_key_parameter_string (ctrl, algostr, -1, 0, + &algo, &size, &keyuse, &curve, + &keygrip, + &subalgo, &subsize, &subkeyuse, + &subcurve, &subkeygrip); + if (err) + { + log_error (_("Key generation failed: %s\n"), gpg_strerror (err)); + goto leave; + } + + para = quickgen_set_para (para, 0, algo, size, curve, keyuse, + keygrip); + if (subalgo) + para = quickgen_set_para (para, 1, + subalgo, subsize, subcurve, subkeyuse, + subkeygrip); + if (*expirestr) + { + u32 expire; + + expire = parse_expire_string (expirestr); + if (expire == (u32)-1 ) + { + err = gpg_error (GPG_ERR_INV_VALUE); + log_error (_("Key generation failed: %s\n"), gpg_strerror (err)); + goto leave; + } + r = xmalloc_clear (sizeof *r + 20); + r->key = pKEYEXPIRE; + r->u.expire = expire; + r->next = para; + para = r; + } + + xfree (keygrip); + xfree (subkeygrip); + } + else + { + /* Extended unattended mode. Creates only the primary key. */ + int algo; + unsigned int use; + u32 expire; + unsigned int nbits; + const char *curve; + char *keygrip; + + err = parse_algo_usage_expire (ctrl, 0, algostr, usagestr, expirestr, + &algo, &use, &expire, &nbits, &curve, + &keygrip); + if (err) + { + log_error (_("Key generation failed: %s\n"), gpg_strerror (err) ); + goto leave; + } + + para = quickgen_set_para (para, 0, algo, nbits, curve, use, + keygrip); + r = xmalloc_clear (sizeof *r + 20); + r->key = pKEYEXPIRE; + r->u.expire = expire; + r->next = para; + para = r; + + xfree (keygrip); + } + + /* If the pinentry loopback mode is not and we have a static + passphrase (i.e. set with --passphrase{,-fd,-file} while in batch + mode), we use that passphrase for the new key. */ + if (opt.pinentry_mode != PINENTRY_MODE_LOOPBACK + && have_static_passphrase ()) + { + const char *s = get_static_passphrase (); + + r = xmalloc_clear (sizeof *r + strlen (s)); + r->key = pPASSPHRASE; + strcpy (r->u.value, s); + r->next = para; + para = r; + } + + proc_parameter_file (ctrl, para, "[internal]", &outctrl, 0); + + leave: + release_parameter_list (para); +} + + +/* + * Generate a keypair (fname is only used in batch mode) If + * CARD_SERIALNO is not NULL the function will create the keys on an + * OpenPGP Card. If CARD_BACKUP_KEY has been set and CARD_SERIALNO is + * NOT NULL, the encryption key for the card is generated on the host, + * imported to the card and a backup file created by gpg-agent. If + * FULL is not set only the basic prompts are used (except for batch + * mode). + */ +void +generate_keypair (ctrl_t ctrl, int full, const char *fname, + const char *card_serialno, int card_backup_key) +{ + gpg_error_t err; + unsigned int nbits; + char *uid = NULL; + 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; + +#ifndef ENABLE_CARD_SUPPORT + (void)card_backup_key; +#endif + + memset( &outctrl, 0, sizeof( outctrl ) ); + + if (opt.batch && card_serialno) + { + /* We don't yet support unattended key generation with a card + * serial number. */ + log_error (_("can't do this in batch mode\n")); + print_further_info ("key generation with card serial number"); + return; + } + + if (opt.batch) + { + read_parameter_file (ctrl, fname); + return; + } + + if (card_serialno) + { +#ifdef ENABLE_CARD_SUPPORT + struct agent_card_info_s info; + + memset (&info, 0, sizeof (info)); + err = agent_scd_getattr ("KEY-ATTR", &info); + if (err) + { + log_error (_("error getting current key info: %s\n"), + gpg_strerror (err)); + return; + } + + r = xcalloc (1, sizeof *r + strlen (card_serialno) ); + r->key = pSERIALNO; + strcpy( r->u.value, card_serialno); + r->next = para; + para = r; + + r = xcalloc (1, sizeof *r + 20 ); + r->key = pKEYTYPE; + sprintf( r->u.value, "%d", info.key_attr[0].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", info.key_attr[1].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; + if (info.key_attr[1].algo == PUBKEY_ALGO_RSA) + { + r = xcalloc (1, sizeof *r + 20 ); + r->key = pSUBKEYLENGTH; + sprintf( r->u.value, "%u", info.key_attr[1].nbits); + r->next = para; + para = r; + } + else if (info.key_attr[1].algo == PUBKEY_ALGO_ECDSA + || info.key_attr[1].algo == PUBKEY_ALGO_EDDSA + || info.key_attr[1].algo == PUBKEY_ALGO_ECDH) + { + r = xcalloc (1, sizeof *r + strlen (info.key_attr[1].curve)); + r->key = pSUBKEYCURVE; + strcpy (r->u.value, info.key_attr[1].curve); + r->next = para; + para = r; + } + + r = xcalloc (1, sizeof *r + 20 ); + r->key = pAUTHKEYTYPE; + sprintf( r->u.value, "%d", info.key_attr[2].algo ); + r->next = para; + para = r; + + if (card_backup_key) + { + r = xcalloc (1, sizeof *r + 1); + r->key = pCARDBACKUPKEY; + strcpy (r->u.value, "1"); + r->next = para; + para = r; + } +#endif /*ENABLE_CARD_SUPPORT*/ + } + else if (full) /* Full featured key generation. */ + { + int subkey_algo; + char *key_from_hexgrip = NULL; + + algo = ask_algo (ctrl, 0, &subkey_algo, &use, &key_from_hexgrip); + if (key_from_hexgrip) + { + 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; + } + + r = xmalloc_clear( sizeof *r + 40 ); + r->key = pKEYGRIP; + strcpy (r->u.value, key_from_hexgrip); + r->next = para; + para = r; + + xfree (key_from_hexgrip); + } + else + { + const char *curve = NULL; + + if (subkey_algo) + { + /* Create primary and subkey at once. */ + both = 1; + if (algo == PUBKEY_ALGO_ECDSA + || algo == PUBKEY_ALGO_EDDSA + || algo == PUBKEY_ALGO_ECDH) + { + curve = ask_curve (&algo, &subkey_algo, NULL); + r = xmalloc_clear( sizeof *r + 20 ); + r->key = pKEYTYPE; + sprintf( r->u.value, "%d", algo); + r->next = para; + para = r; + nbits = 0; + r = xmalloc_clear (sizeof *r + strlen (curve)); + r->key = pKEYCURVE; + strcpy (r->u.value, curve); + 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; + nbits = ask_keysize (algo, 0); + 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; + + r = xmalloc_clear( sizeof *r + 20 ); + r->key = pSUBKEYTYPE; + sprintf( r->u.value, "%d", subkey_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; + + if (algo == PUBKEY_ALGO_ECDSA + || algo == PUBKEY_ALGO_EDDSA + || algo == PUBKEY_ALGO_ECDH) + { + if (algo == PUBKEY_ALGO_EDDSA + && subkey_algo == PUBKEY_ALGO_ECDH) + { + /* Need to switch to a different curve for the + encryption key. */ + curve = "Curve25519"; + } + r = xmalloc_clear (sizeof *r + strlen (curve)); + r->key = pSUBKEYCURVE; + strcpy (r->u.value, curve); + r->next = para; + para = r; + } + } + else /* Create only a single key. */ + { + /* For ECC we need to ask for the curve before storing the + algo because ask_curve may change the algo. */ + if (algo == PUBKEY_ALGO_ECDSA + || algo == PUBKEY_ALGO_EDDSA + || algo == PUBKEY_ALGO_ECDH) + { + curve = ask_curve (&algo, NULL, NULL); + r = xmalloc_clear (sizeof *r + strlen (curve)); + r->key = pKEYCURVE; + strcpy (r->u.value, curve); + r->next = para; + para = r; + } + + 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 = 0; + } + + if (algo == PUBKEY_ALGO_ECDSA + || algo == PUBKEY_ALGO_EDDSA + || algo == PUBKEY_ALGO_ECDH) + { + /* The curve has already been set. */ + } + else + { + nbits = ask_keysize (both? subkey_algo : algo, nbits); + r = xmalloc_clear( sizeof *r + 20 ); + r->key = both? pSUBKEYLENGTH : pKEYLENGTH; + sprintf( r->u.value, "%u", nbits); + r->next = para; + para = r; + } + } + } + else /* Default key generation. */ + { + int subalgo; + unsigned int size, subsize; + unsigned int keyuse, subkeyuse; + const char *curve, *subcurve; + char *keygrip, *subkeygrip; + + tty_printf ( _("Note: Use \"%s %s\"" + " for a full featured key generation dialog.\n"), +#if USE_GPG2_HACK + GPG_NAME "2" +#else + GPG_NAME +#endif + , "--full-generate-key" ); + + err = parse_key_parameter_string (ctrl, NULL, -1, 0, + &algo, &size, &keyuse, &curve, + &keygrip, + &subalgo, &subsize, + &subkeyuse, &subcurve, + &subkeygrip); + if (err) + { + log_error (_("Key generation failed: %s\n"), gpg_strerror (err)); + return; + } + para = quickgen_set_para (para, 0, + algo, size, curve, keyuse, + keygrip); + if (subalgo) + para = quickgen_set_para (para, 1, + subalgo, subsize, subcurve, subkeyuse, + subkeygrip); + + xfree (keygrip); + xfree (subkeygrip); + } + + + expire = full? ask_expire_interval (0, NULL) + : parse_expire_string (default_expiration_interval); + r = xcalloc (1, sizeof *r + 20); + r->key = pKEYEXPIRE; + r->u.expire = expire; + r->next = para; + para = r; + r = xcalloc (1, sizeof *r + 20); + r->key = pSUBKEYEXPIRE; + r->u.expire = expire; + r->next = para; + para = r; + + uid = ask_user_id (0, full, NULL); + if (!uid) + { + log_error(_("Key generation canceled.\n")); + release_parameter_list( para ); + return; + } + r = xcalloc (1, sizeof *r + strlen (uid)); + r->key = pUSERID; + strcpy (r->u.value, uid); + r->next = para; + para = r; + + proc_parameter_file (ctrl, para, "[internal]", &outctrl, !!card_serialno); + release_parameter_list (para); +} + + +/* 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); +} + + +/* Write the *protected* secret key to the file. */ +static gpg_error_t +card_write_key_to_backup_file (PKT_public_key *sk, const char *backup_dir) +{ + gpg_error_t err = 0; + int rc; + char keyid_buffer[2 * 8 + 1]; + char name_buffer[50]; + char *fname; + IOBUF fp; + mode_t oldmask; + PACKET *pkt = NULL; + + format_keyid (pk_keyid (sk), KF_LONG, keyid_buffer, sizeof (keyid_buffer)); + snprintf (name_buffer, sizeof name_buffer, "sk_%s.gpg", keyid_buffer); + + fname = make_filename (backup_dir, name_buffer, NULL); + /* Note that the umask call is not anymore needed because + iobuf_create now takes care of it. However, it does not harm + and thus we keep it. */ + oldmask = umask (077); + if (is_secured_filename (fname)) + { + fp = NULL; + gpg_err_set_errno (EPERM); + } + else + fp = iobuf_create (fname, 1); + umask (oldmask); + if (!fp) + { + err = gpg_error_from_syserror (); + log_error (_("can't create backup file '%s': %s\n"), fname, strerror (errno) ); + goto leave; + } + + pkt = xcalloc (1, sizeof *pkt); + pkt->pkttype = PKT_SECRET_KEY; + pkt->pkt.secret_key = sk; + + rc = build_packet (fp, pkt); + if (rc) + { + log_error ("build packet failed: %s\n", gpg_strerror (rc)); + iobuf_cancel (fp); + } + else + { + char *fprbuf; + + iobuf_close (fp); + iobuf_ioctl (NULL, IOBUF_IOCTL_INVALIDATE_CACHE, 0, (char*)fname); + log_info (_("Note: backup of card key saved to '%s'\n"), fname); + + fprbuf = hexfingerprint (sk, NULL, 0); + if (!fprbuf) + { + err = gpg_error_from_syserror (); + goto leave; + } + write_status_text_and_buffer (STATUS_BACKUP_KEY_CREATED, fprbuf, + fname, strlen (fname), 0); + xfree (fprbuf); + } + + leave: + xfree (pkt); + xfree (fname); + return err; +} + + +/* Store key to card and make a backup file in OpenPGP format. */ +static gpg_error_t +card_store_key_with_backup (ctrl_t ctrl, PKT_public_key *sub_psk, + const char *backup_dir) +{ + PKT_public_key *sk; + gnupg_isotime_t timestamp; + gpg_error_t err; + char *hexgrip; + int rc; + struct agent_card_info_s info; + gcry_cipher_hd_t cipherhd = NULL; + char *cache_nonce = NULL; + void *kek = NULL; + size_t keklen; + + sk = copy_public_key (NULL, sub_psk); + if (!sk) + return gpg_error_from_syserror (); + + epoch2isotime (timestamp, (time_t)sk->timestamp); + err = hexkeygrip_from_pk (sk, &hexgrip); + if (err) + return err; + + memset(&info, 0, sizeof (info)); + rc = agent_scd_getattr ("SERIALNO", &info); + if (rc) + return (gpg_error_t)rc; + + rc = agent_keytocard (hexgrip, 2, 1, info.serialno, timestamp); + xfree (info.serialno); + if (rc) + { + err = (gpg_error_t)rc; + goto leave; + } + + err = agent_keywrap_key (ctrl, 1, &kek, &keklen); + if (err) + { + log_error ("error getting the KEK: %s\n", gpg_strerror (err)); + goto leave; + } + + err = gcry_cipher_open (&cipherhd, GCRY_CIPHER_AES128, + GCRY_CIPHER_MODE_AESWRAP, 0); + if (!err) + err = gcry_cipher_setkey (cipherhd, kek, keklen); + if (err) + { + log_error ("error setting up an encryption context: %s\n", + gpg_strerror (err)); + goto leave; + } + + err = receive_seckey_from_agent (ctrl, cipherhd, 0, + &cache_nonce, hexgrip, sk); + if (err) + { + log_error ("error getting secret key from agent: %s\n", + gpg_strerror (err)); + goto leave; + } + + err = card_write_key_to_backup_file (sk, backup_dir); + if (err) + log_error ("writing card key to backup file: %s\n", gpg_strerror (err)); + else + /* Remove secret key data in agent side. */ + agent_scd_learn (NULL, 1); + + leave: + xfree (cache_nonce); + gcry_cipher_close (cipherhd); + xfree (kek); + xfree (hexgrip); + free_public_key (sk); + return err; +} + + +static void +do_generate_keypair (ctrl_t ctrl, struct para_data_s *para, + struct output_control_s *outctrl, int card) +{ + gpg_error_t err; + KBNODE pub_root = NULL; + const char *s; + PKT_public_key *pri_psk = NULL; + PKT_public_key *sub_psk = NULL; + struct revocation_key *revkey; + int did_sub = 0; + u32 timestamp; + char *cache_nonce = NULL; + int algo; + u32 expire; + const char *key_from_hexgrip = NULL; + + 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, IOBUF_IOCTL_INVALIDATE_CACHE, + 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; + gpg_err_set_errno (EPERM); + } + else + outctrl->pub.stream = iobuf_create (outctrl->pub.fname, 0); + 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; + push_armor_filter (outctrl->pub.afx, outctrl->pub.stream); + } + } + log_assert( outctrl->pub.stream ); + if (opt.verbose) + log_info (_("writing public key to '%s'\n"), outctrl->pub.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); + + timestamp = get_parameter_u32 (para, pKEYCREATIONDATE); + if (!timestamp) + timestamp = make_timestamp (); + + /* Note that, depending on the backend (i.e. the used scdaemon + version), the card key generation may update TIMESTAMP for each + key. Thus we need to pass TIMESTAMP to all signing function to + make sure that the binding signature is done using the timestamp + of the corresponding (sub)key and not that of the primary key. + An alternative implementation could tell the signing function the + node of the subkey but that is more work than just to pass the + current timestamp. */ + + algo = get_parameter_algo (ctrl, para, pKEYTYPE, NULL ); + expire = get_parameter_u32( para, pKEYEXPIRE ); + key_from_hexgrip = get_parameter_value (para, pKEYGRIP); + if (key_from_hexgrip) + err = do_create_from_keygrip (ctrl, algo, key_from_hexgrip, + pub_root, timestamp, expire, 0); + else if (!card) + err = do_create (algo, + get_parameter_uint( para, pKEYLENGTH ), + get_parameter_value (para, pKEYCURVE), + pub_root, + timestamp, + expire, 0, + outctrl->keygen_flags, + get_parameter_passphrase (para), + &cache_nonce, NULL); + else + err = gen_card_key (1, algo, + 1, pub_root, ×tamp, + expire); + + /* Get the pointer to the generated public key packet. */ + if (!err) + { + pri_psk = pub_root->next->pkt->pkt.public_key; + log_assert (pri_psk); + + /* Make sure a few fields are correctly set up before going + further. */ + pri_psk->flags.primary = 1; + keyid_from_pk (pri_psk, NULL); + /* We don't use pk_keyid to get keyid, because it also asserts + that main_keyid is set! */ + keyid_copy (pri_psk->main_keyid, pri_psk->keyid); + } + + if (!err && (revkey = get_parameter_revkey (para, pREVOKER))) + err = write_direct_sig (ctrl, pub_root, pri_psk, + revkey, timestamp, cache_nonce); + + if (!err && (s = get_parameter_value (para, pUSERID))) + { + err = write_uid (pub_root, s ); + if (!err) + err = write_selfsigs (ctrl, pub_root, pri_psk, + get_parameter_uint (para, pKEYUSAGE), timestamp, + cache_nonce); + } + + /* 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 (!err && card && get_parameter (para, pAUTHKEYTYPE)) + { + err = gen_card_key (3, get_parameter_algo (ctrl, para, + pAUTHKEYTYPE, NULL ), + 0, pub_root, ×tamp, expire); + if (!err) + err = write_keybinding (ctrl, pub_root, pri_psk, NULL, + PUBKEY_USAGE_AUTH, timestamp, cache_nonce); + } + + if (!err && get_parameter (para, pSUBKEYTYPE)) + { + int subkey_algo = get_parameter_algo (ctrl, para, pSUBKEYTYPE, NULL); + + s = NULL; + key_from_hexgrip = get_parameter_value (para, pSUBKEYGRIP); + if (key_from_hexgrip) + err = do_create_from_keygrip (ctrl, subkey_algo, key_from_hexgrip, + pub_root, timestamp, + get_parameter_u32 (para, pSUBKEYEXPIRE), + 1); + else if (!card || (s = get_parameter_value (para, pCARDBACKUPKEY))) + { + err = do_create (subkey_algo, + get_parameter_uint (para, pSUBKEYLENGTH), + get_parameter_value (para, pSUBKEYCURVE), + pub_root, + timestamp, + get_parameter_u32 (para, pSUBKEYEXPIRE), 1, + s ? KEYGEN_FLAG_NO_PROTECTION : outctrl->keygen_flags, + get_parameter_passphrase (para), + &cache_nonce, NULL); + /* Get the pointer to the generated public subkey packet. */ + if (!err) + { + kbnode_t node; + + for (node = pub_root; node; node = node->next) + if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY) + sub_psk = node->pkt->pkt.public_key; + log_assert (sub_psk); + + if (s) + err = card_store_key_with_backup (ctrl, + sub_psk, gnupg_homedir ()); + } + } + else + { + err = gen_card_key (2, subkey_algo, 0, pub_root, ×tamp, expire); + } + + if (!err) + err = write_keybinding (ctrl, pub_root, pri_psk, sub_psk, + get_parameter_uint (para, pSUBKEYUSAGE), + timestamp, cache_nonce); + did_sub = 1; + } + + if (!err && outctrl->use_files) /* Direct write to specified files. */ + { + err = write_keyblock (outctrl->pub.stream, pub_root); + if (err) + log_error ("can't write public key: %s\n", gpg_strerror (err)); + } + else if (!err) /* Write to the standard keyrings. */ + { + KEYDB_HANDLE pub_hd; + + pub_hd = keydb_new (); + if (!pub_hd) + err = gpg_error_from_syserror (); + else + { + err = keydb_locate_writable (pub_hd); + if (err) + log_error (_("no writable public keyring found: %s\n"), + gpg_strerror (err)); + } + + if (!err && opt.verbose) + { + log_info (_("writing public key to '%s'\n"), + keydb_get_resource_name (pub_hd)); + } + + if (!err) + { + err = keydb_insert_keyblock (pub_hd, pub_root); + if (err) + log_error (_("error writing public keyring '%s': %s\n"), + keydb_get_resource_name (pub_hd), gpg_strerror (err)); + } + + keydb_release (pub_hd); + + if (!err) + { + int no_enc_rsa; + PKT_public_key *pk; + + no_enc_rsa = ((get_parameter_algo (ctrl, para, pKEYTYPE, NULL) + == PUBKEY_ALGO_RSA) + && get_parameter_uint (para, pKEYUSAGE) + && !((get_parameter_uint (para, pKEYUSAGE) + & PUBKEY_USAGE_ENC)) ); + + pk = find_kbnode (pub_root, PKT_PUBLIC_KEY)->pkt->pkt.public_key; + + update_ownertrust (ctrl, pk, + ((get_ownertrust (ctrl, pk) & ~TRUST_MASK) + | TRUST_ULTIMATE )); + + gen_standard_revoke (ctrl, pk, cache_nonce); + + /* Get rid of the first empty packet. */ + commit_kbnode (&pub_root); + + if (!opt.batch) + { + tty_printf (_("public and secret key created and signed.\n") ); + tty_printf ("\n"); + merge_keys_and_selfsig (ctrl, pub_root); + + list_keyblock_direct (ctrl, pub_root, 0, 1, + opt.fingerprint || opt.with_fingerprint, + 1); + } + + + if (!opt.batch + && (get_parameter_algo (ctrl, para, + pKEYTYPE, NULL) == 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 (err) + { + if (opt.batch) + log_error ("key generation failed: %s\n", gpg_strerror (err) ); + else + tty_printf (_("Key generation failed: %s\n"), gpg_strerror (err) ); + write_status_error (card? "card_key_generate":"key_generate", err); + 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); + xfree (cache_nonce); +} + + +static gpg_error_t +parse_algo_usage_expire (ctrl_t ctrl, int for_subkey, + const char *algostr, const char *usagestr, + const char *expirestr, + int *r_algo, unsigned int *r_usage, u32 *r_expire, + unsigned int *r_nbits, const char **r_curve, + char **r_keygrip) +{ + gpg_error_t err; + int algo; + unsigned int use, nbits; + u32 expire; + int wantuse; + const char *curve = NULL; + + *r_curve = NULL; + if (r_keygrip) + *r_keygrip = NULL; + + nbits = 0; + + /* Parse the algo string. */ + if (algostr && *algostr == '&' && strlen (algostr) == 41) + { + /* Take algo from existing key. */ + algo = check_keygrip (ctrl, algostr+1); + /* FIXME: We need the curve name as well. */ + return gpg_error (GPG_ERR_NOT_IMPLEMENTED); + } + + err = parse_key_parameter_string (ctrl, algostr, for_subkey? 1 : 0, + usagestr? parse_usagestr (usagestr):0, + &algo, &nbits, &use, &curve, + r_keygrip, + NULL, NULL, NULL, NULL, NULL); + if (err) + { + if (r_keygrip) + { + xfree (*r_keygrip); + *r_keygrip = NULL; + } + return err; + } + + /* Parse the usage string. */ + if (!usagestr || !*usagestr + || !ascii_strcasecmp (usagestr, "default") || !strcmp (usagestr, "-")) + ; /* Keep usage from parse_key_parameter_string. */ + else if ((wantuse = parse_usagestr (usagestr)) != -1) + use = wantuse; + else + { + if (r_keygrip) + { + xfree (*r_keygrip); + *r_keygrip = NULL; + } + return gpg_error (GPG_ERR_INV_VALUE); + } + + /* Make sure a primary key has the CERT usage. */ + if (!for_subkey) + use |= PUBKEY_USAGE_CERT; + + /* Check that usage is possible. NB: We have the same check in + * parse_key_parameter_string but need it here again in case the + * separate usage value has been given. */ + if (/**/((use & (PUBKEY_USAGE_SIG|PUBKEY_USAGE_AUTH|PUBKEY_USAGE_CERT)) + && !pubkey_get_nsig (algo)) + || ((use & PUBKEY_USAGE_ENC) + && !pubkey_get_nenc (algo)) + || (for_subkey && (use & PUBKEY_USAGE_CERT))) + { + if (r_keygrip) + { + xfree (*r_keygrip); + *r_keygrip = NULL; + } + return gpg_error (GPG_ERR_WRONG_KEY_USAGE); + } + + /* Parse the expire string. */ + expire = parse_expire_string (expirestr); + if (expire == (u32)-1 ) + { + if (r_keygrip) + { + xfree (*r_keygrip); + *r_keygrip = NULL; + } + return gpg_error (GPG_ERR_INV_VALUE); + } + + if (curve) + *r_curve = curve; + *r_algo = algo; + *r_usage = use; + *r_expire = expire; + *r_nbits = nbits; + return 0; +} + + +/* Add a new subkey to an existing key. Returns 0 if a new key has + been generated and put into the keyblocks. If any of ALGOSTR, + USAGESTR, or EXPIRESTR is NULL interactive mode is used. */ +gpg_error_t +generate_subkeypair (ctrl_t ctrl, kbnode_t keyblock, const char *algostr, + const char *usagestr, const char *expirestr) +{ + gpg_error_t err = 0; + int interactive; + kbnode_t node; + PKT_public_key *pri_psk = NULL; + PKT_public_key *sub_psk = NULL; + int algo; + unsigned int use; + u32 expire; + unsigned int nbits = 0; + const char *curve = NULL; + u32 cur_time; + char *key_from_hexgrip = NULL; + char *hexgrip = NULL; + char *serialno = NULL; + char *cache_nonce = NULL; + char *passwd_nonce = NULL; + + interactive = (!algostr || !usagestr || !expirestr); + + /* Break out the primary key. */ + node = find_kbnode (keyblock, PKT_PUBLIC_KEY); + if (!node) + { + log_error ("Oops; primary key missing in keyblock!\n"); + err = gpg_error (GPG_ERR_BUG); + goto leave; + } + pri_psk = node->pkt->pkt.public_key; + + cur_time = make_timestamp (); + + if (pri_psk->timestamp > cur_time) + { + ulong d = pri_psk->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) + { + err = gpg_error (GPG_ERR_TIME_CONFLICT); + goto leave; + } + } + + if (pri_psk->version < 4) + { + log_info (_("Note: creating subkeys for v3 keys " + "is not OpenPGP compliant\n")); + err = gpg_error (GPG_ERR_CONFLICT); + goto leave; + } + + err = hexkeygrip_from_pk (pri_psk, &hexgrip); + if (err) + goto leave; + if (agent_get_keyinfo (NULL, hexgrip, &serialno, NULL)) + { + if (interactive) + tty_printf (_("Secret parts of primary key are not available.\n")); + else + log_info ( _("Secret parts of primary key are not available.\n")); + err = gpg_error (GPG_ERR_NO_SECKEY); + goto leave; + } + if (serialno) + { + if (interactive) + tty_printf (_("Secret parts of primary key are stored on-card.\n")); + else + log_info ( _("Secret parts of primary key are stored on-card.\n")); + } + + if (interactive) + { + algo = ask_algo (ctrl, 1, NULL, &use, &key_from_hexgrip); + log_assert (algo); + + if (key_from_hexgrip) + nbits = 0; + else if (algo == PUBKEY_ALGO_ECDSA + || algo == PUBKEY_ALGO_EDDSA + || algo == PUBKEY_ALGO_ECDH) + curve = ask_curve (&algo, NULL, NULL); + else + nbits = ask_keysize (algo, 0); + + expire = ask_expire_interval (0, NULL); + if (!cpr_enabled() && !cpr_get_answer_is_yes("keygen.sub.okay", + _("Really create? (y/N) "))) + { + err = gpg_error (GPG_ERR_CANCELED); + goto leave; + } + } + else /* Unattended mode. */ + { + err = parse_algo_usage_expire (ctrl, 1, algostr, usagestr, expirestr, + &algo, &use, &expire, &nbits, &curve, + &key_from_hexgrip); + if (err) + goto leave; + } + + /* Verify the passphrase now so that we get a cache item for the + * primary key passphrase. The agent also returns a passphrase + * nonce, which we can use to set the passphrase for the subkey to + * that of the primary key. */ + { + char *desc = gpg_format_keydesc (ctrl, pri_psk, FORMAT_KEYDESC_NORMAL, 1); + err = agent_passwd (ctrl, hexgrip, desc, 1 /*=verify*/, + &cache_nonce, &passwd_nonce); + xfree (desc); + if (gpg_err_code (err) == GPG_ERR_NOT_IMPLEMENTED + && gpg_err_source (err) == GPG_ERR_SOURCE_GPGAGENT) + err = 0; /* Very likely that the key is on a card. */ + if (err) + goto leave; + } + + /* Start creation. */ + if (key_from_hexgrip) + { + err = do_create_from_keygrip (ctrl, algo, key_from_hexgrip, + keyblock, cur_time, expire, 1); + } + else + { + const char *passwd; + + /* If the pinentry loopback mode is not and we have a static + passphrase (i.e. set with --passphrase{,-fd,-file} while in batch + mode), we use that passphrase for the new subkey. */ + if (opt.pinentry_mode != PINENTRY_MODE_LOOPBACK + && have_static_passphrase ()) + passwd = get_static_passphrase (); + else + passwd = NULL; + + err = do_create (algo, nbits, curve, + keyblock, cur_time, expire, 1, 0, + passwd, &cache_nonce, &passwd_nonce); + } + if (err) + goto leave; + + /* Get the pointer to the generated public subkey packet. */ + for (node = keyblock; node; node = node->next) + if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY) + sub_psk = node->pkt->pkt.public_key; + + /* Write the binding signature. */ + err = write_keybinding (ctrl, keyblock, pri_psk, sub_psk, use, cur_time, + cache_nonce); + if (err) + goto leave; + + print_status_key_created ('S', sub_psk, NULL); + + + leave: + xfree (key_from_hexgrip); + xfree (hexgrip); + xfree (serialno); + xfree (cache_nonce); + xfree (passwd_nonce); + if (err) + log_error (_("Key generation failed: %s\n"), gpg_strerror (err) ); + return err; +} + + +#ifdef ENABLE_CARD_SUPPORT +/* Generate a subkey on a card. */ +gpg_error_t +generate_card_subkeypair (ctrl_t ctrl, kbnode_t pub_keyblock, + int keyno, const char *serialno) +{ + gpg_error_t err = 0; + kbnode_t node; + PKT_public_key *pri_pk = NULL; + unsigned int use; + u32 expire; + u32 cur_time; + struct para_data_s *para = NULL; + PKT_public_key *sub_pk = NULL; + int algo; + struct agent_card_info_s info; + + log_assert (keyno >= 1 && keyno <= 3); + + memset (&info, 0, sizeof (info)); + err = agent_scd_getattr ("KEY-ATTR", &info); + if (err) + { + log_error (_("error getting current key info: %s\n"), gpg_strerror (err)); + return err; + } + algo = info.key_attr[keyno-1].algo; + + para = xtrycalloc (1, sizeof *para + strlen (serialno) ); + if (!para) + { + err = gpg_error_from_syserror (); + goto leave; + } + para->key = pSERIALNO; + strcpy (para->u.value, serialno); + + /* Break out the primary secret key */ + node = find_kbnode (pub_keyblock, PKT_PUBLIC_KEY); + if (!node) + { + log_error ("Oops; public key lost!\n"); + err = gpg_error (GPG_ERR_INTERNAL); + goto leave; + } + pri_pk = node->pkt->pkt.public_key; + + cur_time = make_timestamp(); + if (pri_pk->timestamp > cur_time) + { + ulong d = pri_pk->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) + { + err = gpg_error (GPG_ERR_TIME_CONFLICT); + goto leave; + } + } + + if (pri_pk->version < 4) + { + log_info (_("Note: creating subkeys for v3 keys " + "is not OpenPGP compliant\n")); + err = gpg_error (GPG_ERR_NOT_SUPPORTED); + goto leave; + } + + 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) "))) + { + err = gpg_error (GPG_ERR_CANCELED); + goto leave; + } + + /* Note, that depending on the backend, the card key generation may + update CUR_TIME. */ + err = gen_card_key (keyno, algo, 0, pub_keyblock, &cur_time, expire); + /* Get the pointer to the generated public subkey packet. */ + if (!err) + { + for (node = pub_keyblock; node; node = node->next) + if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY) + sub_pk = node->pkt->pkt.public_key; + log_assert (sub_pk); + err = write_keybinding (ctrl, pub_keyblock, pri_pk, sub_pk, + use, cur_time, NULL); + } + + leave: + if (err) + log_error (_("Key generation failed: %s\n"), gpg_strerror (err) ); + else + print_status_key_created ('S', sub_pk, NULL); + release_parameter_list (para); + return err; +} +#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, gpg_strerror (rc) ); + return rc; + } + } + } + + return 0; +} + + +/* Note that timestamp is an in/out arg. */ +static gpg_error_t +gen_card_key (int keyno, int algo, int is_primary, kbnode_t pub_root, + u32 *timestamp, u32 expireval) +{ +#ifdef ENABLE_CARD_SUPPORT + gpg_error_t err; + PACKET *pkt; + PKT_public_key *pk; + char keyid[10]; + unsigned char *public; + gcry_sexp_t s_key; + + snprintf (keyid, DIM(keyid), "OPENPGP.%d", keyno); + + pk = xtrycalloc (1, sizeof *pk ); + if (!pk) + return gpg_error_from_syserror (); + pkt = xtrycalloc (1, sizeof *pkt); + if (!pkt) + { + xfree (pk); + return gpg_error_from_syserror (); + } + + /* Note: SCD knows the serialnumber, thus there is no point in passing it. */ + err = agent_scd_genkey (keyno, 1, timestamp); + /* The code below is not used because we force creation of + * the a card key (3rd arg). + * 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 (keyno, 1, timestamp); + * } + */ + if (err) + { + log_error ("key generation failed: %s\n", gpg_strerror (err)); + xfree (pkt); + xfree (pk); + return err; + } + + /* Send the READKEY command so that the agent creates a shadow key for + card key. We need to do that now so that we are able to create + the self-signatures. */ + err = agent_readkey (NULL, 1, keyid, &public); + if (err) + return err; + err = gcry_sexp_sscan (&s_key, NULL, public, + gcry_sexp_canon_len (public, 0, NULL, NULL)); + xfree (public); + if (err) + return err; + + if (algo == PUBKEY_ALGO_RSA) + err = key_from_sexp (pk->pkey, s_key, "public-key", "ne"); + else if (algo == PUBKEY_ALGO_ECDSA + || algo == PUBKEY_ALGO_EDDSA + || algo == PUBKEY_ALGO_ECDH ) + err = ecckey_from_sexp (pk->pkey, s_key, algo); + else + err = gpg_error (GPG_ERR_PUBKEY_ALGO); + gcry_sexp_release (s_key); + + if (err) + { + log_error ("key_from_sexp failed: %s\n", gpg_strerror (err) ); + free_public_key (pk); + return err; + } + + pk->timestamp = *timestamp; + pk->version = 4; + if (expireval) + pk->expiredate = pk->timestamp + expireval; + pk->pubkey_algo = algo; + + pkt->pkttype = is_primary ? PKT_PUBLIC_KEY : PKT_PUBLIC_SUBKEY; + pkt->pkt.public_key = pk; + add_kbnode (pub_root, new_kbnode (pkt)); + + return 0; +#else + (void)keyno; + (void)is_primary; + (void)pub_root; + (void)timestamp; + (void)expireval; + return gpg_error (GPG_ERR_NOT_SUPPORTED); +#endif /*!ENABLE_CARD_SUPPORT*/ +} diff --git a/g10/keyid.c b/g10/keyid.c new file mode 100644 index 0000000..69d85da --- /dev/null +++ b/g10/keyid.c @@ -0,0 +1,987 @@ +/* keyid.c - key ID and fingerprint handling + * Copyright (C) 1998, 1999, 2000, 2001, 2003, + * 2004, 2006, 2010 Free Software Foundation, Inc. + * Copyright (C) 2014 Werner Koch + * Copyright (C) 2016 g10 Code GmbH + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include +#include +#include +#include +#include +#include + +#include "gpg.h" +#include "../common/util.h" +#include "main.h" +#include "packet.h" +#include "options.h" +#include "keydb.h" +#include "../common/i18n.h" +#include "rmd160.h" +#include "../common/host2net.h" + + +#define KEYID_STR_SIZE 19 + +#ifdef HAVE_UNSIGNED_TIME_T +# define IS_INVALID_TIME_T(a) ((a) == (time_t)(-1)) +#else + /* Error or 32 bit time_t and value after 2038-01-19. */ +# define IS_INVALID_TIME_T(a) ((a) < 0) +#endif + + +/* Return a letter describing the public key algorithms. */ +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' ; + case PUBKEY_ALGO_ECDH: return 'e' ; /* ECC DH (encrypt only) */ + case PUBKEY_ALGO_ECDSA: return 'E' ; /* ECC DSA (sign only) */ + case PUBKEY_ALGO_EDDSA: return 'E' ; /* ECC EdDSA (sign only) */ + default: return '?'; + } +} + +/* Return a string describing the public key algorithm and the + keysize. For elliptic curves the functions prints the name of the + curve because the keysize is a property of the curve. The string + is copied to the supplied buffer up a length of BUFSIZE-1. + Examples for the output are: + + "rsa2048" - RSA with 2048 bit + "elg1024" - Elgamal with 1024 bit + "ed25519" - ECC using the curve Ed25519. + "E_1.2.3.4" - ECC using the unsupported curve with OID "1.2.3.4". + "E_1.3.6.1.4.1.11591.2.12242973" ECC with a bogus OID. + "unknown_N" - Unknown OpenPGP algorithm N. + + If the option --legacy-list-mode is active, the output use the + legacy format: + + "2048R" - RSA with 2048 bit + "1024g" - Elgamal with 1024 bit + "256E" - ECDSA using a curve with 256 bit + + The macro PUBKEY_STRING_SIZE may be used to allocate a buffer with + a suitable size.*/ +char * +pubkey_string (PKT_public_key *pk, char *buffer, size_t bufsize) +{ + const char *prefix = NULL; + + if (opt.legacy_list_mode) + { + snprintf (buffer, bufsize, "%4u%c", + nbits_from_pk (pk), pubkey_letter (pk->pubkey_algo)); + return buffer; + } + + switch (pk->pubkey_algo) + { + case PUBKEY_ALGO_RSA: + case PUBKEY_ALGO_RSA_E: + case PUBKEY_ALGO_RSA_S: prefix = "rsa"; break; + case PUBKEY_ALGO_ELGAMAL_E: prefix = "elg"; break; + case PUBKEY_ALGO_DSA: prefix = "dsa"; break; + case PUBKEY_ALGO_ELGAMAL: prefix = "xxx"; break; + case PUBKEY_ALGO_ECDH: + case PUBKEY_ALGO_ECDSA: + case PUBKEY_ALGO_EDDSA: prefix = ""; break; + } + + if (prefix && *prefix) + snprintf (buffer, bufsize, "%s%u", prefix, nbits_from_pk (pk)); + else if (prefix) + { + char *curve = openpgp_oid_to_str (pk->pkey[0]); + const char *name = openpgp_oid_to_curve (curve, 0); + + if (name) + snprintf (buffer, bufsize, "%s", name); + else if (curve) + snprintf (buffer, bufsize, "E_%s", curve); + else + snprintf (buffer, bufsize, "E_error"); + xfree (curve); + } + else + snprintf (buffer, bufsize, "unknown_%u", (unsigned int)pk->pubkey_algo); + + return buffer; +} + + +/* Hash a public key. This function is useful for v4 fingerprints and + for v3 or v4 key signing. */ +void +hash_public_key (gcry_md_hd_t md, PKT_public_key *pk) +{ + unsigned int n = 6; + unsigned int nn[PUBKEY_MAX_NPKEY]; + byte *pp[PUBKEY_MAX_NPKEY]; + int i; + unsigned int nbits; + size_t nbytes; + int npkey = pubkey_get_npkey (pk->pubkey_algo); + + /* FIXME: We can avoid the extra malloc by calling only the first + mpi_print here which computes the required length and calling the + real mpi_print only at the end. The speed advantage would only be + for ECC (opaque MPIs) or if we could implement an mpi_print + variant with a callback handler to do the hashing. */ + if (npkey==0 && pk->pkey[0] + && gcry_mpi_get_flag (pk->pkey[0], GCRYMPI_FLAG_OPAQUE)) + { + pp[0] = gcry_mpi_get_opaque (pk->pkey[0], &nbits); + nn[0] = (nbits+7)/8; + n+=nn[0]; + } + else + { + for (i=0; i < npkey; i++ ) + { + if (!pk->pkey[i]) + { + /* This case may only happen if the parsing of the MPI + failed but the key was anyway created. May happen + during "gpg KEYFILE". */ + pp[i] = NULL; + nn[i] = 0; + } + else if (gcry_mpi_get_flag (pk->pkey[i], GCRYMPI_FLAG_OPAQUE)) + { + const void *p; + + p = gcry_mpi_get_opaque (pk->pkey[i], &nbits); + pp[i] = xmalloc ((nbits+7)/8); + if (p) + memcpy (pp[i], p, (nbits+7)/8); + else + pp[i] = NULL; + nn[i] = (nbits+7)/8; + n += nn[i]; + } + else + { + if (gcry_mpi_print (GCRYMPI_FMT_PGP, NULL, 0, + &nbytes, pk->pkey[i])) + BUG (); + pp[i] = xmalloc (nbytes); + if (gcry_mpi_print (GCRYMPI_FMT_PGP, pp[i], nbytes, + &nbytes, pk->pkey[i])) + BUG (); + nn[i] = nbytes; + n += nn[i]; + } + } + } + + gcry_md_putc ( md, 0x99 ); /* ctb */ + /* What does it mean if n is greater than 0xFFFF ? */ + gcry_md_putc ( md, n >> 8 ); /* 2 byte length header */ + gcry_md_putc ( md, n ); + gcry_md_putc ( md, pk->version ); + + gcry_md_putc ( md, pk->timestamp >> 24 ); + gcry_md_putc ( md, pk->timestamp >> 16 ); + gcry_md_putc ( md, pk->timestamp >> 8 ); + gcry_md_putc ( md, pk->timestamp ); + + gcry_md_putc ( md, pk->pubkey_algo ); + + if(npkey==0 && pk->pkey[0] + && gcry_mpi_get_flag (pk->pkey[0], GCRYMPI_FLAG_OPAQUE)) + { + if (pp[0]) + gcry_md_write (md, pp[0], nn[0]); + } + else + { + for(i=0; i < npkey; i++ ) + { + if (pp[i]) + gcry_md_write ( md, pp[i], nn[i] ); + xfree(pp[i]); + } + } +} + + +static gcry_md_hd_t +do_fingerprint_md( PKT_public_key *pk ) +{ + gcry_md_hd_t md; + + if (gcry_md_open (&md, DIGEST_ALGO_SHA1, 0)) + BUG (); + hash_public_key(md,pk); + gcry_md_final( md ); + + return md; +} + + +/* fixme: Check whether we can replace this function or if not + describe why we need it. */ +u32 +v3_keyid (gcry_mpi_t a, u32 *ki) +{ + byte *buffer, *p; + size_t nbytes; + + if (gcry_mpi_print (GCRYMPI_FMT_USG, NULL, 0, &nbytes, a )) + BUG (); + /* fixme: allocate it on the stack */ + buffer = xmalloc (nbytes); + if (gcry_mpi_print( GCRYMPI_FMT_USG, buffer, nbytes, NULL, a )) + BUG (); + if (nbytes < 8) /* oops */ + ki[0] = ki[1] = 0; + else + { + p = buffer + nbytes - 8; + ki[0] = buf32_to_u32 (p); + p += 4; + ki[1] = buf32_to_u32 (p); + } + xfree (buffer); + return ki[1]; +} + + +/* Return PK's keyid. The memory is owned by PK. */ +u32 * +pk_keyid (PKT_public_key *pk) +{ + keyid_from_pk (pk, NULL); + + /* Uncomment this for help tracking down bugs related to keyid or + main_keyid not being set correctly. */ +#if 0 + if (! (pk->main_keyid[0] || pk->main_keyid[1])) + log_bug ("pk->main_keyid not set!\n"); + if (keyid_cmp (pk->keyid, pk->main_keyid) == 0 + && ! pk->flags.primary) + log_bug ("keyid and main_keyid are the same, but primary flag not set!\n"); + if (keyid_cmp (pk->keyid, pk->main_keyid) != 0 + && pk->flags.primary) + log_bug ("keyid and main_keyid are different, but primary flag set!\n"); +#endif + + return pk->keyid; +} + +/* Return the keyid of the primary key associated with PK. The memory + is owned by PK. */ +u32 * +pk_main_keyid (PKT_public_key *pk) +{ + /* Uncomment this for help tracking down bugs related to keyid or + main_keyid not being set correctly. */ +#if 0 + if (! (pk->main_keyid[0] || pk->main_keyid[1])) + log_bug ("pk->main_keyid not set!\n"); +#endif + + return pk->main_keyid; +} + +/* Copy the keyid in SRC to DEST and return DEST. */ +u32 * +keyid_copy (u32 *dest, const u32 *src) +{ + dest[0] = src[0]; + dest[1] = src[1]; + return dest; +} + +char * +format_keyid (u32 *keyid, int format, char *buffer, int len) +{ + char tmp[KEYID_STR_SIZE]; + if (! buffer) + { + buffer = tmp; + len = sizeof (tmp); + } + + if (format == KF_DEFAULT) + format = opt.keyid_format; + if (format == KF_DEFAULT) + format = KF_NONE; + + switch (format) + { + case KF_NONE: + if (len) + *buffer = 0; + break; + + case KF_SHORT: + snprintf (buffer, len, "%08lX", (ulong)keyid[1]); + break; + + case KF_LONG: + snprintf (buffer, len, "%08lX%08lX", (ulong)keyid[0], (ulong)keyid[1]); + break; + + case KF_0xSHORT: + snprintf (buffer, len, "0x%08lX", (ulong)keyid[1]); + break; + + case KF_0xLONG: + snprintf (buffer, len, "0x%08lX%08lX", (ulong)keyid[0],(ulong)keyid[1]); + break; + + default: + BUG(); + } + + if (buffer == tmp) + return xstrdup (buffer); + return buffer; +} + +size_t +keystrlen(void) +{ + int format = opt.keyid_format; + if (format == KF_DEFAULT) + format = KF_NONE; + + switch(format) + { + case KF_NONE: + return 0; + + 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[KEYID_STR_SIZE]; + int format = opt.keyid_format; + + if (format == KF_DEFAULT) + format = KF_NONE; + if (format == KF_NONE) + format = KF_LONG; + + return format_keyid (keyid, format, keyid_str, sizeof (keyid_str)); +} + +/* This function returns the key id of the main and possible the + * subkey as one string. It is used by error messages. */ +const char * +keystr_with_sub (u32 *main_kid, u32 *sub_kid) +{ + static char buffer[KEYID_STR_SIZE+1+KEYID_STR_SIZE]; + char *p; + int format = opt.keyid_format; + + if (format == KF_NONE) + format = KF_LONG; + + format_keyid (main_kid, format, buffer, KEYID_STR_SIZE); + if (sub_kid) + { + p = buffer + strlen (buffer); + *p++ = '/'; + format_keyid (sub_kid, format, p, KEYID_STR_SIZE); + } + return buffer; +} + + +const char * +keystr_from_pk(PKT_public_key *pk) +{ + keyid_from_pk(pk,NULL); + + return keystr(pk->keyid); +} + + +const char * +keystr_from_pk_with_sub (PKT_public_key *main_pk, PKT_public_key *sub_pk) +{ + keyid_from_pk (main_pk, NULL); + if (sub_pk) + keyid_from_pk (sub_pk, NULL); + + return keystr_with_sub (main_pk->keyid, sub_pk? sub_pk->keyid:NULL); +} + + +/* Return PK's key id as a string using the default format. PK owns + the storage. */ +const char * +pk_keyid_str (PKT_public_key *pk) +{ + return keystr (pk_keyid (pk)); +} + + +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] = buf32_to_u32 (desc->u.fpr+12); + keyid[1] = buf32_to_u32 (desc->u.fpr+16); + return keystr(keyid); + } + + case KEYDB_SEARCH_MODE_FPR16: + return "?v3 fpr?"; + + default: + BUG(); + } +} + + +/* + * 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 + { + const byte *dp; + gcry_md_hd_t md; + + md = do_fingerprint_md(pk); + if(md) + { + dp = gcry_md_read ( md, 0 ); + keyid[0] = buf32_to_u32 (dp+12); + keyid[1] = buf32_to_u32 (dp+16); + lowbits = keyid[1]; + gcry_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 (ctrl_t ctrl, 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 (ctrl, &pk, NULL, 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] = buf32_to_u32 (dp+12); + keyid[1] = buf32_to_u32 (dp+16); + } + + 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) + { + 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 PK. + */ +unsigned int +nbits_from_pk (PKT_public_key *pk) +{ + return pubkey_nbits (pk->pubkey_algo, pk->pkey); +} + + +/* Convert an UTC TIMESTAMP into an UTC yyyy-mm-dd string. Return + * that string. The caller should pass a buffer with at least a size + * of MK_DATESTR_SIZE. */ +char * +mk_datestr (char *buffer, size_t bufsize, u32 timestamp) +{ + time_t atime = timestamp; + struct tm *tp; + + if (IS_INVALID_TIME_T (atime)) + strcpy (buffer, "????" "-??" "-??"); /* Mark this as invalid. */ + else + { + tp = gmtime (&atime); + snprintf (buffer, bufsize, "%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[MK_DATESTR_SIZE]; + + return mk_datestr (buffer, sizeof buffer, pk->timestamp); +} + + +const char * +datestr_from_sig (PKT_signature *sig ) +{ + static char buffer[MK_DATESTR_SIZE]; + + return mk_datestr (buffer, sizeof buffer, sig->timestamp); +} + + +const char * +expirestr_from_pk (PKT_public_key *pk) +{ + static char buffer[MK_DATESTR_SIZE]; + + if (!pk->expiredate) + return _("never "); + return mk_datestr (buffer, sizeof buffer, pk->expiredate); +} + + +const char * +expirestr_from_sig (PKT_signature *sig) +{ + static char buffer[MK_DATESTR_SIZE]; + + if (!sig->expiredate) + return _("never "); + return mk_datestr (buffer, sizeof buffer, sig->expiredate); +} + + +const char * +revokestr_from_pk( PKT_public_key *pk ) +{ + static char buffer[MK_DATESTR_SIZE]; + + if(!pk->revoked.date) + return _("never "); + return mk_datestr (buffer, sizeof buffer, pk->revoked.date); +} + + +const char * +usagestr_from_pk (PKT_public_key *pk, int fill) +{ + 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 (fill && i < 4) + buffer[i++] = ' '; + + buffer[i] = 0; + return buffer; +} + + +const char * +colon_strtime (u32 t) +{ + static char buf[20]; + + if (!t) + return ""; + snprintf (buf, sizeof buf, "%lu", (ulong)t); + return buf; +} + +const char * +colon_datestr_from_pk (PKT_public_key *pk) +{ + static char buf[20]; + + snprintf (buf, sizeof buf, "%lu", (ulong)pk->timestamp); + return buf; +} + + +const char * +colon_datestr_from_sig (PKT_signature *sig) +{ + static char buf[20]; + + snprintf (buf, sizeof buf, "%lu", (ulong)sig->timestamp); + return buf; +} + +const char * +colon_expirestr_from_sig (PKT_signature *sig) +{ + static char buf[20]; + + if (!sig->expiredate) + return ""; + + snprintf (buf, sizeof buf,"%lu", (ulong)sig->expiredate); + return buf; +} + + +/* + * 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) +{ + const byte *dp; + size_t len; + gcry_md_hd_t md; + + md = do_fingerprint_md(pk); + dp = gcry_md_read( md, 0 ); + len = gcry_md_get_algo_dlen (gcry_md_get_algo (md)); + log_assert( len <= MAX_FINGERPRINT_LEN ); + if (!array) + array = xmalloc ( len ); + memcpy (array, dp, len ); + pk->keyid[0] = buf32_to_u32 (dp+12); + pk->keyid[1] = buf32_to_u32 (dp+16); + gcry_md_close( md); + + if (ret_len) + *ret_len = len; + return array; +} + + +/* Return an allocated buffer with the fingerprint of PK formatted as + * a plain hexstring. If BUFFER is NULL the result is a malloc'd + * string. If BUFFER is not NULL the result will be copied into this + * buffer. In the latter case BUFLEN describes the length of the + * buffer; if this is too short the function terminates the process. + * Returns a malloc'ed string or BUFFER. A suitable length for BUFFER + * is (2*MAX_FINGERPRINT_LEN + 1). */ +char * +hexfingerprint (PKT_public_key *pk, char *buffer, size_t buflen) +{ + unsigned char fpr[MAX_FINGERPRINT_LEN]; + size_t len; + + fingerprint_from_pk (pk, fpr, &len); + if (!buffer) + { + buffer = xtrymalloc (2 * len + 1); + if (!buffer) + return NULL; + } + else if (buflen < 2*len+1) + log_fatal ("%s: buffer too short (%zu)\n", __func__, buflen); + bin2hex (fpr, len, buffer); + return buffer; +} + + +/* Pretty print a hex fingerprint. If BUFFER is NULL the result is a + malloc'd string. If BUFFER is not NULL the result will be copied + into this buffer. In the latter case BUFLEN describes the length + of the buffer; if this is too short the function terminates the + process. Returns a malloc'ed string or BUFFER. A suitable length + for BUFFER is (MAX_FORMATTED_FINGERPRINT_LEN + 1). */ +char * +format_hexfingerprint (const char *fingerprint, char *buffer, size_t buflen) +{ + int hexlen = strlen (fingerprint); + int space; + int i, j; + + if (hexlen == 40) /* v4 fingerprint */ + { + space = (/* The characters and the NUL. */ + 40 + 1 + /* After every fourth character, we add a space (except + the last). */ + + 40 / 4 - 1 + /* Half way through we add a second space. */ + + 1); + } + else /* Other fingerprint versions - print as is. */ + { + space = hexlen + 1; + } + + if (!buffer) + buffer = xmalloc (space); + else if (buflen < space) + log_fatal ("%s: buffer too short (%zu)\n", __func__, buflen); + + if (hexlen == 40) /* v4 fingerprint */ + { + for (i = 0, j = 0; i < 40; i ++) + { + if (i && i % 4 == 0) + buffer[j ++] = ' '; + if (i == 40 / 2) + buffer[j ++] = ' '; + + buffer[j ++] = fingerprint[i]; + } + buffer[j ++] = 0; + log_assert (j == space); + } + else + { + strcpy (buffer, fingerprint); + } + + return buffer; +} + + + +/* Return the so called KEYGRIP which is the SHA-1 hash of the public + key parameters expressed as an canoncial encoded S-Exp. ARRAY must + be 20 bytes long. Returns 0 on success or an error code. */ +gpg_error_t +keygrip_from_pk (PKT_public_key *pk, unsigned char *array) +{ + gpg_error_t err; + gcry_sexp_t s_pkey; + + if (DBG_PACKET) + log_debug ("get_keygrip for public key\n"); + + switch (pk->pubkey_algo) + { + case GCRY_PK_DSA: + err = gcry_sexp_build (&s_pkey, NULL, + "(public-key(dsa(p%m)(q%m)(g%m)(y%m)))", + pk->pkey[0], pk->pkey[1], + pk->pkey[2], pk->pkey[3]); + break; + + case GCRY_PK_ELG: + case GCRY_PK_ELG_E: + err = gcry_sexp_build (&s_pkey, NULL, + "(public-key(elg(p%m)(g%m)(y%m)))", + pk->pkey[0], pk->pkey[1], pk->pkey[2]); + break; + + case GCRY_PK_RSA: + case GCRY_PK_RSA_S: + case GCRY_PK_RSA_E: + err = gcry_sexp_build (&s_pkey, NULL, + "(public-key(rsa(n%m)(e%m)))", + pk->pkey[0], pk->pkey[1]); + break; + + case PUBKEY_ALGO_EDDSA: + case PUBKEY_ALGO_ECDSA: + case PUBKEY_ALGO_ECDH: + { + char *curve = openpgp_oid_to_str (pk->pkey[0]); + if (!curve) + err = gpg_error_from_syserror (); + else + { + err = gcry_sexp_build (&s_pkey, NULL, + pk->pubkey_algo == PUBKEY_ALGO_EDDSA? + "(public-key(ecc(curve%s)(flags eddsa)(q%m)))": + (pk->pubkey_algo == PUBKEY_ALGO_ECDH + && openpgp_oid_is_cv25519 (pk->pkey[0]))? + "(public-key(ecc(curve%s)(flags djb-tweak)(q%m)))": + "(public-key(ecc(curve%s)(q%m)))", + curve, pk->pkey[1]); + xfree (curve); + } + } + break; + + default: + err = gpg_error (GPG_ERR_PUBKEY_ALGO); + break; + } + + if (err) + return err; + + if (!gcry_pk_get_keygrip (s_pkey, array)) + { + char *hexfpr; + + hexfpr = hexfingerprint (pk, NULL, 0); + log_info ("error computing keygrip (fpr=%s)\n", hexfpr); + xfree (hexfpr); + + memset (array, 0, 20); + err = gpg_error (GPG_ERR_GENERAL); + } + else + { + if (DBG_PACKET) + log_printhex (array, 20, "keygrip="); + /* FIXME: Save the keygrip in PK. */ + } + gcry_sexp_release (s_pkey); + + return err; +} + + +/* Store an allocated buffer with the keygrip of PK encoded as a + hexstring at r_GRIP. Returns 0 on success. */ +gpg_error_t +hexkeygrip_from_pk (PKT_public_key *pk, char **r_grip) +{ + gpg_error_t err; + unsigned char grip[20]; + + *r_grip = NULL; + err = keygrip_from_pk (pk, grip); + if (!err) + { + char * buf = xtrymalloc (20*2+1); + if (!buf) + err = gpg_error_from_syserror (); + else + { + bin2hex (grip, 20, buf); + *r_grip = buf; + } + } + return err; +} diff --git a/g10/keylist.c b/g10/keylist.c new file mode 100644 index 0000000..af0ce9d --- /dev/null +++ b/g10/keylist.c @@ -0,0 +1,2226 @@ +/* keylist.c - Print information about OpenPGP keys + * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, + * 2008, 2010, 2012 Free Software Foundation, Inc. + * Copyright (C) 2013, 2014 Werner Koch + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include +#include +#include +#include +#include +#ifdef HAVE_DOSISH_SYSTEM +# include /* for setmode() */ +#endif + +#include "gpg.h" +#include "options.h" +#include "packet.h" +#include "../common/status.h" +#include "keydb.h" +#include "photoid.h" +#include "../common/util.h" +#include "../common/ttyio.h" +#include "trustdb.h" +#include "main.h" +#include "../common/i18n.h" +#include "../common/status.h" +#include "call-agent.h" +#include "../common/mbox-util.h" +#include "../common/zb32.h" +#include "tofu.h" +#include "../common/compliance.h" + + +static void list_all (ctrl_t, int, int); +static void list_one (ctrl_t ctrl, + strlist_t names, int secret, int mark_secret); +static void locate_one (ctrl_t ctrl, strlist_t names, int no_local); +static void print_card_serialno (const char *serialno); + +struct keylist_context +{ + int check_sigs; /* If set signatures shall be verified. */ + int good_sigs; /* Counter used if CHECK_SIGS is set. */ + int inv_sigs; /* Counter used if CHECK_SIGS is set. */ + int no_key; /* Counter used if CHECK_SIGS is set. */ + int oth_err; /* Counter used if CHECK_SIGS is set. */ + int no_validity; /* Do not show validity. */ +}; + + +static void list_keyblock (ctrl_t ctrl, + kbnode_t keyblock, int secret, int has_secret, + int fpr, struct keylist_context *listctx); + + +/* The stream used to write attribute packets to. */ +static estream_t attrib_fp; + + +/* Release resources from a keylist context. */ +static void +keylist_context_release (struct keylist_context *listctx) +{ + (void)listctx; /* Nothing to release. */ +} + + +/* List the keys. If list is NULL, all available keys are listed. + * With LOCATE_MODE set the locate algorithm is used to find a key; if + * in addition NO_LOCAL is set the locate does not look into the local + * keyring. */ +void +public_key_list (ctrl_t ctrl, strlist_t list, int locate_mode, int no_local) +{ +#ifndef NO_TRUST_MODELS + if (opt.with_colons) + { + byte trust_model, marginals, completes, cert_depth, min_cert_level; + ulong created, nextcheck; + + read_trust_options (ctrl, &trust_model, &created, &nextcheck, + &marginals, &completes, &cert_depth, &min_cert_level); + + es_fprintf (es_stdout, "tru:"); + + if (nextcheck && nextcheck <= make_timestamp ()) + es_fprintf (es_stdout, "o"); + if (trust_model != opt.trust_model) + es_fprintf (es_stdout, "t"); + if (opt.trust_model == TM_PGP || opt.trust_model == TM_CLASSIC + || opt.trust_model == TM_TOFU_PGP) + { + if (marginals != opt.marginals_needed) + es_fprintf (es_stdout, "m"); + if (completes != opt.completes_needed) + es_fprintf (es_stdout, "c"); + if (cert_depth != opt.max_cert_depth) + es_fprintf (es_stdout, "d"); + if (min_cert_level != opt.min_cert_level) + es_fprintf (es_stdout, "l"); + } + + es_fprintf (es_stdout, ":%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) + es_fprintf (es_stdout, ":%d:%d:%d", marginals, completes, cert_depth); + es_fprintf (es_stdout, "\n"); + } +#endif /*!NO_TRUST_MODELS*/ + + /* 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 (ctrl); + +#ifdef USE_TOFU + tofu_begin_batch_update (ctrl); +#endif + + if (locate_mode) + locate_one (ctrl, list, no_local); + else if (!list) + list_all (ctrl, 0, opt.with_secret); + else + list_one (ctrl, list, 0, opt.with_secret); + +#ifdef USE_TOFU + tofu_end_batch_update (ctrl); +#endif +} + + +void +secret_key_list (ctrl_t ctrl, strlist_t list) +{ + (void)ctrl; + + check_trustdb_stale (ctrl); + + if (!list) + list_all (ctrl, 1, 0); + else /* List by user id */ + list_one (ctrl, list, 1, 0); +} + +char * +format_seckey_info (ctrl_t ctrl, PKT_public_key *pk) +{ + u32 keyid[2]; + char *p; + char pkstrbuf[PUBKEY_STRING_SIZE]; + char *info; + + keyid_from_pk (pk, keyid); + p = get_user_id_native (ctrl, keyid); + + info = xtryasprintf ("sec %s/%s %s %s", + pubkey_string (pk, pkstrbuf, sizeof pkstrbuf), + keystr (keyid), datestr_from_pk (pk), p); + + xfree (p); + + return info; +} + +void +print_seckey_info (ctrl_t ctrl, PKT_public_key *pk) +{ + char *p = format_seckey_info (ctrl, pk); + tty_printf ("\n%s\n", p); + xfree (p); +} + +/* Print information about the public key. With FP passed as NULL, + the tty output interface is used, otherwise output is directed to + the given stream. */ +void +print_pubkey_info (ctrl_t ctrl, estream_t fp, PKT_public_key *pk) +{ + u32 keyid[2]; + char *p; + char pkstrbuf[PUBKEY_STRING_SIZE]; + + 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 (ctrl, keyid); + + if (!fp) + tty_printf ("\n"); + tty_fprintf (fp, "%s %s/%s %s %s\n", + pk->flags.primary? "pub":"sub", + pubkey_string (pk, pkstrbuf, sizeof pkstrbuf), + keystr (keyid), datestr_from_pk (pk), p); + xfree (p); +} + + +/* Print basic information of a secret key including the card serial + number information. */ +#ifdef ENABLE_CARD_SUPPORT +void +print_card_key_info (estream_t fp, kbnode_t keyblock) +{ + kbnode_t node; + char *hexgrip; + char *serialno; + int s2k_char; + char pkstrbuf[PUBKEY_STRING_SIZE]; + int indent; + + for (node = keyblock; node; node = node->next) + { + if (node->pkt->pkttype == PKT_PUBLIC_KEY + || node->pkt->pkttype == PKT_PUBLIC_SUBKEY) + { + int rc; + PKT_public_key *pk = node->pkt->pkt.public_key; + + serialno = NULL; + rc = hexkeygrip_from_pk (pk, &hexgrip); + if (rc) + { + log_error ("error computing a keygrip: %s\n", gpg_strerror (rc)); + s2k_char = '?'; + } + else if (!agent_get_keyinfo (NULL, hexgrip, &serialno, NULL)) + s2k_char = serialno? '>':' '; + else + s2k_char = '#'; /* Key not found. */ + + tty_fprintf (fp, "%s%c %s/%s %n", + node->pkt->pkttype == PKT_PUBLIC_KEY ? "sec" : "ssb", + s2k_char, + pubkey_string (pk, pkstrbuf, sizeof pkstrbuf), + keystr_from_pk (pk), + &indent); + tty_fprintf (fp, _("created: %s"), datestr_from_pk (pk)); + tty_fprintf (fp, " "); + tty_fprintf (fp, _("expires: %s"), expirestr_from_pk (pk)); + if (serialno) + { + tty_fprintf (fp, "\n%*s%s", indent, "", _("card-no: ")); + if (strlen (serialno) == 32 + && !strncmp (serialno, "D27600012401", 12)) + { + /* This is an OpenPGP card. Print the relevant part. */ + /* Example: D2760001240101010001000003470000 */ + /* xxxxyyyyyyyy */ + tty_fprintf (fp, "%.*s %.*s", 4, serialno+16, 8, serialno+20); + } + else + tty_fprintf (fp, "%s", serialno); + } + tty_fprintf (fp, "\n"); + xfree (hexgrip); + xfree (serialno); + } + } +} +#endif /*ENABLE_CARD_SUPPORT*/ + + +/* 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; + + snprintf (status, sizeof status, + "%d %u %u ", type, flags, (unsigned int) len); + + write_status_text_and_buffer (STATUS_SIG_SUBPACKET, status, buf, len, 0); +} + + +/* Print a policy URL. Allowed values for MODE are: + * -1 - print to the TTY + * 0 - print to stdout. + * 1 - use log_info and emit status messages. + * 2 - emit only status messages. + */ +void +show_policy_url (PKT_signature * sig, int indent, int mode) +{ + const byte *p; + size_t len; + int seq = 0, crit; + estream_t fp = mode < 0? NULL : mode ? log_get_stream () : es_stdout; + + while ((p = + enum_sig_subpkt (sig->hashed, SIGSUBPKT_POLICY, &len, &seq, &crit))) + { + if (mode != 2) + { + const char *str; + + tty_fprintf (fp, "%*s", indent, ""); + + if (crit) + str = _("Critical signature policy: "); + else + str = _("Signature policy: "); + if (mode > 0) + log_info ("%s", str); + else + tty_fprintf (fp, "%s", str); + tty_print_utf8_string2 (fp, p, len, 0); + tty_fprintf (fp, "\n"); + } + + if (mode > 0) + write_status_buffer (STATUS_POLICY_URL, p, len, 0); + } +} + + +/* Print a keyserver URL. Allowed values for MODE are: + * -1 - print to the TTY + * 0 - print to stdout. + * 1 - use log_info and emit status messages. + * 2 - emit only status messages. + */ +void +show_keyserver_url (PKT_signature * sig, int indent, int mode) +{ + const byte *p; + size_t len; + int seq = 0, crit; + estream_t fp = mode < 0? NULL : mode ? log_get_stream () : es_stdout; + + while ((p = + enum_sig_subpkt (sig->hashed, SIGSUBPKT_PREF_KS, &len, &seq, + &crit))) + { + if (mode != 2) + { + const char *str; + + tty_fprintf (fp, "%*s", indent, ""); + + if (crit) + str = _("Critical preferred keyserver: "); + else + str = _("Preferred keyserver: "); + if (mode > 0) + log_info ("%s", str); + else + tty_fprintf (fp, "%s", str); + tty_print_utf8_string2 (fp, p, len, 0); + tty_fprintf (fp, "\n"); + } + + if (mode > 0) + status_one_subpacket (SIGSUBPKT_PREF_KS, len, + (crit ? 0x02 : 0) | 0x01, p); + } +} + + +/* Print notation data. Allowed values for MODE are: + * -1 - print to the TTY + * 0 - print to stdout. + * 1 - use log_info and emit status messages. + * 2 - emit only status messages. + * + * Defined bits in WHICH: + * 1 - standard notations + * 2 - user notations + */ +void +show_notation (PKT_signature * sig, int indent, int mode, int which) +{ + estream_t fp = mode < 0? NULL : mode ? log_get_stream () : es_stdout; + notation_t 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)) + { + const char *str; + + tty_fprintf (fp, "%*s", indent, ""); + + if (nd->flags.critical) + str = _("Critical signature notation: "); + else + str = _("Signature notation: "); + if (mode > 0) + log_info ("%s", str); + else + tty_fprintf (fp, "%s", str); + /* This is all UTF8 */ + tty_print_utf8_string2 (fp, nd->name, strlen (nd->name), 0); + tty_fprintf (fp, "="); + tty_print_utf8_string2 (fp, nd->value, strlen (nd->value), 0); + /* (We need to use log_printf so that the next call to a + log function does not insert an extra LF.) */ + if (mode > 0) + log_printf ("\n"); + else + tty_fprintf (fp, "\n"); + } + } + + if (mode > 0) + { + write_status_buffer (STATUS_NOTATION_NAME, + nd->name, strlen (nd->name), 0); + if (nd->flags.critical || nd->flags.human) + write_status_text (STATUS_NOTATION_FLAGS, + nd->flags.critical && nd->flags.human? "1 1" : + nd->flags.critical? "1 0" : "0 1"); + if (!nd->flags.human && nd->bdat && nd->blen) + write_status_buffer (STATUS_NOTATION_DATA, + nd->bdat, nd->blen, 250); + else + write_status_buffer (STATUS_NOTATION_DATA, + nd->value, strlen (nd->value), 50); + } + } + + free_notation (notations); +} + + +static void +print_signature_stats (struct keylist_context *s) +{ + if (!s->check_sigs) + return; /* Signature checking was not requested. */ + + /* Better flush stdout so that the stats are always printed after + * the output. */ + es_fflush (es_stdout); + + if (s->good_sigs) + log_info (ngettext("%d good signature\n", + "%d good signatures\n", s->good_sigs), s->good_sigs); + + if (s->inv_sigs) + log_info (ngettext("%d bad signature\n", + "%d bad signatures\n", s->inv_sigs), s->inv_sigs); + + if (s->no_key) + log_info (ngettext("%d signature not checked due to a missing key\n", + "%d signatures not checked due to missing keys\n", + s->no_key), s->no_key); + + if (s->oth_err) + log_info (ngettext("%d signature not checked due to an error\n", + "%d signatures not checked due to errors\n", + s->oth_err), s->oth_err); +} + + +/* List all keys. If SECRET is true only secret keys are listed. If + MARK_SECRET is true secret keys are indicated in a public key + listing. */ +static void +list_all (ctrl_t ctrl, int secret, int mark_secret) +{ + KEYDB_HANDLE hd; + KBNODE keyblock = NULL; + int rc = 0; + int any_secret; + const char *lastresname, *resname; + struct keylist_context listctx; + + memset (&listctx, 0, sizeof (listctx)); + if (opt.check_sigs) + listctx.check_sigs = 1; + + hd = keydb_new (); + if (!hd) + rc = gpg_error_from_syserror (); + else + rc = keydb_search_first (hd); + if (rc) + { + if (gpg_err_code (rc) != GPG_ERR_NOT_FOUND) + log_error ("keydb_search_first failed: %s\n", gpg_strerror (rc)); + goto leave; + } + + lastresname = NULL; + do + { + if (secret) + glo_ctrl.silence_parse_warnings++; + rc = keydb_get_keyblock (hd, &keyblock); + if (secret) + glo_ctrl.silence_parse_warnings--; + if (rc) + { + if (gpg_err_code (rc) == GPG_ERR_LEGACY_KEY) + continue; /* Skip legacy keys. */ + if (gpg_err_code (rc) == GPG_ERR_UNKNOWN_VERSION) + continue; /* Skip keys with unknown versions. */ + log_error ("keydb_get_keyblock failed: %s\n", gpg_strerror (rc)); + goto leave; + } + + if (secret || mark_secret) + any_secret = !agent_probe_any_secret_key (NULL, keyblock); + else + any_secret = 0; + + if (secret && !any_secret) + ; /* Secret key listing requested but this isn't one. */ + else + { + if (!opt.with_colons && !(opt.list_options & LIST_SHOW_ONLY_FPR_MBOX)) + { + resname = keydb_get_resource_name (hd); + if (lastresname != resname) + { + int i; + + es_fprintf (es_stdout, "%s\n", resname); + for (i = strlen (resname); i; i--) + es_putc ('-', es_stdout); + es_putc ('\n', es_stdout); + lastresname = resname; + } + } + merge_keys_and_selfsig (ctrl, keyblock); + list_keyblock (ctrl, keyblock, secret, any_secret, opt.fingerprint, + &listctx); + } + release_kbnode (keyblock); + keyblock = NULL; + } + while (!(rc = keydb_search_next (hd))); + es_fflush (es_stdout); + if (rc && gpg_err_code (rc) != GPG_ERR_NOT_FOUND) + log_error ("keydb_search_next failed: %s\n", gpg_strerror (rc)); + if (keydb_get_skipped_counter (hd)) + log_info (ngettext("Warning: %lu key skipped due to its large size\n", + "Warning: %lu keys skipped due to their large sizes\n", + keydb_get_skipped_counter (hd)), + keydb_get_skipped_counter (hd)); + + if (opt.check_sigs && !opt.with_colons) + print_signature_stats (&listctx); + + leave: + keylist_context_release (&listctx); + release_kbnode (keyblock); + keydb_release (hd); +} + + +static void +list_one (ctrl_t ctrl, strlist_t names, int secret, int mark_secret) +{ + int rc = 0; + KBNODE keyblock = NULL; + GETKEY_CTX ctx; + int any_secret; + const char *resname; + const char *keyring_str = _("Keyring"); + int i; + struct keylist_context listctx; + + memset (&listctx, 0, sizeof (listctx)); + if (!secret && opt.check_sigs) + listctx.check_sigs = 1; + + /* fixme: using the bynames function has the disadvantage that we + * don't know whether 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. */ + rc = getkey_bynames (ctrl, &ctx, NULL, names, secret, &keyblock); + if (rc) + { + log_error ("error reading key: %s\n", gpg_strerror (rc)); + getkey_end (ctrl, ctx); + write_status_error ("keylist.getkey", rc); + return; + } + + do + { + /* getkey_bynames makes sure that only secret keys are returned + * if requested, thus we do not need to test again. With + * MARK_SECRET set (ie. option --with-secret) we have to test + * for a secret key, though. */ + if (secret) + any_secret = 1; + else if (mark_secret) + any_secret = !agent_probe_any_secret_key (NULL, keyblock); + else + any_secret = 0; + + if (secret && !any_secret) + ;/* Secret key listing requested but getkey_bynames failed. */ + else + { + if ((opt.list_options & LIST_SHOW_KEYRING) && !opt.with_colons) + { + resname = keydb_get_resource_name (get_ctx_handle (ctx)); + es_fprintf (es_stdout, "%s: %s\n", keyring_str, resname); + for (i = strlen (resname) + strlen (keyring_str) + 2; i; i--) + es_putc ('-', es_stdout); + es_putc ('\n', es_stdout); + } + list_keyblock (ctrl, keyblock, secret, any_secret, + opt.fingerprint, &listctx); + } + release_kbnode (keyblock); + } + while (!getkey_next (ctrl, ctx, NULL, &keyblock)); + getkey_end (ctrl, ctx); + + if (opt.check_sigs && !opt.with_colons) + print_signature_stats (&listctx); + + keylist_context_release (&listctx); +} + + +static void +locate_one (ctrl_t ctrl, strlist_t names, int no_local) +{ + int rc = 0; + strlist_t sl; + GETKEY_CTX ctx = NULL; + KBNODE keyblock = NULL; + struct keylist_context listctx; + + memset (&listctx, 0, sizeof (listctx)); + if (opt.check_sigs) + listctx.check_sigs = 1; + + for (sl = names; sl; sl = sl->next) + { + rc = get_best_pubkey_byname (ctrl, + no_local? GET_PUBKEY_NO_LOCAL + /* */: GET_PUBKEY_NORMAL, + &ctx, NULL, sl->d, &keyblock, 1); + if (rc) + { + if (gpg_err_code (rc) != GPG_ERR_NO_PUBKEY) + log_error ("error reading key: %s\n", gpg_strerror (rc)); + else if (opt.verbose) + log_info (_("key \"%s\" not found: %s\n"), + sl->d, gpg_strerror (rc)); + } + else + { + do + { + list_keyblock (ctrl, keyblock, 0, 0, opt.fingerprint, &listctx); + release_kbnode (keyblock); + } + while (ctx && !getkey_next (ctrl, ctx, NULL, &keyblock)); + getkey_end (ctrl, ctx); + ctx = NULL; + } + } + + if (opt.check_sigs && !opt.with_colons) + print_signature_stats (&listctx); + + keylist_context_release (&listctx); +} + + +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++) + { + es_fprintf (es_stdout, "pkd:%d:%u:", i, mpi_get_nbits (pk->pkey[i])); + mpi_print (es_stdout, pk->pkey[i], 1); + es_putc (':', es_stdout); + es_putc ('\n', es_stdout); + } +} + +static void +print_capabilities (ctrl_t ctrl, PKT_public_key *pk, KBNODE keyblock) +{ + unsigned int use = pk->pubkey_usage; + int c_printed = 0; + + if (use & PUBKEY_USAGE_ENC) + es_putc ('e', es_stdout); + + if (use & PUBKEY_USAGE_SIG) + { + es_putc ('s', es_stdout); + if (pk->flags.primary) + { + es_putc ('c', es_stdout); + /* The PUBKEY_USAGE_CERT flag was introduced later and we + used to always print 'c' for a primary key. To avoid any + regression here we better track whether we printed 'c' + already. */ + c_printed = 1; + } + } + + if ((use & PUBKEY_USAGE_CERT) && !c_printed) + es_putc ('c', es_stdout); + + if ((use & PUBKEY_USAGE_AUTH)) + es_putc ('a', es_stdout); + + if ((use & PUBKEY_USAGE_UNKNOWN)) + es_putc ('?', es_stdout); + + 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->flags.primary) + disabled = pk_is_disabled (pk); + + if (pk->flags.valid && !pk->flags.revoked && !pk->has_expired) + { + if (pk->pubkey_usage & PUBKEY_USAGE_ENC) + enc = 1; + if (pk->pubkey_usage & PUBKEY_USAGE_SIG) + { + sign = 1; + if (pk->flags.primary) + cert = 1; + } + if (pk->pubkey_usage & PUBKEY_USAGE_CERT) + cert = 1; + if ((pk->pubkey_usage & PUBKEY_USAGE_AUTH)) + auth = 1; + } + } + } + if (enc) + es_putc ('E', es_stdout); + if (sign) + es_putc ('S', es_stdout); + if (cert) + es_putc ('C', es_stdout); + if (auth) + es_putc ('A', es_stdout); + if (disabled) + es_putc ('D', es_stdout); + } + + es_putc (':', es_stdout); +} + + +/* FLAGS: 0x01 hashed + 0x02 critical */ +static void +print_one_subpacket (sigsubpkttype_t type, size_t len, int flags, + const byte * buf) +{ + size_t i; + + es_fprintf (es_stdout, "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] != '%') + es_fprintf (es_stdout, "%c", buf[i]); + else + es_fprintf (es_stdout, "%%%02X", buf[i]); + } + + es_fprintf (es_stdout, "\n"); +} + + +void +print_subpackets_colon (PKT_signature * sig) +{ + byte *i; + + log_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) +{ + 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) + BUG (); + fingerprint_from_pk (pk, array, &n); + + 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->flags.primary ? 0x01 : 0) | (uid->flags.revoked ? 0x02 : 0) | + (uid->flags.expired ? 0x04 : 0))); + write_status_text (STATUS_ATTRIBUTE, buf); + } + + es_fwrite (uid->attribs[i].data, uid->attribs[i].len, 1, attrib_fp); + es_fflush (attrib_fp); + } +} + + +/* Order two signatures. We first order by keyid and then by creation + * time. This is currently only used in keyedit.c */ +int +cmp_signodes (const void *av, const void *bv) +{ + const kbnode_t an = *(const kbnode_t *)av; + const kbnode_t bn = *(const kbnode_t *)bv; + const PKT_signature *a; + const PKT_signature *b; + int i; + + /* log_assert (an->pkt->pkttype == PKT_SIGNATURE); */ + /* log_assert (bn->pkt->pkttype == PKT_SIGNATURE); */ + + a = an->pkt->pkt.signature; + b = bn->pkt->pkt.signature; + + /* Self-signatures are ordered first. */ + if ((an->flag & NODFLG_MARK_B) && !(bn->flag & NODFLG_MARK_B)) + return -1; + if (!(an->flag & NODFLG_MARK_B) && (bn->flag & NODFLG_MARK_B)) + return 1; + + /* then the keyids. (which are or course the same for self-sigs). */ + i = keyid_cmp (a->keyid, b->keyid); + if (i) + return i; + + /* Followed by creation time */ + if (a->timestamp > b->timestamp) + return 1; + if (a->timestamp < b->timestamp) + return -1; + + /* followed by the class in a way that a rev comes first. */ + if (a->sig_class > b->sig_class) + return 1; + if (a->sig_class < b->sig_class) + return -1; + + /* To make the sort stable we compare the entire structure as last resort. */ + return memcmp (a, b, sizeof *a); +} + + +static void +list_keyblock_print (ctrl_t ctrl, kbnode_t keyblock, int secret, int fpr, + struct keylist_context *listctx) +{ + int rc; + KBNODE kbctx; + KBNODE node; + PKT_public_key *pk; + int skip_sigs = 0; + char *hexgrip = NULL; + char *serialno = NULL; + + /* Get the keyid from the keyblock. */ + node = find_kbnode (keyblock, PKT_PUBLIC_KEY); + if (!node) + { + log_error ("Oops; key lost!\n"); + dump_kbnode (keyblock); + return; + } + + pk = node->pkt->pkt.public_key; + + if (secret || opt.with_keygrip) + { + rc = hexkeygrip_from_pk (pk, &hexgrip); + if (rc) + log_error ("error computing a keygrip: %s\n", gpg_strerror (rc)); + } + + if (secret) + { + /* Encode some info about the secret key in SECRET. */ + if (!agent_get_keyinfo (NULL, hexgrip, &serialno, NULL)) + secret = serialno? 3 : 1; + else + secret = 2; /* Key not found. */ + } + + if (!listctx->no_validity) + check_trustdb_stale (ctrl); + + /* Print the "pub" line and in KF_NONE mode the fingerprint. */ + print_key_line (ctrl, es_stdout, pk, secret); + + if (fpr) + print_fingerprint (ctrl, NULL, pk, 0); + + if (opt.with_keygrip && hexgrip) + es_fprintf (es_stdout, " Keygrip = %s\n", hexgrip); + + if (serialno) + print_card_serialno (serialno); + + if (opt.with_key_data) + print_key_data (pk); + + if (opt.with_key_origin + && (pk->keyorg || pk->keyupdate || pk->updateurl)) + { + char updatestr[MK_DATESTR_SIZE]; + + es_fprintf (es_stdout, " origin=%s last=%s %s", + key_origin_string (pk->keyorg), + mk_datestr (updatestr, sizeof updatestr, pk->keyupdate), + pk->updateurl? "url=":""); + if (pk->updateurl) + print_utf8_string (es_stdout, pk->updateurl); + es_putc ('\n', es_stdout); + } + + + 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; + int indent; + int kl = opt.keyid_format == KF_NONE? 10 : keystrlen (); + + if ((uid->flags.expired || uid->flags.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); + + if ((uid->flags.revoked || uid->flags.expired) + || ((opt.list_options & LIST_SHOW_UID_VALIDITY) + && !listctx->no_validity)) + { + const char *validity; + + validity = uid_trust_string_fixed (ctrl, pk, uid); + indent = ((kl + (opt.legacy_list_mode? 9:11)) + - atoi (uid_trust_string_fixed (ctrl, NULL, NULL))); + if (indent < 0 || indent > 40) + indent = 0; + + es_fprintf (es_stdout, "uid%*s%s ", indent, "", validity); + } + else + { + indent = kl + (opt.legacy_list_mode? 10:12); + es_fprintf (es_stdout, "uid%*s", indent, ""); + } + + print_utf8_buffer (es_stdout, uid->name, uid->len); + es_putc ('\n', es_stdout); + + if (opt.with_wkd_hash) + { + char *mbox, *hash, *p; + char hashbuf[32]; + + mbox = mailbox_from_userid (uid->name); + if (mbox && (p = strchr (mbox, '@'))) + { + *p++ = 0; + gcry_md_hash_buffer (GCRY_MD_SHA1, hashbuf, + mbox, strlen (mbox)); + hash = zb32_encode (hashbuf, 8*20); + if (hash) + { + es_fprintf (es_stdout, " %*s%s@%s\n", + indent, "", hash, p); + xfree (hash); + } + } + xfree (mbox); + } + + if (opt.with_key_origin + && (uid->keyorg || uid->keyupdate || uid->updateurl)) + { + char updatestr[MK_DATESTR_SIZE]; + + es_fprintf (es_stdout, " %*sorigin=%s last=%s %s", + indent, "", + key_origin_string (uid->keyorg), + mk_datestr (updatestr, sizeof updatestr, + uid->keyupdate), + uid->updateurl? "url=":""); + if (uid->updateurl) + print_utf8_string (es_stdout, uid->updateurl); + es_putc ('\n', es_stdout); + } + + if ((opt.list_options & LIST_SHOW_PHOTOS) && uid->attribs != NULL) + show_photos (ctrl, uid->attribs, uid->numattribs, pk, uid); + } + else if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY) + { + PKT_public_key *pk2 = node->pkt->pkt.public_key; + + if ((pk2->flags.revoked || pk2->has_expired) + && !(opt.list_options & LIST_SHOW_UNUSABLE_SUBKEYS)) + { + skip_sigs = 1; + continue; + } + else + skip_sigs = 0; + + xfree (serialno); serialno = NULL; + xfree (hexgrip); hexgrip = NULL; + if (secret || opt.with_keygrip) + { + rc = hexkeygrip_from_pk (pk2, &hexgrip); + if (rc) + log_error ("error computing a keygrip: %s\n", + gpg_strerror (rc)); + } + if (secret) + { + if (!agent_get_keyinfo (NULL, hexgrip, &serialno, NULL)) + secret = serialno? 3 : 1; + else + secret = 2; /* Key not found. */ + } + + /* Print the "sub" line. */ + print_key_line (ctrl, es_stdout, pk2, secret); + if (fpr > 1 || opt.with_subkey_fingerprint) + { + print_fingerprint (ctrl, NULL, pk2, 0); + if (serialno) + print_card_serialno (serialno); + } + if (opt.with_keygrip && hexgrip) + es_fprintf (es_stdout, " Keygrip = %s\n", hexgrip); + if (opt.with_key_data) + print_key_data (pk2); + } + else if (opt.list_sigs + && node->pkt->pkttype == PKT_SIGNATURE && !skip_sigs) + { + PKT_signature *sig = node->pkt->pkt.signature; + int sigrc; + char *sigstr; + char *reason_text = NULL; + char *reason_comment = NULL; + size_t reason_commentlen; + + if (listctx->check_sigs) + { + rc = check_key_signature (ctrl, keyblock, node, NULL); + switch (gpg_err_code (rc)) + { + case 0: + listctx->good_sigs++; + sigrc = '!'; + break; + case GPG_ERR_BAD_SIGNATURE: + listctx->inv_sigs++; + sigrc = '-'; + break; + case GPG_ERR_NO_PUBKEY: + case GPG_ERR_UNUSABLE_PUBKEY: + listctx->no_key++; + continue; + default: + listctx->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"; + get_revocation_reason (sig, &reason_text, + &reason_comment, &reason_commentlen); + } + 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 + { + es_fprintf (es_stdout, "sig " + "[unexpected signature class 0x%02x]\n", + sig->sig_class); + continue; + } + + es_fputs (sigstr, es_stdout); + es_fprintf (es_stdout, "%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) + es_fprintf (es_stdout, " %s", expirestr_from_sig (sig)); + es_fprintf (es_stdout, " "); + if (sigrc == '%') + es_fprintf (es_stdout, "[%s] ", gpg_strerror (rc)); + else if (sigrc == '?') + ; + else if (!opt.fast_list_mode) + { + size_t n; + char *p = get_user_id (ctrl, sig->keyid, &n, NULL); + print_utf8_buffer (es_stdout, p, n); + xfree (p); + } + es_putc ('\n', es_stdout); + + 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); + + if (reason_text) + { + es_fprintf (es_stdout, " %s%s\n", + _("reason for revocation: "), reason_text); + if (reason_comment) + { + const byte *s, *s_lf; + size_t n, n_lf; + + s = reason_comment; + n = reason_commentlen; + s_lf = NULL; + do + { + /* We don't want any empty lines, so we skip them. */ + for (;n && *s == '\n'; s++, n--) + ; + if (n) + { + s_lf = memchr (s, '\n', n); + n_lf = s_lf? s_lf - s : n; + es_fprintf (es_stdout, " %s", + _("revocation comment: ")); + es_write_sanitized (es_stdout, s, n_lf, NULL, NULL); + es_putc ('\n', es_stdout); + s += n_lf; n -= n_lf; + } + } while (s_lf); + } + } + + xfree (reason_text); + xfree (reason_comment); + + /* fixme: check or list other sigs here */ + } + } + es_putc ('\n', es_stdout); + xfree (serialno); + xfree (hexgrip); +} + + +/* Do a simple key listing printing only the fingerprint and the mail + * address of valid keys. */ +static void +list_keyblock_simple (ctrl_t ctrl, kbnode_t keyblock) +{ + gpg_err_code_t ec; + kbnode_t kbctx; + kbnode_t node; + char hexfpr[2*MAX_FINGERPRINT_LEN+1]; + char *mbox; + + (void)ctrl; + + node = find_kbnode (keyblock, PKT_PUBLIC_KEY); + if (!node) + { + log_error ("Oops; key lost!\n"); + dump_kbnode (keyblock); + return; + } + hexfingerprint (node->pkt->pkt.public_key, hexfpr, sizeof hexfpr); + + 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 (uid->attrib_data) + continue; + + if (uid->flags.expired || uid->flags.revoked) + continue; + + mbox = mailbox_from_userid (uid->name); + if (!mbox) + { + ec = gpg_err_code_from_syserror (); + if (ec != GPG_ERR_EINVAL) + log_error ("error getting mailbox from user-id: %s\n", + gpg_strerror (ec)); + continue; + } + es_fprintf (es_stdout, "%s %s\n", hexfpr, mbox); + xfree (mbox); + } + } +} + + +void +print_revokers (estream_t fp, 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; + + es_fprintf (fp, "rvk:::%d::::::", pk->revkey[i].algid); + p = pk->revkey[i].fpr; + for (j = 0; j < 20; j++, p++) + es_fprintf (fp, "%02X", *p); + es_fprintf (fp, ":%02x%s:\n", + pk->revkey[i].class, + (pk->revkey[i].class & 0x40) ? "s" : ""); + } + } +} + + +/* Print the compliance flags to field 18. PK is the public key. + * KEYLENGTH is the length of the key in bits and CURVENAME is either + * NULL or the name of the curve. The latter two args are here + * merely because the caller has already computed them. */ +static void +print_compliance_flags (PKT_public_key *pk, + unsigned int keylength, const char *curvename) +{ + int any = 0; + + if (!keylength) + keylength = nbits_from_pk (pk); + + if (pk->version == 5) + { + es_fputs (gnupg_status_compliance_flag (CO_GNUPG), es_stdout); + any++; + } + if (gnupg_pk_is_compliant (CO_DE_VS, pk->pubkey_algo, 0, pk->pkey, + keylength, curvename)) + { + es_fprintf (es_stdout, any ? " %s" : "%s", + gnupg_status_compliance_flag (CO_DE_VS)); + any++; + } +} + + +/* List a key in colon mode. If SECRET is true this is a secret key + record (i.e. requested via --list-secret-key). If HAS_SECRET a + secret key is available even if SECRET is not set. */ +static void +list_keyblock_colon (ctrl_t ctrl, kbnode_t keyblock, + int secret, int has_secret) +{ + int rc; + KBNODE kbctx; + KBNODE node; + PKT_public_key *pk; + u32 keyid[2]; + int trustletter = 0; + int trustletter_print; + int ownertrust_print; + int ulti_hack = 0; + int i; + char *hexgrip_buffer = NULL; + const char *hexgrip = NULL; + char *serialno = NULL; + int stubkey; + unsigned int keylength; + char *curve = NULL; + const char *curvename = NULL; + + /* Get the keyid from the keyblock. */ + node = find_kbnode (keyblock, PKT_PUBLIC_KEY); + if (!node) + { + log_error ("Oops; key lost!\n"); + dump_kbnode (keyblock); + return; + } + + pk = node->pkt->pkt.public_key; + if (secret || has_secret || opt.with_keygrip || opt.with_key_data) + { + rc = hexkeygrip_from_pk (pk, &hexgrip_buffer); + if (rc) + log_error ("error computing a keygrip: %s\n", gpg_strerror (rc)); + /* In the error case we print an empty string so that we have a + * "grp" record for each and subkey - even if it is empty. This + * may help to prevent sync problems. */ + hexgrip = hexgrip_buffer? hexgrip_buffer : ""; + } + stubkey = 0; + if ((secret || has_secret) + && agent_get_keyinfo (NULL, hexgrip, &serialno, NULL)) + stubkey = 1; /* Key not found. */ + + keyid_from_pk (pk, keyid); + if (!pk->flags.valid) + trustletter_print = 'i'; + else if (pk->flags.revoked) + trustletter_print = 'r'; + else if (pk->has_expired) + trustletter_print = 'e'; + else if (opt.fast_list_mode || opt.no_expensive_trust_checks) + trustletter_print = 0; + else + { + trustletter = get_validity_info (ctrl, keyblock, pk, NULL); + if (trustletter == 'u') + ulti_hack = 1; + trustletter_print = trustletter; + } + + if (!opt.fast_list_mode && !opt.no_expensive_trust_checks) + ownertrust_print = get_ownertrust_info (ctrl, pk, 0); + else + ownertrust_print = 0; + + keylength = nbits_from_pk (pk); + + es_fputs (secret? "sec:":"pub:", es_stdout); + if (trustletter_print) + es_putc (trustletter_print, es_stdout); + es_fprintf (es_stdout, ":%u:%d:%08lX%08lX:%s:%s::", + keylength, + pk->pubkey_algo, + (ulong) keyid[0], (ulong) keyid[1], + colon_datestr_from_pk (pk), colon_strtime (pk->expiredate)); + + if (ownertrust_print) + es_putc (ownertrust_print, es_stdout); + es_putc (':', es_stdout); + + es_putc (':', es_stdout); + es_putc (':', es_stdout); + print_capabilities (ctrl, pk, keyblock); + es_putc (':', es_stdout); /* End of field 13. */ + es_putc (':', es_stdout); /* End of field 14. */ + if (secret || has_secret) + { + if (stubkey) + es_putc ('#', es_stdout); + else if (serialno) + es_fputs (serialno, es_stdout); + else if (has_secret) + es_putc ('+', es_stdout); + } + es_putc (':', es_stdout); /* End of field 15. */ + es_putc (':', es_stdout); /* End of field 16. */ + if (pk->pubkey_algo == PUBKEY_ALGO_ECDSA + || pk->pubkey_algo == PUBKEY_ALGO_EDDSA + || pk->pubkey_algo == PUBKEY_ALGO_ECDH) + { + curve = openpgp_oid_to_str (pk->pkey[0]); + curvename = openpgp_oid_to_curve (curve, 0); + if (!curvename) + curvename = curve; + es_fputs (curvename, es_stdout); + } + es_putc (':', es_stdout); /* End of field 17. */ + print_compliance_flags (pk, keylength, curvename); + es_putc (':', es_stdout); /* End of field 18 (compliance). */ + if (pk->keyupdate) + es_fputs (colon_strtime (pk->keyupdate), es_stdout); + es_putc (':', es_stdout); /* End of field 19 (last_update). */ + es_fprintf (es_stdout, "%d%s", pk->keyorg, pk->updateurl? " ":""); + if (pk->updateurl) + es_write_sanitized (es_stdout, pk->updateurl, strlen (pk->updateurl), + ":", NULL); + es_putc (':', es_stdout); /* End of field 20 (origin). */ + es_putc ('\n', es_stdout); + + print_revokers (es_stdout, pk); + print_fingerprint (ctrl, NULL, pk, 0); + if (hexgrip) + es_fprintf (es_stdout, "grp:::::::::%s:\n", hexgrip); + 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) + { + PKT_user_id *uid = node->pkt->pkt.user_id; + int uid_validity; + + if (attrib_fp && uid->attrib_data != NULL) + dump_attribs (uid, pk); + + if (uid->flags.revoked) + uid_validity = 'r'; + else if (uid->flags.expired) + uid_validity = 'e'; + else if (opt.no_expensive_trust_checks) + uid_validity = 0; + else if (ulti_hack) + uid_validity = 'u'; + else + uid_validity = get_validity_info (ctrl, keyblock, pk, uid); + + es_fputs (uid->attrib_data? "uat:":"uid:", es_stdout); + if (uid_validity) + es_putc (uid_validity, es_stdout); + es_fputs ("::::", es_stdout); + + es_fprintf (es_stdout, "%s:", colon_strtime (uid->created)); + es_fprintf (es_stdout, "%s:", colon_strtime (uid->expiredate)); + + namehash_from_uid (uid); + + for (i = 0; i < 20; i++) + es_fprintf (es_stdout, "%02X", uid->namehash[i]); + + es_fprintf (es_stdout, "::"); + + if (uid->attrib_data) + es_fprintf (es_stdout, "%u %lu", uid->numattribs, uid->attrib_len); + else + es_write_sanitized (es_stdout, uid->name, uid->len, ":", NULL); + es_fputs (":::::::::", es_stdout); + if (uid->keyupdate) + es_fputs (colon_strtime (uid->keyupdate), es_stdout); + es_putc (':', es_stdout); /* End of field 19 (last_update). */ + es_fprintf (es_stdout, "%d%s", uid->keyorg, uid->updateurl? " ":""); + if (uid->updateurl) + es_write_sanitized (es_stdout, + uid->updateurl, strlen (uid->updateurl), + ":", NULL); + es_putc (':', es_stdout); /* End of field 20 (origin). */ + es_putc ('\n', es_stdout); +#ifdef USE_TOFU + if (!uid->attrib_data && opt.with_tofu_info + && (opt.trust_model == TM_TOFU || opt.trust_model == TM_TOFU_PGP)) + { + /* Print a "tfs" record. */ + tofu_write_tfs_record (ctrl, es_stdout, pk, uid->name); + } +#endif /*USE_TOFU*/ + } + else if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY) + { + u32 keyid2[2]; + PKT_public_key *pk2; + int need_hexgrip = !!hexgrip; + + pk2 = node->pkt->pkt.public_key; + xfree (hexgrip_buffer); hexgrip_buffer = NULL; hexgrip = NULL; + xfree (serialno); serialno = NULL; + if (need_hexgrip + || secret || has_secret || opt.with_keygrip || opt.with_key_data) + { + rc = hexkeygrip_from_pk (pk2, &hexgrip_buffer); + if (rc) + log_error ("error computing a keygrip: %s\n", + gpg_strerror (rc)); + hexgrip = hexgrip_buffer? hexgrip_buffer : ""; + } + stubkey = 0; + if ((secret||has_secret) + && agent_get_keyinfo (NULL, hexgrip, &serialno, NULL)) + stubkey = 1; /* Key not found. */ + + keyid_from_pk (pk2, keyid2); + es_fputs (secret? "ssb:":"sub:", es_stdout); + if (!pk2->flags.valid) + es_putc ('i', es_stdout); + else if (pk2->flags.revoked) + es_putc ('r', es_stdout); + else if (pk2->has_expired) + es_putc ('e', es_stdout); + else if (opt.fast_list_mode || opt.no_expensive_trust_checks) + ; + else + { + /* TRUSTLETTER should always be defined here. */ + if (trustletter) + es_fprintf (es_stdout, "%c", trustletter); + } + keylength = nbits_from_pk (pk2); + es_fprintf (es_stdout, ":%u:%d:%08lX%08lX:%s:%s:::::", + keylength, + pk2->pubkey_algo, + (ulong) keyid2[0], (ulong) keyid2[1], + colon_datestr_from_pk (pk2), + colon_strtime (pk2->expiredate)); + print_capabilities (ctrl, pk2, NULL); + es_putc (':', es_stdout); /* End of field 13. */ + es_putc (':', es_stdout); /* End of field 14. */ + if (secret || has_secret) + { + if (stubkey) + es_putc ('#', es_stdout); + else if (serialno) + es_fputs (serialno, es_stdout); + else if (has_secret) + es_putc ('+', es_stdout); + } + es_putc (':', es_stdout); /* End of field 15. */ + es_putc (':', es_stdout); /* End of field 16. */ + if (pk2->pubkey_algo == PUBKEY_ALGO_ECDSA + || pk2->pubkey_algo == PUBKEY_ALGO_EDDSA + || pk2->pubkey_algo == PUBKEY_ALGO_ECDH) + { + xfree (curve); + curve = openpgp_oid_to_str (pk2->pkey[0]); + curvename = openpgp_oid_to_curve (curve, 0); + if (!curvename) + curvename = curve; + es_fputs (curvename, es_stdout); + } + es_putc (':', es_stdout); /* End of field 17. */ + print_compliance_flags (pk2, keylength, curvename); + es_putc (':', es_stdout); /* End of field 18. */ + es_putc ('\n', es_stdout); + print_fingerprint (ctrl, NULL, pk2, 0); + if (hexgrip) + es_fprintf (es_stdout, "grp:::::::::%s:\n", hexgrip); + if (opt.with_key_data) + print_key_data (pk2); + } + 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]; + char *siguid; + size_t siguidlen; + char *issuer_fpr = NULL; + char *reason_text = NULL; + char *reason_comment = NULL; + size_t reason_commentlen; + int reason_code = 0; /* init to silence cc warning. */ + + if (sig->sig_class == 0x20 || sig->sig_class == 0x28 + || sig->sig_class == 0x30) + { + sigstr = "rev"; + reason_code = get_revocation_reason (sig, &reason_text, + &reason_comment, + &reason_commentlen); + } + 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 + { + es_fprintf (es_stdout, "sig::::::::::%02x%c:\n", + sig->sig_class, sig->flags.exportable ? 'x' : 'l'); + continue; + } + + if (opt.check_sigs) + { + PKT_public_key *signer_pk = NULL; + + es_fflush (es_stdout); + if (opt.no_sig_cache) + signer_pk = xmalloc_clear (sizeof (PKT_public_key)); + + rc = check_key_signature2 (ctrl, keyblock, node, NULL, signer_pk, + NULL, NULL, NULL); + switch (gpg_err_code (rc)) + { + case 0: + sigrc = '!'; + break; + case GPG_ERR_BAD_SIGNATURE: + sigrc = '-'; + break; + case GPG_ERR_NO_PUBKEY: + case GPG_ERR_UNUSABLE_PUBKEY: + sigrc = '?'; + break; + default: + sigrc = '%'; + break; + } + + if (opt.no_sig_cache) + { + if (!rc) + { + fingerprint_from_pk (signer_pk, fparray, &fplen); + fprokay = 1; + } + free_public_key (signer_pk); + } + } + else + { + rc = 0; + sigrc = ' '; /* Note the fix-up below in --list-sigs mode. */ + } + + if (sigrc != '%' && sigrc != '?' && !opt.fast_list_mode) + { + int nouid; + siguid = get_user_id (ctrl, sig->keyid, &siguidlen, &nouid); + if (!opt.check_sigs && nouid) + sigrc = '?'; /* No key in local keyring. */ + } + else + { + siguid = NULL; + siguidlen = 0; + } + + + es_fputs (sigstr, es_stdout); + es_putc (':', es_stdout); + if (sigrc != ' ') + es_putc (sigrc, es_stdout); + es_fprintf (es_stdout, "::%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) + es_fprintf (es_stdout, "%d %d", sig->trust_depth, sig->trust_value); + es_fprintf (es_stdout, ":"); + + if (sig->trust_regexp) + es_write_sanitized (es_stdout, sig->trust_regexp, + strlen (sig->trust_regexp), ":", NULL); + es_fprintf (es_stdout, ":"); + + if (sigrc == '%') + es_fprintf (es_stdout, "[%s] ", gpg_strerror (rc)); + else if (siguid) + es_write_sanitized (es_stdout, siguid, siguidlen, ":", NULL); + + es_fprintf (es_stdout, ":%02x%c", sig->sig_class, + sig->flags.exportable ? 'x' : 'l'); + if (reason_text) + es_fprintf (es_stdout, ",%02x", reason_code); + es_fputs ("::", es_stdout); + + if (opt.no_sig_cache && opt.check_sigs && fprokay) + { + for (i = 0; i < fplen; i++) + es_fprintf (es_stdout, "%02X", fparray[i]); + } + else if ((issuer_fpr = issuer_fpr_string (sig))) + es_fputs (issuer_fpr, es_stdout); + + es_fprintf (es_stdout, ":::%d:", sig->digest_algo); + + if (reason_comment) + { + es_fputs ("::::", es_stdout); + es_write_sanitized (es_stdout, reason_comment, reason_commentlen, + ":", NULL); + es_putc (':', es_stdout); + } + es_putc ('\n', es_stdout); + + if (opt.show_subpackets) + print_subpackets_colon (sig); + + /* fixme: check or list other sigs here */ + xfree (reason_text); + xfree (reason_comment); + xfree (siguid); + xfree (issuer_fpr); + } + } + + xfree (curve); + xfree (hexgrip_buffer); + xfree (serialno); +} + +/* + * 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->flags.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; + } + log_assert (node); + log_assert (last); /* The user ID is never the first packet. */ + log_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); +} + +static void +list_keyblock (ctrl_t ctrl, + KBNODE keyblock, int secret, int has_secret, int fpr, + struct keylist_context *listctx) +{ + reorder_keyblock (keyblock); + + if (opt.with_colons) + list_keyblock_colon (ctrl, keyblock, secret, has_secret); + else if ((opt.list_options & LIST_SHOW_ONLY_FPR_MBOX)) + { + if (!listctx->no_validity) + check_trustdb_stale (ctrl); + list_keyblock_simple (ctrl, keyblock); + } + else + list_keyblock_print (ctrl, keyblock, secret, fpr, listctx); + + if (secret) + es_fflush (es_stdout); +} + + +/* Public function used by keygen to list a keyblock. If NO_VALIDITY + * is set the validity of a key is never shown. */ +void +list_keyblock_direct (ctrl_t ctrl, + kbnode_t keyblock, int secret, int has_secret, int fpr, + int no_validity) +{ + struct keylist_context listctx; + + memset (&listctx, 0, sizeof (listctx)); + listctx.no_validity = !!no_validity; + list_keyblock (ctrl, keyblock, secret, has_secret, fpr, &listctx); + keylist_context_release (&listctx); +} + + +/* Print an hex digit in ICAO spelling. */ +static void +print_icao_hexdigit (estream_t fp, int c) +{ + static const char *list[16] = { + "Zero", "One", "Two", "Three", "Four", "Five", "Six", "Seven", + "Eight", "Niner", "Alfa", "Bravo", "Charlie", "Delta", "Echo", "Foxtrot" + }; + + tty_fprintf (fp, "%s", list[c&15]); +} + + +/* + * 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. + * 4: direct use of tty but only subkey. + * 10: Same as 0 but with_colons etc is ignored. + * 20: Same as 0 but using a compact format. + * + * Modes 1 and 2 will try and print both subkey and primary key + * fingerprints. A MODE with bit 7 set is used internally. If + * OVERRIDE_FP is not NULL that stream will be used in 0 instead + * of es_stdout or instead of the TTY in modes 2 and 3. + */ +void +print_fingerprint (ctrl_t ctrl, estream_t override_fp, + PKT_public_key *pk, int mode) +{ + char hexfpr[2*MAX_FINGERPRINT_LEN+1]; + char *p; + size_t i; + estream_t fp; + const char *text; + int primary = 0; + int with_colons = opt.with_colons; + int with_icao = opt.with_icao_spelling; + int compact = 0; + + if (mode == 10) + { + mode = 0; + with_colons = 0; + with_icao = 0; + } + else if (mode == 20) + { + mode = 0; + with_colons = 0; + compact = 1; + } + + if (!opt.fingerprint && !opt.with_fingerprint + && opt.with_subkey_fingerprint) + compact = 1; + + 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)) + { + PKT_public_key *primary_pk = xmalloc_clear (sizeof (*primary_pk)); + get_pubkey (ctrl, primary_pk, pk->main_keyid); + print_fingerprint (ctrl, override_fp, primary_pk, (mode | 0x80)); + free_public_key (primary_pk); + } + + if (mode == 1) + { + fp = log_get_stream (); + if (primary) + text = _("Primary key fingerprint:"); + else + text = _(" Subkey fingerprint:"); + } + else if (mode == 2) + { + fp = override_fp; /* Use tty or given stream. */ + if (primary) + /* TRANSLATORS: this should fit into 24 bytes so that the + * fingerprint data is properly aligned with the user ID */ + text = _(" Primary key fingerprint:"); + else + text = _(" Subkey fingerprint:"); + } + else if (mode == 3) + { + fp = override_fp; /* Use tty or given stream. */ + text = _(" Key fingerprint ="); + } + else if (mode == 4) + { + fp = override_fp; /* Use tty or given stream. */ + text = _(" Subkey fingerprint:"); + } + else + { + fp = override_fp? override_fp : es_stdout; + if (opt.keyid_format == KF_NONE) + { + text = " "; /* To indent ICAO spelling. */ + compact = 1; + } + else + text = _(" Key fingerprint ="); + } + + hexfingerprint (pk, hexfpr, sizeof hexfpr); + if (with_colons && !mode) + { + es_fprintf (fp, "fpr:::::::::%s:", hexfpr); + } + else if (compact && !opt.fingerprint && !opt.with_fingerprint) + { + tty_fprintf (fp, "%*s%s", 6, "", hexfpr); + } + else + { + char fmtfpr[MAX_FORMATTED_FINGERPRINT_LEN + 1]; + format_hexfingerprint (hexfpr, fmtfpr, sizeof fmtfpr); + if (compact) + tty_fprintf (fp, "%*s%s", 6, "", fmtfpr); + else + tty_fprintf (fp, "%s %s", text, fmtfpr); + } + tty_fprintf (fp, "\n"); + if (!with_colons && with_icao) + { + ; + tty_fprintf (fp, "%*s\"", (int)strlen(text)+1, ""); + for (i = 0, p = hexfpr; *p; i++, p++) + { + if (!i) + ; + else if (!(i%8)) + tty_fprintf (fp, "\n%*s ", (int)strlen(text)+1, ""); + else if (!(i%4)) + tty_fprintf (fp, " "); + else + tty_fprintf (fp, " "); + print_icao_hexdigit (fp, xtoi_1 (p)); + } + tty_fprintf (fp, "\"\n"); + } +} + +/* Print the serial number of an OpenPGP card if available. */ +static void +print_card_serialno (const char *serialno) +{ + if (!serialno) + return; + if (opt.with_colons) + return; /* Handled elsewhere. */ + + es_fputs (_(" Card serial no. ="), es_stdout); + es_putc (' ', es_stdout); + if (strlen (serialno) == 32 && !strncmp (serialno, "D27600012401", 12)) + { + /* This is an OpenPGP card. Print the relevant part. */ + /* Example: D2760001240101010001000003470000 */ + /* xxxxyyyyyyyy */ + es_fprintf (es_stdout, "%.*s %.*s", 4, serialno+16, 8, serialno+20); + } + else + es_fputs (serialno, es_stdout); + es_putc ('\n', es_stdout); +} + + +/* Print a public or secret (sub)key line. Example: + * + * pub dsa2048 2007-12-31 [SC] [expires: 2018-12-31] + * 80615870F5BAD690333686D0F2AD85AC1E42B367 + * + * Some global options may result in a different output format. If + * SECRET is set, "sec" or "ssb" is used instead of "pub" or "sub" and + * depending on the value a flag character is shown: + * + * 1 := ' ' Regular secret key + * 2 := '#' Stub secret key + * 3 := '>' Secret key is on a token. + */ +void +print_key_line (ctrl_t ctrl, estream_t fp, PKT_public_key *pk, int secret) +{ + char pkstrbuf[PUBKEY_STRING_SIZE]; + + tty_fprintf (fp, "%s%c %s", + pk->flags.primary? (secret? "sec":"pub") + /**/ : (secret? "ssb":"sub"), + secret == 2? '#' : secret == 3? '>' : ' ', + pubkey_string (pk, pkstrbuf, sizeof pkstrbuf)); + if (opt.keyid_format != KF_NONE) + tty_fprintf (fp, "/%s", keystr_from_pk (pk)); + tty_fprintf (fp, " %s", datestr_from_pk (pk)); + + if (pk->flags.primary + && !(openpgp_pk_algo_usage (pk->pubkey_algo) + & (PUBKEY_USAGE_CERT| PUBKEY_USAGE_SIG|PUBKEY_USAGE_AUTH))) + { + /* A primary key which is really not capable to sign. */ + tty_fprintf (fp, " [INVALID_ALGO]"); + } + else if ((opt.list_options & LIST_SHOW_USAGE)) + { + tty_fprintf (fp, " [%s]", usagestr_from_pk (pk, 0)); + } + + if (pk->flags.revoked) + { + tty_fprintf (fp, " ["); + tty_fprintf (fp, _("revoked: %s"), revokestr_from_pk (pk)); + tty_fprintf (fp, "]"); + } + else if (pk->has_expired) + { + tty_fprintf (fp, " ["); + tty_fprintf (fp, _("expired: %s"), expirestr_from_pk (pk)); + tty_fprintf (fp, "]"); + } + else if (pk->expiredate) + { + tty_fprintf (fp, " ["); + tty_fprintf (fp, _("expires: %s"), expirestr_from_pk (pk)); + tty_fprintf (fp, "]"); + } + +#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 (ctrl, pk, NULL, NULL, 0); + tty_fprintf (fp, " [%s]", trust_value_to_string (validity)); + } +#endif + + if (pk->pubkey_algo >= 100) + tty_fprintf (fp, " [experimental algorithm %d]", pk->pubkey_algo); + + tty_fprintf (fp, "\n"); + + /* if the user hasn't explicitly asked for human-readable + fingerprints, show compact fpr of primary key: */ + if (pk->flags.primary && + !opt.fingerprint && !opt.with_fingerprint) + print_fingerprint (ctrl, fp, pk, 20); +} + + +void +set_attrib_fd (int fd) +{ + static int last_fd = -1; + + if (fd != -1 && last_fd == fd) + return; + + /* Fixme: Do we need to check for the log stream here? */ + if (attrib_fp && attrib_fp != log_get_stream ()) + es_fclose (attrib_fp); + attrib_fp = NULL; + if (fd == -1) + return; + + if (! gnupg_fd_valid (fd)) + log_fatal ("attribute-fd is invalid: %s\n", strerror (errno)); + +#ifdef HAVE_DOSISH_SYSTEM + setmode (fd, O_BINARY); +#endif + if (fd == 1) + attrib_fp = es_stdout; + else if (fd == 2) + attrib_fp = es_stderr; + else + attrib_fp = es_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..8c31ccc --- /dev/null +++ b/g10/keyring.c @@ -0,0 +1,1747 @@ +/* keyring.c - keyring file handling + * Copyright (C) 1998-2010 Free Software Foundation, Inc. + * Copyright (C) 1997-2015 Werner Koch + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gpg.h" +#include "../common/util.h" +#include "keyring.h" +#include "packet.h" +#include "keydb.h" +#include "options.h" +#include "main.h" /*for check_key_signature()*/ +#include "../common/i18n.h" +#include "../kbx/keybox.h" + + +typedef struct keyring_resource *KR_RESOURCE; +struct keyring_resource +{ + struct keyring_resource *next; + int read_only; + dotlock_t lockhd; + int is_locked; + int did_full_scan; + char fname[1]; +}; +typedef struct keyring_resource const * CONST_KR_RESOURCE; + +static KR_RESOURCE kr_resources; + +struct keyring_handle +{ + CONST_KR_RESOURCE resource; + struct { + CONST_KR_RESOURCE kr; + IOBUF iobuf; + int eof; + int error; + } current; + struct { + CONST_KR_RESOURCE kr; + off_t offset; + size_t pk_no; + size_t uid_no; + unsigned int n_packets; /*used for delete and update*/ + } found, saved_found; + struct { + char *name; + char *pattern; + } word_match; +}; + +/* The number of extant handles. */ +static int active_handles; + +static int do_copy (int mode, const char *fname, KBNODE root, + off_t start_offset, unsigned int n_packets ); + + + +/* We keep a cache of entries that we have entered in the DB. This + includes not only public keys, but also subkeys. + + Note: we'd like to keep the offset of the items that are present, + however, this doesn't work, because another concurrent GnuPG + process could modify the keyring. */ +struct key_present { + struct key_present *next; + u32 kid[2]; +}; + +/* For the hash table, we use separate chaining with linked lists. + This means that we have an array of N linked lists (buckets), which + is indexed by KEYID[1] mod N. Elements present in the keyring will + be on the list; elements not present in the keyring will not be on + the list. + + Note: since the hash table stores both present and not present + information, it cannot be used until we complete a full scan of the + keyring. This is indicated by key_present_hash_ready. */ +typedef struct key_present **key_present_hash_t; +static key_present_hash_t key_present_hash; +static int key_present_hash_ready; + +#define KEY_PRESENT_HASH_BUCKETS 2048 + +/* Allocate a new value for a key present hash table. */ +static struct key_present * +key_present_value_new (void) +{ + struct key_present *k; + + k = xmalloc_clear (sizeof *k); + return k; +} + +/* Allocate a new key present hash table. */ +static key_present_hash_t +key_present_hash_new (void) +{ + struct key_present **tbl; + + tbl = xmalloc_clear (KEY_PRESENT_HASH_BUCKETS * sizeof *tbl); + return tbl; +} + +/* Return whether the value described by KID if it is in the hash + table. Otherwise, return NULL. */ +static struct key_present * +key_present_hash_lookup (key_present_hash_t tbl, u32 *kid) +{ + struct key_present *k; + + for (k = tbl[(kid[1] % (KEY_PRESENT_HASH_BUCKETS - 1))]; k; k = k->next) + if (k->kid[0] == kid[0] && k->kid[1] == kid[1]) + return k; + return NULL; +} + +/* Add the key to the hash table TBL if it is not already present. */ +static void +key_present_hash_update (key_present_hash_t tbl, u32 *kid) +{ + struct key_present *k; + + for (k = tbl[(kid[1] % (KEY_PRESENT_HASH_BUCKETS - 1))]; k; k = k->next) + { + if (k->kid[0] == kid[0] && k->kid[1] == kid[1]) + return; + } + + k = key_present_value_new (); + k->kid[0] = kid[0]; + k->kid[1] = kid[1]; + k->next = tbl[(kid[1] % (KEY_PRESENT_HASH_BUCKETS - 1))]; + tbl[(kid[1] % (KEY_PRESENT_HASH_BUCKETS - 1))] = k; +} + +/* Add all the keys (public and subkeys) present in the keyblock to + the hash TBL. */ +static void +key_present_hash_update_from_kb (key_present_hash_t 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); + key_present_hash_update (tbl, aki); + } + } +} + +/* + * 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 read_only, void **ptr) +{ + KR_RESOURCE kr; + + if (active_handles) + /* There are open handles. */ + BUG (); + + for (kr=kr_resources; kr; kr = kr->next) + { + if (same_file_p (kr->fname, fname)) + { + /* Already registered. */ + if (read_only) + kr->read_only = 1; + *ptr=kr; + return 0; + } + } + + kr = xmalloc (sizeof *kr + strlen (fname)); + strcpy (kr->fname, fname); + kr->read_only = read_only; + kr->lockhd = NULL; + kr->is_locked = 0; + kr->did_full_scan = 0; + /* keep a list of all issued pointers */ + kr->next = kr_resources; + kr_resources = kr; + + /* create the offset table the first time a function here is used */ + if (!key_present_hash) + key_present_hash = key_present_hash_new (); + + *ptr=kr; + + return 1; +} + +int +keyring_is_writable (void *token) +{ + KR_RESOURCE r = token; + + return r? (r->read_only || !gnupg_access (r->fname, W_OK)) : 0; +} + + + +/* Create a new handle for the resource associated with TOKEN. + On error NULL is returned and ERRNO is set. + The returned handle must be released using keyring_release (). */ +KEYRING_HANDLE +keyring_new (void *token) +{ + KEYRING_HANDLE hd; + KR_RESOURCE resource = token; + + log_assert (resource); + + hd = xtrycalloc (1, sizeof *hd); + if (!hd) + return hd; + hd->resource = resource; + active_handles++; + return hd; +} + +void +keyring_release (KEYRING_HANDLE hd) +{ + if (!hd) + return; + log_assert (active_handles > 0); + active_handles--; + xfree (hd->word_match.name); + xfree (hd->word_match.pattern); + iobuf_close (hd->current.iobuf); + xfree (hd); +} + + +/* Save the current found state in HD for later retrieval by + keybox_pop_found_state. Only one state may be saved. */ +void +keyring_push_found_state (KEYRING_HANDLE hd) +{ + hd->saved_found = hd->found; + hd->found.kr = NULL; +} + + +/* Restore the saved found state in HD. */ +void +keyring_pop_found_state (KEYRING_HANDLE hd) +{ + hd->found = hd->saved_found; + hd->saved_found.kr = NULL; +} + + +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 unlock if YES is false. + * We ignore the handle and lock all registered files. + */ +int +keyring_lock (KEYRING_HANDLE hd, int yes) +{ + KR_RESOURCE kr; + int rc = 0; + + (void)hd; + + if (yes) { + /* first make sure the lock handles are created */ + for (kr=kr_resources; kr; kr = kr->next) { + if (!keyring_is_writable(kr)) + continue; + if (!kr->lockhd) { + kr->lockhd = dotlock_create (kr->fname, 0); + if (!kr->lockhd) { + log_info ("can't allocate lock for '%s'\n", kr->fname ); + rc = GPG_ERR_GENERAL; + } + } + } + if (rc) + return rc; + + /* and now set the locks */ + for (kr=kr_resources; kr; kr = kr->next) { + if (!keyring_is_writable(kr)) + continue; + if (kr->is_locked) + continue; + +#ifdef HAVE_W32_SYSTEM + /* Under Windows we need to CloseHandle the file before we + * try to lock it. This is because another process might + * have taken the lock and is using keybox_file_rename to + * rename the base file. How if our dotlock_take below is + * waiting for the lock but we have the base file still + * open, keybox_file_rename will never succeed as we are + * in a deadlock. */ + iobuf_ioctl (NULL, IOBUF_IOCTL_INVALIDATE_CACHE, 0, + (char*)kr->fname); +#endif /*HAVE_W32_SYSTEM*/ + if (dotlock_take (kr->lockhd, -1) ) { + log_info ("can't lock '%s'\n", kr->fname ); + rc = GPG_ERR_GENERAL; + } + else + kr->is_locked = 1; + } + } + + if (rc || !yes) { + for (kr=kr_resources; kr; kr = kr->next) { + if (!keyring_is_writable(kr)) + continue; + if (!kr->is_locked) + continue; + + if (dotlock_release (kr->lockhd)) + log_info ("can't unlock '%s'\n", kr->fname ); + else + kr->is_locked = 0; + } + } + + return rc; +} + + + +/* + * Return the last found keyblock. 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; + struct parse_packet_ctx_s parsectx; + 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 GPG_ERR_KEYRING_OPEN; + } + + if (iobuf_seek (a, hd->found.offset) ) { + log_error ("can't seek '%s'\n", hd->found.kr->fname); + iobuf_close(a); + return GPG_ERR_KEYRING_OPEN; + } + + pkt = xmalloc (sizeof *pkt); + init_packet (pkt); + init_parse_packet (&parsectx, a); + hd->found.n_packets = 0; + lastnode = NULL; + save_mode = set_packet_list_mode(0); + while ((rc=parse_packet (&parsectx, pkt)) != -1) { + hd->found.n_packets = parsectx.n_parsed_packets; + if (gpg_err_code (rc) == GPG_ERR_UNKNOWN_PACKET) { + free_packet (pkt, &parsectx); + init_packet (pkt); + continue; + } + if (gpg_err_code (rc) == GPG_ERR_LEGACY_KEY) + { + if (in_cert) + /* It is not this key that is problematic, but the + following key. */ + { + rc = 0; + hd->found.n_packets --; + } + else + /* Upper layer needs to handle this. */ + { + } + break; + } + if (rc) { + log_error ("keyring_get_keyblock: read error: %s\n", + gpg_strerror (rc) ); + rc = GPG_ERR_INV_KEYRING; + break; + } + + /* Filter allowed packets. */ + switch (pkt->pkttype) + { + case PKT_PUBLIC_KEY: + case PKT_PUBLIC_SUBKEY: + case PKT_SECRET_KEY: + case PKT_SECRET_SUBKEY: + case PKT_USER_ID: + case PKT_ATTRIBUTE: + case PKT_SIGNATURE: + break; /* Allowed per RFC. */ + case PKT_RING_TRUST: + case PKT_OLD_COMMENT: + case PKT_COMMENT: + case PKT_GPG_CONTROL: + break; /* Allowed by us. */ + + default: + log_info ("skipped packet of type %d in keyring\n", + (int)pkt->pkttype); + free_packet(pkt, &parsectx); + 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; + node = new_kbnode (pkt); + if (!keyblock) + keyblock = lastnode = node; + else + { + lastnode->next = node; + lastnode = node; + } + switch (pkt->pkttype) + { + case PKT_PUBLIC_KEY: + case PKT_PUBLIC_SUBKEY: + case PKT_SECRET_KEY: + case PKT_SECRET_SUBKEY: + if (++pk_no == hd->found.pk_no) + node->flag |= 1; + break; + + case PKT_USER_ID: + if (++uid_no == hd->found.uid_no) + node->flag |= 2; + break; + + default: + break; + } + + 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 { + *ret_kb = keyblock; + } + free_packet (pkt, &parsectx); + deinit_parse_packet (&parsectx); + 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 (gpg_err_code (rc) == GPG_ERR_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.kr->read_only) + return gpg_error (GPG_ERR_EACCES); + + 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", gpg_strerror (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->found.offset, hd->found.n_packets ); + if (!rc) { + if (key_present_hash) + { + key_present_hash_update_from_kb (key_present_hash, kb); + } + /* 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; + if (hd->found.kr->read_only) + return gpg_error (GPG_ERR_EACCES); + } + else if (hd->current.kr) + { + fname = hd->current.kr->fname; + if (hd->current.kr->read_only) + return gpg_error (GPG_ERR_EACCES); + } + else + fname = hd->resource? hd->resource->fname:NULL; + + if (!fname) + return GPG_ERR_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, 0, 0 ); + if (!rc && key_present_hash) + { + key_present_hash_update_from_kb (key_present_hash, kb); + } + + return rc; +} + + +int +keyring_delete_keyblock (KEYRING_HANDLE hd) +{ + int rc; + + if (!hd->found.kr) + return -1; /* no successful prior search */ + + if (hd->found.kr->read_only) + return gpg_error (GPG_ERR_EACCES); + + 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", gpg_strerror (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->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) +{ + log_assert (hd); + + 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; + + if (hd->current.kr) + iobuf_ioctl (NULL, IOBUF_IOCTL_INVALIDATE_CACHE, 0, + (char*)hd->current.kr->fname); + hd->current.kr = NULL; + + return 0; +} + + +static int +prepare_search (KEYRING_HANDLE hd) +{ + if (hd->current.error) { + /* If the last key was a legacy key, we simply ignore the error so that + we can easily use search_next. */ + if (gpg_err_code (hd->current.error) == GPG_ERR_LEGACY_KEY) + { + if (DBG_LOOKUP) + log_debug ("%s: last error was GPG_ERR_LEGACY_KEY, clearing\n", + __func__); + hd->current.error = 0; + } + else + { + if (DBG_LOOKUP) + log_debug ("%s: returning last error: %s\n", + __func__, gpg_strerror (hd->current.error)); + return hd->current.error; /* still in error state */ + } + } + + if (hd->current.kr && !hd->current.eof) { + if ( !hd->current.iobuf ) + { + if (DBG_LOOKUP) + log_debug ("%s: missing iobuf!\n", __func__); + return GPG_ERR_GENERAL; /* Position invalid after a modify. */ + } + return 0; /* okay */ + } + + if (!hd->current.kr && hd->current.eof) + { + if (DBG_LOOKUP) + log_debug ("%s: EOF!\n", __func__); + return -1; /* still EOF */ + } + + if (!hd->current.kr) { /* start search with first keyring */ + hd->current.kr = hd->resource; + if (!hd->current.kr) { + if (DBG_LOOKUP) + log_debug ("%s: keyring not available!\n", __func__); + hd->current.eof = 1; + return -1; /* keyring not available */ + } + log_assert (!hd->current.iobuf); + } + else { /* EOF */ + if (DBG_LOOKUP) + log_debug ("%s: EOF\n", __func__); + 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) + { + hd->current.error = gpg_error_from_syserror (); + log_error(_("can't open '%s'\n"), hd->current.kr->fname ); + return hd->current.error; + } + + return 0; +} + + +/* A map of the all characters valid used for word_match() + * Valid characters are 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) { + int have_angles = 1; + for (i=0, s= uid; i < uidlen && *s != '<'; s++, i++) + ; + if (i == uidlen) + { + /* The UID is a plain addr-spec (cf. RFC2822 section 4.3). */ + have_angles = 0; + s = uid; + i = 0; + } + if (i < uidlen) { + if (have_angles) + { + /* skip opening delim and one char and look for the closing one*/ + s++; i++; + for (se=s+1, i++; i < uidlen && *se != '>'; se++, i++) + ; + } + else + se = s + uidlen; + + 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 ignore_legacy) +{ + int rc; + PACKET pkt; + struct parse_packet_ctx_s parsectx; + 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 scanned_from_start; + int use_key_present_hash; + PKT_user_id *uid = NULL; + PKT_public_key *pk = 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; + } + } + + if (DBG_LOOKUP) + log_debug ("%s: need_uid = %d; need_words = %d; need_keyid = %d; need_fpr = %d; any_skip = %d\n", + __func__, need_uid, need_words, need_keyid, need_fpr, any_skip); + + rc = prepare_search (hd); + if (rc) + { + if (DBG_LOOKUP) + log_debug ("%s: prepare_search failed: %s (%d)\n", + __func__, gpg_strerror (rc), gpg_err_code (rc)); + return rc; + } + + use_key_present_hash = !!key_present_hash; + if (!use_key_present_hash) + { + if (DBG_LOOKUP) + log_debug ("%s: no offset table.\n", __func__); + } + else if (!key_present_hash_ready) + { + if (DBG_LOOKUP) + log_debug ("%s: initializing offset table. (need_keyid: %d => 1)\n", + __func__, need_keyid); + need_keyid = 1; + } + else if (ndesc == 1 && desc[0].mode == KEYDB_SEARCH_MODE_LONG_KID) + { + struct key_present *oi; + + if (DBG_LOOKUP) + log_debug ("%s: look up by long key id, checking cache\n", __func__); + + oi = key_present_hash_lookup (key_present_hash, desc[0].u.kid); + if (!oi) + { /* We know that we don't have this key */ + if (DBG_LOOKUP) + log_debug ("%s: cache says not present\n", __func__); + 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; + } + log_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 */ + scanned_from_start = iobuf_tell (hd->current.iobuf) == 0; + if (DBG_LOOKUP) + log_debug ("%s: %ssearching from start of resource.\n", + __func__, scanned_from_start ? "" : "not "); + init_parse_packet (&parsectx, hd->current.iobuf); + while (1) + { + byte afp[MAX_FINGERPRINT_LEN]; + size_t an; + + rc = search_packet (&parsectx, &pkt, &offset, need_uid); + if (ignore_legacy && gpg_err_code (rc) == GPG_ERR_LEGACY_KEY) + { + free_packet (&pkt, &parsectx); + continue; + } + if (rc) + break; + + 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, &parsectx); + continue; + } + + pk = NULL; + uid = NULL; + if ( pkt.pkttype == PKT_PUBLIC_KEY + || pkt.pkttype == PKT_PUBLIC_SUBKEY + || pkt.pkttype == PKT_SECRET_KEY + || pkt.pkttype == PKT_SECRET_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_key_present_hash + && !key_present_hash_ready + && scanned_from_start) + key_present_hash_update (key_present_hash, aki); + } + else if (pkt.pkttype == PKT_USER_ID) + { + uid = pkt.pkt.user_id; + ++uid_no; + } + + 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 && desc[n].u.kid[1] == aki[1]) + goto found; + break; + case KEYDB_SEARCH_MODE_LONG_KID: + if (pk && desc[n].u.kid[0] == aki[0] + && desc[n].u.kid[1] == aki[1]) + goto found; + break; + case KEYDB_SEARCH_MODE_FPR16: + if (pk && !memcmp (desc[n].u.fpr, afp, 16)) + goto found; + break; + case KEYDB_SEARCH_MODE_FPR20: + case KEYDB_SEARCH_MODE_FPR: + if (pk && !memcmp (desc[n].u.fpr, afp, 20)) + goto found; + break; + case KEYDB_SEARCH_MODE_FIRST: + if (pk) + goto found; + break; + case KEYDB_SEARCH_MODE_NEXT: + if (pk) + goto found; + break; + default: + rc = GPG_ERR_INV_ARG; + goto found; + } + } + free_packet (&pkt, &parsectx); + continue; + found: + if (rc) + goto real_found; + + if (DBG_LOOKUP) + log_debug ("%s: packet starting at offset %lld matched descriptor %zu\n" + , __func__, (long long)offset, n); + + /* 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_no)) + { + if (DBG_LOOKUP) + log_debug ("%s: skipping match: desc %zd's skip function returned TRUE\n", + __func__, n); + break; + } + } + if (n == ndesc) + goto real_found; + free_packet (&pkt, &parsectx); + } + real_found: + if (!rc) + { + if (DBG_LOOKUP) + log_debug ("%s: returning success\n", __func__); + hd->found.offset = main_offset; + hd->found.kr = hd->current.kr; + hd->found.pk_no = pk? pk_no : 0; + hd->found.uid_no = uid? uid_no : 0; + } + else if (rc == -1) + { + if (DBG_LOOKUP) + log_debug ("%s: no matches (EOF)\n", __func__); + + 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_key_present_hash + && !key_present_hash_ready + && scanned_from_start) + { + KR_RESOURCE kr; + + /* First set the did_full_scan flag for this keyring. */ + for (kr=kr_resources; kr; kr = kr->next) + { + if (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_resources; kr; kr = kr->next) + { + if (!kr->did_full_scan) + break; + } + if (!kr) + key_present_hash_ready = 1; + } + } + else + { + if (DBG_LOOKUP) + log_debug ("%s: error encountered during search: %s (%d)\n", + __func__, gpg_strerror (rc), rc); + hd->current.error = rc; + } + + free_packet (&pkt, &parsectx); + deinit_parse_packet (&parsectx); + 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) +{ + gpg_error_t err; + mode_t oldmask; + + err = keybox_tmp_names (template, 1, r_bakfname, r_tmpfname); + if (err) + return err; + + /* Create the temp file with limited access. Note that the umask + call is not anymore needed because iobuf_create now takes care of + it. However, it does not harm and thus we keep it. */ + oldmask = umask (077); + if (is_secured_filename (*r_tmpfname)) + { + *r_fp = NULL; + gpg_err_set_errno (EPERM); + } + else + *r_fp = iobuf_create (*r_tmpfname, 1); + umask (oldmask); + if (!*r_fp) + { + err = gpg_error_from_syserror (); + log_error (_("can't create '%s': %s\n"), *r_tmpfname, gpg_strerror (err)); + xfree (*r_tmpfname); + *r_tmpfname = NULL; + xfree (*r_bakfname); + *r_bakfname = NULL; + } + + return err; +} + + +static int +rename_tmp_file (const char *bakfname, const char *tmpfname, const char *fname) +{ + int rc = 0; + int block = 0; + + /* Invalidate close caches. */ + if (iobuf_ioctl (NULL, IOBUF_IOCTL_INVALIDATE_CACHE, 0, (char*)tmpfname )) + { + rc = gpg_error_from_syserror (); + goto fail; + } + iobuf_ioctl (NULL, IOBUF_IOCTL_INVALIDATE_CACHE, 0, (char*)bakfname ); + iobuf_ioctl (NULL, IOBUF_IOCTL_INVALIDATE_CACHE, 0, (char*)fname ); + + /* First make a backup file. */ + block = 1; + rc = gnupg_rename_file (fname, bakfname, &block); + if (rc) + goto fail; + + /* then rename the file */ + rc = gnupg_rename_file (tmpfname, fname, NULL); + if (block) + { + gnupg_unblock_all_signals (); + block = 0; + } + if (rc) + { + register_secured_file (fname); + goto fail; + } + + /* 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 (!gnupg_stat (bakfname, &statbuf) && !chmod (fname, statbuf.st_mode)) + ; + else + log_error ("WARNING: unable to restore permissions to '%s': %s", + fname, strerror(errno)); + } +#endif + + return 0; + + fail: + if (block) + gnupg_unblock_all_signals (); + return rc; +} + + +static int +write_keyblock (IOBUF fp, KBNODE keyblock) +{ + KBNODE kbctx = NULL, node; + int rc; + + while ( (node = walk_kbnode (keyblock, &kbctx, 0)) ) + { + if ( (rc = build_packet_and_meta (fp, node->pkt) )) + { + log_error ("build_packet(%d) failed: %s\n", + node->pkt->pkttype, gpg_strerror (rc) ); + return rc; + } + } + 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 (ctrl_t ctrl, 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); + if (!hd) + return gpg_error_from_syserror (); + memset (&desc, 0, sizeof desc); + desc.mode = KEYDB_SEARCH_MODE_FIRST; + + rc=keyring_lock (hd, 1); + if(rc) + goto leave; + + for (;;) + { + rc = keyring_search (hd, &desc, 1, NULL, 1 /* ignore_legacy */); + if (rc) + break; /* ready. */ + + 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)) + { + rc = gpg_error_from_syserror (); + log_error ("error closing '%s': %s\n", + tmpfilename, strerror (errno)); + goto leave; + } + /* because we have switched resources, we can be sure that + * the original file is closed */ + tmpfp = NULL; + } + /* Static analyzer note: BAKFILENAME is never NULL here + because it is controlled by LASTRESNAME. */ + rc = lastresname? rename_tmp_file (bakfilename, tmpfilename, + lastresname) : 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) + { + if (gpg_err_code (rc) == GPG_ERR_LEGACY_KEY) + continue; /* Skip legacy keys. */ + if (gpg_err_code (rc) == GPG_ERR_UNKNOWN_VERSION) + continue; /* Skip keys with unknown version. */ + log_error ("keyring_get_keyblock failed: %s\n", gpg_strerror (rc)); + goto leave; + } + if ( keyblock->pkt->pkttype != PKT_PUBLIC_KEY) + { + /* We had a few reports about corrupted keyrings; if we have + been called directly from the command line we delete such + a keyblock instead of bailing out. */ + log_error ("unexpected keyblock found (pkttype=%d)%s\n", + keyblock->pkt->pkttype, noisy? " - deleted":""); + if (noisy) + continue; + log_info ("Hint: backup your keys and try running '%s'\n", + "gpg --rebuild-keydb-caches"); + rc = gpg_error (GPG_ERR_INV_KEYRING); + goto leave; + } + + if (keyblock->pkt->pkt.public_key->version < 4) + { + /* We do not copy/cache v3 keys or any other unknown + packets. It is better to remove them from the keyring. + The code required to keep them in the keyring would be + too complicated. Given that we do not touch the old + secring.gpg a suitable backup for decryption of v3 stuff + using an older gpg version will always be available. + Note: This test is actually superfluous because we + already acted upon GPG_ERR_LEGACY_KEY. */ + } + else + { + /* 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 + && (openpgp_md_test_algo(sig->digest_algo) + || openpgp_pk_test_algo(sig->pubkey_algo))) + sig->flags.checked=sig->flags.valid=0; + else + check_key_signature (ctrl, 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 (ngettext("%lu keys cached so far (%lu signature)\n", + "%lu keys cached so far (%lu signatures)\n", + sigcount), + count, sigcount); + } + } /* end main loop */ + if (rc == -1) + rc = 0; + if (rc) + { + log_error ("keyring_search failed: %s\n", gpg_strerror (rc)); + goto leave; + } + + if (noisy || opt.verbose) + { + log_info (ngettext("%lu key cached", + "%lu keys cached", count), count); + log_printf (ngettext(" (%lu signature)\n", + " (%lu signatures)\n", sigcount), sigcount); + } + + if (tmpfp) + { + if (iobuf_close (tmpfp)) + { + rc = gpg_error_from_syserror (); + log_error ("error closing '%s': %s\n", + tmpfilename, strerror (errno)); + 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; + 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, + off_t start_offset, unsigned int n_packets ) +{ + gpg_err_code_t ec; + 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 ((ec = gnupg_access (fname, W_OK))) + return gpg_error (ec); + + 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 (is_secured_filename (fname)) { + newfp = NULL; + gpg_err_set_errno (EPERM); + } + else + newfp = iobuf_create (fname, 1); + umask(oldmask); + if( !newfp ) + { + rc = gpg_error_from_syserror (); + log_error (_("can't create '%s': %s\n"), fname, strerror(errno)); + return rc; + } + 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, gpg_strerror (rc) ); + iobuf_cancel(newfp); + return rc; + } + } + if( iobuf_close(newfp) ) { + rc = gpg_error_from_syserror (); + log_error ("%s: close failed: %s\n", fname, strerror(errno)); + return rc; + } + return 0; /* ready */ + } + + if( !fp ) + { + rc = gpg_error_from_syserror (); + log_error(_("can't open '%s': %s\n"), fname, strerror(errno) ); + goto leave; + } + + /* Create the new file. */ + rc = create_tmp_file (fname, &bakfname, &tmpfname, &newfp); + if (rc) { + iobuf_close(fp); + goto leave; + } + + 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, gpg_strerror (rc) ); + iobuf_close(fp); + iobuf_cancel(newfp); + goto leave; + } + } + + 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, gpg_strerror (rc) ); + iobuf_close(fp); + iobuf_cancel(newfp); + goto leave; + } + /* skip this keyblock */ + log_assert( n_packets ); + rc = skip_some_packets( fp, n_packets ); + if( rc ) { + log_error("%s: skipping %u packets failed: %s\n", + fname, n_packets, gpg_strerror (rc)); + iobuf_close(fp); + iobuf_cancel(newfp); + goto leave; + } + } + + if( mode == 1 || mode == 3 ) { /* insert or update */ + rc = write_keyblock (newfp, root); + if (rc) { + iobuf_close(fp); + 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, gpg_strerror (rc) ); + iobuf_close(fp); + iobuf_cancel(newfp); + goto leave; + } + } + + /* close both files */ + if( iobuf_close(fp) ) { + rc = gpg_error_from_syserror (); + log_error("%s: close failed: %s\n", fname, strerror(errno) ); + goto leave; + } + if( iobuf_close(newfp) ) { + rc = gpg_error_from_syserror (); + log_error("%s: close failed: %s\n", tmpfname, strerror(errno) ); + goto leave; + } + + rc = rename_tmp_file (bakfname, tmpfname, fname); + + leave: + xfree(bakfname); + xfree(tmpfname); + return rc; +} diff --git a/g10/keyring.h b/g10/keyring.h new file mode 100644 index 0000000..7155d1d --- /dev/null +++ b/g10/keyring.h @@ -0,0 +1,45 @@ +/* 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 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#ifndef GPG_KEYRING_H +#define GPG_KEYRING_H 1 + +#include "../common/userids.h" + +typedef struct keyring_handle *KEYRING_HANDLE; + +int keyring_register_filename (const char *fname, int read_only, void **ptr); +int keyring_is_writable (void *token); + +KEYRING_HANDLE keyring_new (void *token); +void keyring_release (KEYRING_HANDLE hd); +void keyring_push_found_state (KEYRING_HANDLE hd); +void keyring_pop_found_state (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_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 skip_legacy); +int keyring_rebuild_cache (ctrl_t ctrl, 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..45cb936 --- /dev/null +++ b/g10/keyserver-internal.h @@ -0,0 +1,66 @@ +/* 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 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#ifndef _KEYSERVER_INTERNAL_H_ +#define _KEYSERVER_INTERNAL_H_ + +#include +#include "../common/iobuf.h" +#include "../common/types.h" + +/* Flags for the keyserver import functions. */ +#define KEYSERVER_IMPORT_FLAG_QUICK 1 +#define KEYSERVER_IMPORT_FLAG_LDAP 2 + +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); +struct keyserver_spec *parse_preferred_keyserver(PKT_signature *sig); +int keyserver_any_configured (ctrl_t ctrl); +int keyserver_export (ctrl_t ctrl, strlist_t users); +int keyserver_import (ctrl_t ctrl, strlist_t users); +int keyserver_import_fprint (ctrl_t ctrl, const byte *fprint,size_t fprint_len, + struct keyserver_spec *keyserver, + unsigned int flags); +int keyserver_import_fprint_ntds (ctrl_t ctrl, + const byte *fprint, size_t fprint_len); +int keyserver_import_keyid (ctrl_t ctrl, u32 *keyid, + struct keyserver_spec *keyserver, + unsigned int flags); +gpg_error_t keyserver_refresh (ctrl_t ctrl, strlist_t users); +gpg_error_t keyserver_search (ctrl_t ctrl, strlist_t tokens); +int keyserver_fetch (ctrl_t ctrl, strlist_t urilist, int origin); +int keyserver_import_cert (ctrl_t ctrl, const char *name, int dane_mode, + unsigned char **fpr,size_t *fpr_len); +gpg_error_t keyserver_import_pka (ctrl_t ctrl, const char *name, + unsigned char **fpr,size_t *fpr_len); +gpg_error_t keyserver_import_wkd (ctrl_t ctrl, const char *name, + unsigned int flags, + unsigned char **fpr, size_t *fpr_len); +int keyserver_import_ntds (ctrl_t ctrl, const char *name, + unsigned char **fpr,size_t *fpr_len); +int keyserver_import_mbox (ctrl_t ctrl, const char *mbox, + unsigned char **fpr,size_t *fpr_len, + struct keyserver_spec *keyserver); +int keyserver_import_ldap (ctrl_t ctrl, 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..1fbe728 --- /dev/null +++ b/g10/keyserver.c @@ -0,0 +1,2001 @@ +/* keyserver.c - generic keyserver code + * Copyright (C) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, + * 2009, 2011, 2012 Free Software Foundation, Inc. + * Copyright (C) 2014 Werner Koch + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include +#include +#include +#include +#include +#include + +#include "gpg.h" +#include "../common/iobuf.h" +#include "filter.h" +#include "keydb.h" +#include "../common/status.h" +#include "exec.h" +#include "main.h" +#include "../common/i18n.h" +#include "../common/ttyio.h" +#include "options.h" +#include "packet.h" +#include "trustdb.h" +#include "keyserver-internal.h" +#include "../common/util.h" +#include "../common/membuf.h" +#include "../common/mbox-util.h" +#include "call-dirmngr.h" + +#ifdef HAVE_W32_SYSTEM +/* It seems Vista doesn't grok X_OK and so fails access() tests. + Previous versions interpreted X_OK as F_OK anyway, so we'll just + use F_OK directly. */ +#undef X_OK +#define X_OK F_OK +#endif /* HAVE_W32_SYSTEM */ + +struct keyrec +{ + KEYDB_SEARCH_DESC desc; + u32 createtime,expiretime; + int size,flags; + byte type; + IOBUF uidbuf; + unsigned int lines; +}; + +/* Parameters for the search line handler. */ +struct search_line_handler_parm_s +{ + ctrl_t ctrl; /* The session control structure. */ + char *searchstr_disp; /* Native encoded search string or NULL. */ + KEYDB_SEARCH_DESC *desc; /* Array with search descriptions. */ + int count; /* Number of keys we are currently prepared to + handle. This is the size of the DESC array. If + it is too small, it will grow safely. */ + int validcount; /* Enable the "Key x-y of z" messages. */ + int nkeys; /* Number of processed records. */ + int any_lines; /* At least one line has been processed. */ + unsigned int numlines; /* Counter for displayed lines. */ + int eof_seen; /* EOF encountered. */ + int not_found; /* Set if no keys have been found. */ +}; + + +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}, /* MUST be the first in this array! */ + {"http-proxy", KEYSERVER_HTTP_PROXY, NULL, /* MUST be the second! */ + N_("override proxy options set for dirmngr")}, + + {"include-revoked",0,NULL,N_("include revoked keys in search results")}, + {"include-subkeys",0,NULL,N_("include subkeys when searching by key ID")}, + {"timeout", KEYSERVER_TIMEOUT, NULL, + N_("override timeout options set for dirmngr")}, + {"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 gpg_error_t keyserver_get (ctrl_t ctrl, + KEYDB_SEARCH_DESC *desc, int ndesc, + struct keyserver_spec *override_keyserver, + unsigned int flags, + unsigned char **r_fpr, size_t *r_fprlen); +static gpg_error_t keyserver_put (ctrl_t ctrl, strlist_t keyspecs); + + +/* Reasonable guess. The commonly used test key simon.josefsson.org + is larger than 32k, thus we need at least this value. */ +#define DEFAULT_MAX_CERT_SIZE 65536 + +static size_t max_cert_size=DEFAULT_MAX_CERT_SIZE; + + +static void +warn_kshelper_option(char *option, int noisy) +{ + char *p; + + if ((p=strchr (option, '='))) + *p = 0; + + if (!strcmp (option, "ca-cert-file")) + log_info ("keyserver option '%s' is obsolete; please use " + "'%s' in dirmngr.conf\n", + "ca-cert-file", "hkp-cacert"); + else if (!strcmp (option, "check-cert") + || !strcmp (option, "broken-http-proxy")) + log_info ("keyserver option '%s' is obsolete\n", option); + else if (noisy || opt.verbose) + log_info ("keyserver option '%s' is unknown\n", option); +} + + +/* Called from main to parse the args for --keyserver-options. */ +int +parse_keyserver_options(char *options) +{ + int ret=1; + char *tok; + char *max_cert=NULL; + + keyserver_opts[0].value=&max_cert; + keyserver_opts[1].value=&opt.keyserver_options.http_proxy; + + while((tok=optsep(&options))) + { + if(tok[0]=='\0') + continue; + + /* 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. */ + + 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 was + destined for a keyserver plugin as used by GnuPG < 2.1 */ + warn_kshelper_option (tok, 1); + } + } + + 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); +} + +/* Return 0 for match */ +static int +cmp_keyserver_spec(struct keyserver_spec *one, struct keyserver_spec *two) +{ + return !!ascii_strcasecmp(one->uri, two->uri); +} + + +/* 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; +} + + +/* Create a new keyserver object from STRING. Unless REQUIRE_SCHEME + * is set a missing scheme is replaced by "hkp://". The data structure + * could be much easier but in the past we parsed the URI here for the + * old 2.0 keyserver helpers - which is not anymore needed. */ +keyserver_spec_t +parse_keyserver_uri (const char *string, int require_scheme) +{ + struct keyserver_spec *keyserver; + const char *idx; + int count; + + log_assert (string); + + keyserver = xcalloc (1, sizeof *keyserver); + + /* Get the scheme */ + for(idx=string, 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 */ + keyserver->uri = xstrconcat ("hkp://", string, NULL); + } + else + { + keyserver->uri = xstrdup (string); + } + + 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); + xfree(dupe); + } + + return spec; +} + +static void +print_keyrec (ctrl_t ctrl, int number,struct keyrec *keyrec) +{ + int i; + + iobuf_writebyte(keyrec->uidbuf,0); + iobuf_flush_temp(keyrec->uidbuf); + es_printf ("(%d)\t%s ", number, iobuf_get_temp_buffer (keyrec->uidbuf)); + + if (keyrec->size>0) + es_printf ("%d bit ", keyrec->size); + + if(keyrec->type) + { + const char *str; + + str = openpgp_pk_algo_name (keyrec->type); + + if (str && strcmp (str, "?")) + es_printf ("%s ",str); + else + es_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: + es_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 via keystr(). */ + case KEYDB_SEARCH_MODE_LONG_KID: + es_printf ("key %s",keystr(keyrec->desc.u.kid)); + break; + + /* If it gave us a PGP 2.x fingerprint, not much we can do + beyond displaying it. */ + case KEYDB_SEARCH_MODE_FPR16: + es_printf ("key "); + for(i=0;i<16;i++) + es_printf ("%02X",keyrec->desc.u.fpr[i]); + break; + + /* If we get a modern fingerprint, we have the most + flexibility. */ + case KEYDB_SEARCH_MODE_FPR20: + { + u32 kid[2]; + keyid_from_fingerprint (ctrl, keyrec->desc.u.fpr,20,kid); + es_printf("key %s",keystr(kid)); + } + break; + + default: + BUG(); + break; + } + + if(keyrec->createtime>0) + { + es_printf (", "); + es_printf (_("created: %s"), strtimestamp(keyrec->createtime)); + } + + if(keyrec->expiretime>0) + { + es_printf (", "); + es_printf (_("expires: %s"), strtimestamp(keyrec->expiretime)); + } + + if (keyrec->flags&1) + es_printf (" (%s)", _("revoked")); + if(keyrec->flags&2) + es_printf (" (%s)", _("disabled")); + if(keyrec->flags&4) + es_printf (" (%s)", _("expired")); + + es_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) +{ + /* FIXME: Remove the static and put the data into the parms we use + for the caller anyway. */ + 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(); + } + + trim_trailing_ws (keystring, strlen (keystring)); + + if((record=strsep(&keystring,":"))==NULL) + return ret; + + if(ascii_strcasecmp("pub",record)==0) + { + char *tok; + gpg_error_t err; + + if(work->desc.mode) + { + ret=work; + work=xmalloc_clear(sizeof(struct keyrec)); + work->uidbuf=iobuf_temp(); + } + + if((tok=strsep(&keystring,":"))==NULL) + return ret; + + err = classify_user_id (tok, &work->desc, 1); + if (err || (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]) + { + int c; + + userid[i] = (c=hextobyte(&tok[1])) == -1 ? '?' : c; + 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; +} + +/* Show a prompt and allow the user to select keys for retrieval. */ +static gpg_error_t +show_prompt (ctrl_t ctrl, KEYDB_SEARCH_DESC *desc, int numdesc, + int count, const char *search) +{ + gpg_error_t err; + char *answer = NULL; + + es_fflush (es_stdout); + + 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; + } + + again: + err = 0; + xfree (answer); + answer = cpr_get_no_help ("keysearch.prompt", + _("Enter number(s), N)ext, or Q)uit > ")); + /* control-d */ + if (answer[0]=='\x04') + { + tty_printf ("Q\n"); + answer[0] = 'q'; + } + + if (answer[0]=='q' || answer[0]=='Q') + err = gpg_error (GPG_ERR_CANCELED); + else if (atoi (answer) >= 1 && atoi (answer) <= numdesc) + { + char *split = answer; + char *num; + int numarray[50]; + int numidx = 0; + int idx; + + while ((num = strsep (&split, " ,"))) + if (atoi (num) >= 1 && atoi (num) <= numdesc) + { + if (numidx >= DIM (numarray)) + { + tty_printf ("Too many keys selected\n"); + goto again; + } + numarray[numidx++] = atoi (num); + } + + if (!numidx) + goto again; + + { + KEYDB_SEARCH_DESC *selarray; + + selarray = xtrymalloc (numidx * sizeof *selarray); + if (!selarray) + { + err = gpg_error_from_syserror (); + goto leave; + } + for (idx = 0; idx < numidx; idx++) + selarray[idx] = desc[numarray[idx]-1]; + err = keyserver_get (ctrl, selarray, numidx, NULL, 0, NULL, NULL); + xfree (selarray); + } + } + + leave: + xfree (answer); + return err; +} + + +/* This is a callback used by call-dirmngr.c to process the result of + KS_SEARCH command. If SPECIAL is 0, LINE is the actual data line + received with all escaping removed and guaranteed to be exactly one + line with stripped LF; an EOF is indicated by LINE passed as NULL. + If special is 1, the line contains the source of the information + (usually an URL). LINE may be modified after return. */ +static gpg_error_t +search_line_handler (void *opaque, int special, char *line) +{ + struct search_line_handler_parm_s *parm = opaque; + gpg_error_t err = 0; + struct keyrec *keyrec; + + if (special == 1) + { + log_info ("data source: %s\n", line); + return 0; + } + else if (special) + { + log_debug ("unknown value %d for special search callback", special); + return 0; + } + + if (parm->eof_seen && line) + { + log_debug ("ooops: unexpected data after EOF\n"); + line = NULL; + } + + /* Print the received line. */ + if (opt.with_colons && line) + { + es_printf ("%s\n", line); + } + + /* Look for an info: line. The only current info: values defined + are the version and key count. */ + if (line && !parm->any_lines && !ascii_strncasecmp ("info:", line, 5)) + { + char *str = line + 5; + char *tok; + + if ((tok = strsep (&str, ":"))) + { + 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); + return gpg_error (GPG_ERR_UNSUPPORTED_PROTOCOL); + } + } + + if ((tok = strsep (&str, ":")) + && sscanf (tok, "%d", &parm->count) == 1) + { + if (!parm->count) + parm->not_found = 1;/* Server indicated that no items follow. */ + else if (parm->count < 0) + parm->count = 10; /* Bad value - assume something reasonable. */ + else + parm->validcount = 1; /* COUNT seems to be okay. */ + } + + parm->any_lines = 1; + return 0; /* Line processing finished. */ + } + + again: + if (line) + keyrec = parse_keyrec (line); + else + { + /* Received EOF - flush data */ + parm->eof_seen = 1; + keyrec = parse_keyrec (NULL); + if (!keyrec) + { + if (!parm->nkeys) + parm->not_found = 1; /* No keys at all. */ + else + { + if (parm->nkeys != parm->count) + parm->validcount = 0; + + if (!(opt.with_colons && opt.batch)) + { + err = show_prompt (parm->ctrl, parm->desc, parm->nkeys, + parm->validcount? parm->count : 0, + parm->searchstr_disp); + return err; + } + } + } + } + + /* Save the key in the key array. */ + if (keyrec) + { + /* Allocate or enlarge the key array if needed. */ + if (!parm->desc) + { + if (parm->count < 1) + { + parm->count = 10; + parm->validcount = 0; + } + parm->desc = xtrymalloc (parm->count * sizeof *parm->desc); + if (!parm->desc) + { + err = gpg_error_from_syserror (); + iobuf_close (keyrec->uidbuf); + xfree (keyrec); + return err; + } + } + else if (parm->nkeys == parm->count) + { + /* Keyserver sent more keys than claimed in the info: line. */ + KEYDB_SEARCH_DESC *tmp; + int newcount = parm->count + 10; + + tmp = xtryrealloc (parm->desc, newcount * sizeof *parm->desc); + if (!tmp) + { + err = gpg_error_from_syserror (); + iobuf_close (keyrec->uidbuf); + xfree (keyrec); + return err; + } + parm->count = newcount; + parm->desc = tmp; + parm->validcount = 0; + } + + parm->desc[parm->nkeys] = keyrec->desc; + + if (!opt.with_colons) + { + /* SCREEN_LINES - 1 for the prompt. */ + if (parm->numlines + keyrec->lines > opt.screen_lines - 1) + { + err = show_prompt (parm->ctrl, parm->desc, parm->nkeys, + parm->validcount ? parm->count:0, + parm->searchstr_disp); + if (err) + return err; + parm->numlines = 0; + } + + print_keyrec (parm->ctrl, parm->nkeys+1, keyrec); + } + + parm->numlines += keyrec->lines; + iobuf_close (keyrec->uidbuf); + xfree (keyrec); + + parm->any_lines = 1; + parm->nkeys++; + + /* If we are here due to a flush after the EOF, run again for + the last prompt. Fixme: Make this code better readable. */ + if (parm->eof_seen) + goto again; + } + + return 0; +} + + + +int +keyserver_export (ctrl_t ctrl, strlist_t users) +{ + gpg_error_t err; + strlist_t sl=NULL; + KEYDB_SEARCH_DESC desc; + int rc=0; + + /* Weed out descriptors that we don't support sending */ + for(;users;users=users->next) + { + err = classify_user_id (users->d, &desc, 1); + if (err || (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_put (ctrl, sl); + free_strlist(sl); + } + + return rc; +} + + +/* Structure to convey the arg to keyserver_retrieval_screener. */ +struct ks_retrieval_screener_arg_s +{ + KEYDB_SEARCH_DESC *desc; + int ndesc; +}; + + +/* Check whether a key matches the search description. The function + returns 0 if the key shall be imported. */ +static gpg_error_t +keyserver_retrieval_screener (kbnode_t keyblock, void *opaque) +{ + struct ks_retrieval_screener_arg_s *arg = opaque; + KEYDB_SEARCH_DESC *desc = arg->desc; + int ndesc = arg->ndesc; + kbnode_t node; + PKT_public_key *pk; + int n; + u32 keyid[2]; + byte fpr[MAX_FINGERPRINT_LEN]; + size_t fpr_len = 0; + + /* Secret keys are not expected from a keyserver. We do not + care about secret subkeys because the import code takes care + of skipping them. Not allowing an import of a public key + with a secret subkey would make it too easy to inhibit the + downloading of a public key. Recall that keyservers do only + limited checks. */ + node = find_kbnode (keyblock, PKT_SECRET_KEY); + if (node) + return gpg_error (GPG_ERR_GENERAL); /* Do not import. */ + + if (!ndesc) + return 0; /* Okay if no description given. */ + + /* Loop over all key packets. */ + for (node = keyblock; node; node = node->next) + { + if (node->pkt->pkttype != PKT_PUBLIC_KEY + && node->pkt->pkttype != PKT_PUBLIC_SUBKEY) + continue; + + pk = node->pkt->pkt.public_key; + fingerprint_from_pk (pk, fpr, &fpr_len); + keyid_from_pk (pk, keyid); + + /* Compare requested and returned fingerprints if available. */ + for (n = 0; n < ndesc; n++) + { + if (desc[n].mode == KEYDB_SEARCH_MODE_FPR20) + { + if (fpr_len == 20 && !memcmp (fpr, desc[n].u.fpr, 20)) + return 0; + } + else if (desc[n].mode == KEYDB_SEARCH_MODE_FPR16) + { + if (fpr_len == 16 && !memcmp (fpr, desc[n].u.fpr, 16)) + return 0; + } + else if (desc[n].mode == KEYDB_SEARCH_MODE_LONG_KID) + { + if (keyid[0] == desc[n].u.kid[0] && keyid[1] == desc[n].u.kid[1]) + return 0; + } + else if (desc[n].mode == KEYDB_SEARCH_MODE_SHORT_KID) + { + if (keyid[1] == desc[n].u.kid[1]) + return 0; + } + else /* No keyid or fingerprint - can't check. */ + return 0; /* allow import. */ + } + } + + return gpg_error (GPG_ERR_GENERAL); +} + + +int +keyserver_import (ctrl_t ctrl, strlist_t users) +{ + gpg_error_t err; + 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) + { + err = classify_user_id (users->d, &desc[count], 1); + if (err || (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_get (ctrl, desc, count, NULL, 0, NULL, NULL); + + xfree(desc); + + return rc; +} + + +/* Return true if any keyserver has been configured. */ +int +keyserver_any_configured (ctrl_t ctrl) +{ + return !gpg_dirmngr_ks_list (ctrl, NULL); +} + + +/* Import all keys that exactly match MBOX */ +int +keyserver_import_mbox (ctrl_t ctrl, const char *mbox, + unsigned char **fpr, size_t *fprlen, + struct keyserver_spec *keyserver) +{ + KEYDB_SEARCH_DESC desc = { 0 }; + + desc.mode = KEYDB_SEARCH_MODE_MAIL; + desc.u.name = mbox; + + return keyserver_get (ctrl, &desc, 1, keyserver, 0, fpr, fprlen); +} + + +/* Import the keys that match exactly MBOX */ +int +keyserver_import_ntds (ctrl_t ctrl, const char *mbox, + unsigned char **fpr, size_t *fprlen) +{ + KEYDB_SEARCH_DESC desc = { 0 }; + struct keyserver_spec keyserver = { NULL, "ldap:///" }; + + desc.mode = KEYDB_SEARCH_MODE_MAIL; + desc.u.name = mbox; + + return keyserver_get (ctrl, &desc, 1, &keyserver, 0, fpr, fprlen); +} + + +int +keyserver_import_fprint (ctrl_t ctrl, const byte *fprint, size_t fprint_len, + struct keyserver_spec *keyserver, + unsigned int flags) +{ + 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 gpg_error (GPG_ERR_INV_ARG); + + memcpy (desc.u.fpr, fprint, fprint_len); + + return keyserver_get (ctrl, &desc, 1, keyserver, flags, NULL, NULL); +} + + +int +keyserver_import_fprint_ntds (ctrl_t ctrl, + const byte *fprint, size_t fprint_len) +{ + struct keyserver_spec keyserver = { NULL, "ldap:///" }; + + return keyserver_import_fprint (ctrl, fprint, fprint_len, + &keyserver, KEYSERVER_IMPORT_FLAG_LDAP); +} + + +int +keyserver_import_keyid (ctrl_t ctrl, + u32 *keyid,struct keyserver_spec *keyserver, + unsigned int flags) +{ + 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_get (ctrl, &desc, 1, keyserver, flags, NULL, NULL); +} + + +/* code mostly stolen from do_export_stream */ +static int +keyidlist (ctrl_t ctrl, strlist_t users, KEYDB_SEARCH_DESC **klist, + int *count) +{ + int rc = 0; + int num = 100; + kbnode_t keyblock = NULL; + kbnode_t node; + KEYDB_HANDLE kdbhd; + int ndesc; + KEYDB_SEARCH_DESC *desc = NULL; + strlist_t sl; + + *count=0; + + *klist=xmalloc(sizeof(KEYDB_SEARCH_DESC)*num); + + kdbhd = keydb_new (); + if (!kdbhd) + { + rc = gpg_error_from_syserror (); + goto leave; + } + keydb_disable_caching (kdbhd); /* We are looping the search. */ + + 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) + { + gpg_error_t err; + if (!(err = classify_user_id (sl->d, desc+ndesc, 1))) + ndesc++; + else + log_error (_("key \"%s\" not found: %s\n"), + sl->d, gpg_strerror (err)); + } + } + + for (;;) + { + rc = keydb_search (kdbhd, desc, ndesc, NULL); + if (rc) + break; /* ready. */ + + 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"), gpg_strerror (rc) ); + goto leave; + } + + if((node=find_kbnode(keyblock,PKT_PUBLIC_KEY))) + { + /* 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 (ctrl, keyblock); + + for(node=node->next;node;node=node->next) + { + if(node->pkt->pkttype==PKT_USER_ID + && node->pkt->pkt.user_id->flags.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 (gpg_err_code (rc) == GPG_ERR_NOT_FOUND) + rc = 0; + + leave: + if(rc) + { + xfree(*klist); + *klist = NULL; + } + 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. */ + +gpg_error_t +keyserver_refresh (ctrl_t ctrl, strlist_t users) +{ + gpg_error_t err; + int count, numdesc; + 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; + + + err = keyidlist (ctrl, users, &desc, &numdesc); + if (err) + return err; + + count=numdesc; + if(count>0) + { + int i; + + /* Try to handle preferred keyserver keys first */ + for(i=0;iuri); + + /* We use the keyserver structure we parsed out before. + Note that a preferred keyserver without a scheme:// + will be interpreted as hkp:// */ + err = keyserver_get (ctrl, &desc[i], 1, keyserver, 0, NULL, NULL); + if (err) + log_info(_("WARNING: unable to refresh key %s" + " via %s: %s\n"),keystr_from_desc(&desc[i]), + keyserver->uri,gpg_strerror (err)); + 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) + { + char *tmpuri; + + err = gpg_dirmngr_ks_list (ctrl, &tmpuri); + if (!err) + { + if (!opt.quiet) + { + log_info (ngettext("refreshing %d key from %s\n", + "refreshing %d keys from %s\n", + count), count, tmpuri); + } + xfree (tmpuri); + + err = keyserver_get (ctrl, desc, numdesc, NULL, 0, NULL, NULL); + } + } + + 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)) + check_or_update_trustdb (ctrl); + + return err; +} + + +/* Search for keys on the keyservers. The patterns are given in the + string list TOKENS. */ +gpg_error_t +keyserver_search (ctrl_t ctrl, strlist_t tokens) +{ + gpg_error_t err; + char *searchstr; + struct search_line_handler_parm_s parm; + + memset (&parm, 0, sizeof parm); + + if (!tokens) + return 0; /* Return success if no patterns are given. */ + + { + membuf_t mb; + strlist_t item; + + init_membuf (&mb, 1024); + for (item = tokens; item; item = item->next) + { + if (item != tokens) + put_membuf (&mb, " ", 1); + put_membuf_str (&mb, item->d); + } + put_membuf (&mb, "", 1); /* Append Nul. */ + searchstr = get_membuf (&mb, NULL); + if (!searchstr) + { + err = gpg_error_from_syserror (); + goto leave; + } + } + + parm.ctrl = ctrl; + if (searchstr) + parm.searchstr_disp = utf8_to_native (searchstr, strlen (searchstr), 0); + + err = gpg_dirmngr_ks_search (ctrl, searchstr, search_line_handler, &parm); + + if (parm.not_found || gpg_err_code (err) == GPG_ERR_NO_DATA) + { + if (parm.searchstr_disp) + log_info (_("key \"%s\" not found on keyserver\n"), + parm.searchstr_disp); + else + log_info (_("key not found on keyserver\n")); + } + + if (gpg_err_code (err) == GPG_ERR_NO_DATA) + err = gpg_error (GPG_ERR_NOT_FOUND); + else if (err) + log_error ("error searching keyserver: %s\n", gpg_strerror (err)); + + leave: + xfree (parm.desc); + xfree (parm.searchstr_disp); + xfree(searchstr); + + return err; +} + +/* Helper for keyserver_get. Here we only receive a chunk of the + description to be processed in one batch. This is required due to + the limited number of patterns the dirmngr interface (KS_GET) can + grok and to limit the amount of temporary required memory. */ +static gpg_error_t +keyserver_get_chunk (ctrl_t ctrl, KEYDB_SEARCH_DESC *desc, int ndesc, + int *r_ndesc_used, + import_stats_t stats_handle, + struct keyserver_spec *override_keyserver, + unsigned int flags, + unsigned char **r_fpr, size_t *r_fprlen) + +{ + gpg_error_t err = 0; + char **pattern; + int idx, npat, npat_fpr; + estream_t datastream; + char *source = NULL; + size_t linelen; /* Estimated linelen for KS_GET. */ + size_t n; + int only_fprs; + +#define MAX_KS_GET_LINELEN 950 /* Somewhat lower than the real limit. */ + + *r_ndesc_used = 0; + + /* Create an array filled with a search pattern for each key. The + array is delimited by a NULL entry. */ + pattern = xtrycalloc (ndesc+1, sizeof *pattern); + if (!pattern) + return gpg_error_from_syserror (); + + /* Note that we break the loop as soon as our estimation of the to + be used line length reaches the limit. But we do this only if we + have processed at least one search requests so that an overlong + single request will be rejected only later by gpg_dirmngr_ks_get + but we are sure that R_NDESC_USED has been updated. This avoids + a possible indefinite loop. */ + linelen = 24; /* "KS_GET --quick --ldap --" */ + for (npat=npat_fpr=0, idx=0; idx < ndesc; idx++) + { + int quiet = 0; + + if (desc[idx].mode == KEYDB_SEARCH_MODE_FPR20 + || desc[idx].mode == KEYDB_SEARCH_MODE_FPR16) + { + n = 1+2+2*20; + if (idx && linelen + n > MAX_KS_GET_LINELEN) + break; /* Declare end of this chunk. */ + linelen += n; + + pattern[npat] = xtrymalloc (n); + if (!pattern[npat]) + err = gpg_error_from_syserror (); + else + { + strcpy (pattern[npat], "0x"); + bin2hex (desc[idx].u.fpr, + desc[idx].mode == KEYDB_SEARCH_MODE_FPR20? 20 : 16, + pattern[npat]+2); + npat++; + if (desc[idx].mode == KEYDB_SEARCH_MODE_FPR20) + npat_fpr++; + } + } + else if(desc[idx].mode == KEYDB_SEARCH_MODE_LONG_KID) + { + n = 1+2+16; + if (idx && linelen + n > MAX_KS_GET_LINELEN) + break; /* Declare end of this chunk. */ + linelen += n; + + pattern[npat] = xtryasprintf ("0x%08lX%08lX", + (ulong)desc[idx].u.kid[0], + (ulong)desc[idx].u.kid[1]); + if (!pattern[npat]) + err = gpg_error_from_syserror (); + else + npat++; + } + else if(desc[idx].mode == KEYDB_SEARCH_MODE_SHORT_KID) + { + n = 1+2+8; + if (idx && linelen + n > MAX_KS_GET_LINELEN) + break; /* Declare end of this chunk. */ + linelen += n; + + pattern[npat] = xtryasprintf ("0x%08lX", (ulong)desc[idx].u.kid[1]); + if (!pattern[npat]) + err = gpg_error_from_syserror (); + else + npat++; + } + else if(desc[idx].mode == KEYDB_SEARCH_MODE_EXACT) + { + /* The Dirmngr also uses classify_user_id to detect the type + of the search string. By adding the '=' prefix we force + Dirmngr's KS_GET to consider this an exact search string. + (In gpg 1.4 and gpg 2.0 the keyserver helpers used the + KS_GETNAME command to indicate this.) */ + + n = 1+1+strlen (desc[idx].u.name); + if (idx && linelen + n > MAX_KS_GET_LINELEN) + break; /* Declare end of this chunk. */ + linelen += n; + + pattern[npat] = strconcat ("=", desc[idx].u.name, NULL); + if (!pattern[npat]) + err = gpg_error_from_syserror (); + else + { + npat++; + quiet = 1; + } + } + else if(desc[idx].mode == KEYDB_SEARCH_MODE_MAIL) + { + n = 1 + strlen (desc[idx].u.name) + 1 + 1; + if (idx && linelen + n > MAX_KS_GET_LINELEN) + break; /* Declare end of this chunk. */ + linelen += n; + + if (desc[idx].u.name[0] == '<') + pattern[npat] = xtrystrdup (desc[idx].u.name); + else + pattern[npat] = strconcat ("<", desc[idx].u.name, ">", NULL); + if (!pattern[npat]) + err = gpg_error_from_syserror (); + else + { + npat++; + quiet = 1; + } + } + else if (desc[idx].mode == KEYDB_SEARCH_MODE_NONE) + continue; + else + BUG(); + + if (err) + { + for (idx=0; idx < npat; idx++) + xfree (pattern[idx]); + xfree (pattern); + return err; + } + + if (!quiet && override_keyserver) + { + log_info (_("requesting key %s from %s\n"), + keystr_from_desc (&desc[idx]), override_keyserver->uri); + } + } + + /* Remember now many of search items were considered. Note that + this is different from NPAT. */ + *r_ndesc_used = idx; + + only_fprs = (npat && npat == npat_fpr); + + err = gpg_dirmngr_ks_get (ctrl, pattern, override_keyserver, flags, + &datastream, &source); + for (idx=0; idx < npat; idx++) + xfree (pattern[idx]); + xfree (pattern); + if (opt.verbose && source) + log_info ("data source: %s\n", source); + + + + if (!err) + { + struct ks_retrieval_screener_arg_s screenerarg; + unsigned int options; + + /* FIXME: Check whether this comment should be moved to dirmngr. + + 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. Note that we don't allow the + import of secret keys from a keyserver. Keyservers should + never accept or send them but we better protect against rogue + keyservers. */ + + /* For LDAP servers we reset IMPORT_SELF_SIGS_ONLY unless it has + * been set explicitly. */ + options = (opt.keyserver_options.import_options | IMPORT_NO_SECKEY); + if (source && (!strncmp (source, "ldap:", 5) + || !strncmp (source, "ldaps:", 6)) + && !opt.flags.expl_import_self_sigs_only) + options &= ~IMPORT_SELF_SIGS_ONLY; + + screenerarg.desc = desc; + screenerarg.ndesc = *r_ndesc_used; + import_keys_es_stream (ctrl, datastream, stats_handle, + r_fpr, r_fprlen, options, + keyserver_retrieval_screener, &screenerarg, + only_fprs? KEYORG_KS : 0, + source); + } + es_fclose (datastream); + xfree (source); + + return err; +} + + +/* Retrieve a key from a keyserver. The search pattern are in + (DESC,NDESC). Allowed search modes are keyid, fingerprint, and + exact searches. OVERRIDE_KEYSERVER gives an optional override + keyserver. If (R_FPR,R_FPRLEN) are not NULL, they may return the + fingerprint of a single imported key. If the FLAG bit + KEYSERVER_IMPORT_FLAG_QUICK is set, dirmngr is advised to use a + shorter timeout. */ +static gpg_error_t +keyserver_get (ctrl_t ctrl, KEYDB_SEARCH_DESC *desc, int ndesc, + struct keyserver_spec *override_keyserver, unsigned int flags, + unsigned char **r_fpr, size_t *r_fprlen) +{ + gpg_error_t err; + import_stats_t stats_handle; + int ndesc_used; + int any_good = 0; + + stats_handle = import_new_stats_handle(); + + for (;;) + { + err = keyserver_get_chunk (ctrl, desc, ndesc, &ndesc_used, stats_handle, + override_keyserver, flags, r_fpr, r_fprlen); + if (!err) + any_good = 1; + if (err || ndesc_used >= ndesc) + break; /* Error or all processed. */ + /* Prepare for the next chunk. */ + desc += ndesc_used; + ndesc -= ndesc_used; + } + + if (any_good) + import_print_stats (stats_handle); + + import_release_stats_handle (stats_handle); + return err; +} + + +/* Send all keys specified by KEYSPECS to the configured keyserver. */ +static gpg_error_t +keyserver_put (ctrl_t ctrl, strlist_t keyspecs) + +{ + gpg_error_t err; + strlist_t kspec; + char *ksurl; + + if (!keyspecs) + return 0; /* Return success if the list is empty. */ + + if (gpg_dirmngr_ks_list (ctrl, &ksurl)) + { + log_error (_("no keyserver known\n")); + return gpg_error (GPG_ERR_NO_KEYSERVER); + } + + for (kspec = keyspecs; kspec; kspec = kspec->next) + { + void *data; + size_t datalen; + kbnode_t keyblock; + + err = export_pubkey_buffer (ctrl, kspec->d, + opt.keyserver_options.export_options, + NULL, 0, NULL, + &keyblock, &data, &datalen); + if (err) + log_error (_("skipped \"%s\": %s\n"), kspec->d, gpg_strerror (err)); + else + { + if (!opt.quiet) + log_info (_("sending key %s to %s\n"), + keystr (keyblock->pkt->pkt.public_key->keyid), + ksurl?ksurl:"[?]"); + + err = gpg_dirmngr_ks_put (ctrl, data, datalen, keyblock); + release_kbnode (keyblock); + xfree (data); + if (err) + { + write_status_error ("keyserver_send", err); + log_error (_("keyserver send failed: %s\n"), gpg_strerror (err)); + } + } + } + + xfree (ksurl); + + return err; + +} + + +/* Loop over all URLs in STRLIST and fetch the key at that URL. Note + that the fetch operation ignores the configured keyservers and + instead directly retrieves the keys. */ +int +keyserver_fetch (ctrl_t ctrl, strlist_t urilist, int origin) +{ + gpg_error_t err; + strlist_t sl; + estream_t datastream; + unsigned int save_options = opt.keyserver_options.import_options; + int any_success = 0; + gpg_error_t firsterr = 0; + + /* 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; + + for (sl=urilist; sl; sl=sl->next) + { + if (!opt.quiet) + log_info (_("requesting key from '%s'\n"), sl->d); + + err = gpg_dirmngr_ks_fetch (ctrl, sl->d, &datastream); + if (!err) + { + import_stats_t stats_handle; + + stats_handle = import_new_stats_handle(); + import_keys_es_stream (ctrl, datastream, stats_handle, NULL, NULL, + opt.keyserver_options.import_options, + NULL, NULL, origin, sl->d); + + import_print_stats (stats_handle); + import_release_stats_handle (stats_handle); + any_success = 1; + } + else + { + log_info (_("WARNING: unable to fetch URI %s: %s\n"), + sl->d, gpg_strerror (err)); + if (!firsterr) + firsterr = err; + } + es_fclose (datastream); + } + + if (!urilist) + err = gpg_error (GPG_ERR_NO_NAME); + else if (any_success) + err = 0; + else + err = firsterr; + + opt.keyserver_options.import_options = save_options; + + /* If the original options didn't have fast import, and the trustdb + is dirty, rebuild. */ + if (!(opt.keyserver_options.import_options&IMPORT_FAST)) + check_or_update_trustdb (ctrl); + + return err; +} + + +/* Import key in a CERT or pointed to by a CERT. In DANE_MODE fetch + the certificate using the DANE method. */ +int +keyserver_import_cert (ctrl_t ctrl, const char *name, int dane_mode, + unsigned char **fpr,size_t *fpr_len) +{ + gpg_error_t err; + char *look,*url; + estream_t key; + + look = xstrdup(name); + + if (!dane_mode) + { + char *domain = strrchr (look,'@'); + if (domain) + *domain='.'; + } + + err = gpg_dirmngr_dns_cert (ctrl, look, dane_mode? NULL : "*", + &key, fpr, fpr_len, &url); + if (err) + ; + else if (key) + { + int armor_status=opt.no_armor; + import_filter_t save_filt; + + /* CERTs and DANE records are always in binary format */ + opt.no_armor=1; + if (dane_mode) + { + save_filt = save_and_clear_import_filter (); + if (!save_filt) + err = gpg_error_from_syserror (); + else + { + char *filtstr = es_bsprintf ("keep-uid=mbox = %s", look); + err = filtstr? 0 : gpg_error_from_syserror (); + if (!err) + err = parse_and_set_import_filter (filtstr); + xfree (filtstr); + if (!err) + err = import_keys_es_stream (ctrl, key, NULL, fpr, fpr_len, + IMPORT_NO_SECKEY, + NULL, NULL, KEYORG_DANE, NULL); + restore_import_filter (save_filt); + } + } + else + { + err = import_keys_es_stream (ctrl, key, NULL, fpr, fpr_len, + (opt.keyserver_options.import_options + | IMPORT_NO_SECKEY), + NULL, NULL, 0, NULL); + } + + opt.no_armor=armor_status; + + es_fclose (key); + key = NULL; + } + else if (*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); + if(spec) + { + err = keyserver_import_fprint (ctrl, *fpr, *fpr_len, spec, 0); + free_keyserver_spec(spec); + } + } + else if (keyserver_any_configured (ctrl)) + { + /* If only a fingerprint is provided, try and fetch it from + the configured keyserver. */ + + err = keyserver_import_fprint (ctrl, + *fpr, *fpr_len, opt.keyserver, 0); + } + else + log_info(_("no keyserver known\n")); + + /* Give a better string here? "CERT fingerprint for \"%s\" + found, but no keyserver" " known (use option + --keyserver)\n" ? */ + + } + + xfree(url); + xfree(look); + + return err; +} + +/* Import key pointed to by a PKA record. Return the requested + fingerprint in fpr. */ +gpg_error_t +keyserver_import_pka (ctrl_t ctrl, const char *name, + unsigned char **fpr, size_t *fpr_len) +{ + gpg_error_t err; + char *url; + + err = gpg_dirmngr_get_pka (ctrl, name, fpr, fpr_len, &url); + if (url && *url && fpr && fpr_len) + { + /* An URL is available. Lookup the key. */ + struct keyserver_spec *spec; + spec = parse_keyserver_uri (url, 1); + if (spec) + { + err = keyserver_import_fprint (ctrl, *fpr, *fpr_len, spec, 0); + free_keyserver_spec (spec); + } + } + xfree (url); + + if (err) + { + xfree(*fpr); + *fpr = NULL; + *fpr_len = 0; + } + + return err; +} + + +/* Import a key using the Web Key Directory protocol. */ +gpg_error_t +keyserver_import_wkd (ctrl_t ctrl, const char *name, unsigned int flags, + unsigned char **fpr, size_t *fpr_len) +{ + gpg_error_t err; + char *mbox; + estream_t key; + char *url = NULL; + + /* We want to work on the mbox. That is what dirmngr will do anyway + * and we need the mbox for the import filter anyway. */ + mbox = mailbox_from_userid (name); + if (!mbox) + { + err = gpg_error_from_syserror (); + if (gpg_err_code (err) == GPG_ERR_EINVAL) + err = gpg_error (GPG_ERR_INV_USER_ID); + return err; + } + + err = gpg_dirmngr_wkd_get (ctrl, mbox, flags, &key, &url); + if (err) + ; + else if (key) + { + int armor_status = opt.no_armor; + import_filter_t save_filt; + + /* Keys returned via WKD are in binary format. However, we + * relax that requirement and allow also for armored data. */ + opt.no_armor = 0; + save_filt = save_and_clear_import_filter (); + if (!save_filt) + err = gpg_error_from_syserror (); + else + { + char *filtstr = es_bsprintf ("keep-uid=mbox = %s", mbox); + err = filtstr? 0 : gpg_error_from_syserror (); + if (!err) + err = parse_and_set_import_filter (filtstr); + xfree (filtstr); + if (!err) + err = import_keys_es_stream (ctrl, key, NULL, fpr, fpr_len, + IMPORT_NO_SECKEY, + NULL, NULL, KEYORG_WKD, url); + + } + + restore_import_filter (save_filt); + opt.no_armor = armor_status; + + es_fclose (key); + key = NULL; + } + + xfree (url); + xfree (mbox); + return err; +} + + +/* Import a key by name using LDAP */ +int +keyserver_import_ldap (ctrl_t ctrl, + const char *name, unsigned char **fpr, size_t *fprlen) +{ + (void)ctrl; + (void)name; + (void)fpr; + (void)fprlen; + return gpg_error (GPG_ERR_NOT_IMPLEMENTED); /*FIXME*/ +#if 0 + char *domain; + struct keyserver_spec *keyserver; + strlist_t list=NULL; + int rc,hostlen=1; + struct srventry *srvlist=NULL; + int srvcount,i; + char srvname[MAXDNAME]; + + /* Parse out the domain */ + domain=strrchr(name,'@'); + if(!domain) + return GPG_ERR_GENERAL; + + domain++; + + keyserver=xmalloc_clear(sizeof(struct keyserver_spec)); + keyserver->scheme=xstrdup("ldap"); + keyserver->host=xmalloc(1); + keyserver->host[0]='\0'; + + snprintf(srvname,MAXDNAME,"_pgpkey-ldap._tcp.%s",domain); + + FIXME("network related - move to dirmngr or drop the code"); + srvcount=getsrv(srvname,&srvlist); + + for(i=0;ihost=xrealloc(keyserver->host,hostlen); + + strcat(keyserver->host,srvlist[i].target); + + if(srvlist[i].port!=389) + { + char port[7]; + + hostlen+=6; /* a colon, plus 5 digits (unsigned 16-bit value) */ + keyserver->host=xrealloc(keyserver->host,hostlen); + + snprintf(port,7,":%u",srvlist[i].port); + strcat(keyserver->host,port); + } + + strcat(keyserver->host," "); + } + + free(srvlist); + + /* If all else fails, do the PGP Universal trick of + ldap://keys.(domain) */ + + hostlen+=5+strlen(domain); + keyserver->host=xrealloc(keyserver->host,hostlen); + strcat(keyserver->host,"keys."); + strcat(keyserver->host,domain); + + append_to_strlist(&list,name); + + rc = gpg_error (GPG_ERR_NOT_IMPLEMENTED); /*FIXME*/ + /* keyserver_work (ctrl, KS_GETNAME, list, NULL, */ + /* 0, fpr, fpr_len, keyserver); */ + + free_strlist(list); + + free_keyserver_spec(keyserver); + + return rc; +#endif +} diff --git a/g10/main.h b/g10/main.h new file mode 100644 index 0000000..273ddaa --- /dev/null +++ b/g10/main.h @@ -0,0 +1,518 @@ +/* main.h + * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, + * 2008, 2009, 2010 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ +#ifndef G10_MAIN_H +#define G10_MAIN_H + +#include "../common/types.h" +#include "../common/iobuf.h" +#include "../common/util.h" +#include "keydb.h" +#include "keyedit.h" + +/* It could be argued that the default cipher should be 3DES rather + than AES128, 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. */ + +#if GPG_USE_AES128 +# define DEFAULT_CIPHER_ALGO CIPHER_ALGO_AES +#elif GPG_USE_CAST5 +# define DEFAULT_CIPHER_ALGO CIPHER_ALGO_CAST5 +#else +# define DEFAULT_CIPHER_ALGO CIPHER_ALGO_3DES +#endif + +#define DEFAULT_DIGEST_ALGO ((GNUPG)? DIGEST_ALGO_SHA256:DIGEST_ALGO_SHA1) +#define DEFAULT_S2K_DIGEST_ALGO DIGEST_ALGO_SHA1 +#ifdef HAVE_ZIP +# define DEFAULT_COMPRESS_ALGO COMPRESS_ALGO_ZIP +#else +# define DEFAULT_COMPRESS_ALGO COMPRESS_ALGO_NONE +#endif + + +#define S2K_DIGEST_ALGO (opt.s2k_digest_algo?opt.s2k_digest_algo:DEFAULT_S2K_DIGEST_ALGO) + + +/* Various data objects. */ + +typedef struct +{ + ctrl_t ctrl; + 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_t values; + struct groupitem *next; +}; + +struct weakhash +{ + enum gcry_md_algos algo; + int rejection_shown; + struct weakhash *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 (pubkey_algo_t algo); +void print_cipher_algo_note (cipher_algo_t algo); +void print_digest_algo_note (digest_algo_t algo); +void print_digest_rejected_note (enum gcry_md_algos algo); +void print_sha1_keysig_rejected_note (void); +void print_reported_error (gpg_error_t err, gpg_err_code_t skip_if_ec); +void print_further_info (const char *format, ...) GPGRT_ATTR_PRINTF(1,2); +void additional_weak_digest (const char* digestname); +int is_weak_digest (digest_algo_t algo); + +/*-- armor.c --*/ +char *make_radix64_string( const byte *data, size_t len ); + +/*-- misc.c --*/ +void trap_unaligned(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( gcry_mpi_t a ); +u32 buffer_to_u32( const byte *buffer ); +const byte *get_session_marker( size_t *rlen ); + +enum gcry_cipher_algos map_cipher_openpgp_to_gcry (cipher_algo_t algo); +#define openpgp_cipher_open(_a,_b,_c,_d) \ + gcry_cipher_open((_a),map_cipher_openpgp_to_gcry((_b)),(_c),(_d)) +#define openpgp_cipher_get_algo_keylen(_a) \ + gcry_cipher_get_algo_keylen(map_cipher_openpgp_to_gcry((_a))) +#define openpgp_cipher_get_algo_blklen(_a) \ + gcry_cipher_get_algo_blklen(map_cipher_openpgp_to_gcry((_a))) +int openpgp_cipher_blocklen (cipher_algo_t algo); +int openpgp_cipher_test_algo(cipher_algo_t algo); +const char *openpgp_cipher_algo_name (cipher_algo_t algo); +const char *openpgp_cipher_algo_mode_name (cipher_algo_t algo, + aead_algo_t aead); + +gpg_error_t openpgp_aead_test_algo (aead_algo_t algo); +const char *openpgp_aead_algo_name (aead_algo_t algo); +gpg_error_t openpgp_aead_algo_info (aead_algo_t algo, + enum gcry_cipher_modes *r_mode, + unsigned int *r_noncelen); + +pubkey_algo_t map_pk_gcry_to_openpgp (enum gcry_pk_algos algo); +int openpgp_pk_test_algo (pubkey_algo_t algo); +int openpgp_pk_test_algo2 (pubkey_algo_t algo, unsigned int use); +int openpgp_pk_algo_usage ( int algo ); +const char *openpgp_pk_algo_name (pubkey_algo_t algo); + +enum gcry_md_algos map_md_openpgp_to_gcry (digest_algo_t algo); +int openpgp_md_test_algo (digest_algo_t algo); +const char *openpgp_md_algo_name (int algo); + +struct expando_args +{ + PKT_public_key *pk; + PKT_public_key *pksk; + byte imagetype; + int validity_info; + const char *validity_string; + const byte *namehash; +}; + +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); +void obsolete_scdaemon_option (const char *configname, + unsigned int configlineno, const char *name); + +int string_to_cipher_algo (const char *string); +int string_to_digest_algo (const char *string); + +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); +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); +const char *get_libexecdir (void); +int path_access(const char *file,int mode); + +int pubkey_get_npkey (pubkey_algo_t algo); +int pubkey_get_nskey (pubkey_algo_t algo); +int pubkey_get_nsig (pubkey_algo_t algo); +int pubkey_get_nenc (pubkey_algo_t algo); + +/* Temporary helpers. */ +unsigned int pubkey_nbits( int algo, gcry_mpi_t *pkey ); +int mpi_print (estream_t stream, gcry_mpi_t a, int mode); +unsigned int ecdsa_qbits_from_Q (unsigned int qbits); + + +/*-- cpr.c --*/ +void set_status_fd ( int fd ); +int is_status_enabled ( void ); +void write_status ( int no ); +void write_status_error (const char *where, gpg_error_t err); +void write_status_errcode (const char *where, int errcode); +void write_status_failure (const char *where, gpg_error_t err); +void write_status_text ( int no, const char *text ); +void write_status_printf (int no, const char *format, + ...) GPGRT_ATTR_PRINTF(2,3); +void write_status_strings (int no, const char *text, + ...) GPGRT_ATTR_SENTINEL(0); +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 ); + +void write_status_begin_signing (gcry_md_hd_t md); + + +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_def (const char *keyword, const char *prompt, + int def_yes); +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); + +/*-- helptext.c --*/ +void display_online_help( const char *keyword ); + +/*-- encode.c --*/ +gpg_error_t setup_symkey (STRING2KEY **symkey_s2k,DEK **symkey_dek); +void encrypt_seskey (DEK *dek, DEK **seskey, byte *enckey); +int use_mdc (pk_list_t pk_list,int algo); +int encrypt_symmetric (const char *filename ); +int encrypt_store (const char *filename ); +int encrypt_crypt (ctrl_t ctrl, int filefd, const char *filename, + strlist_t remusr, int use_symkey, pk_list_t provided_keys, + int outputfd); +void encrypt_crypt_files (ctrl_t ctrl, + int nfiles, char **files, strlist_t remusr); +int encrypt_filter (void *opaque, int control, + iobuf_t a, byte *buf, size_t *ret_len); + +int write_pubkey_enc (ctrl_t ctrl, PKT_public_key *pk, int throw_keyid, + DEK *dek, iobuf_t out); + +/*-- sign.c --*/ +int sign_file (ctrl_t ctrl, strlist_t filenames, int detached, strlist_t locusr, + int do_encrypt, strlist_t remusr, const char *outfile ); +int clearsign_file (ctrl_t ctrl, + const char *fname, strlist_t locusr, const char *outfile); +int sign_symencrypt_file (ctrl_t ctrl, const char *fname, strlist_t locusr); + +/*-- sig-check.c --*/ +void sig_check_dump_stats (void); + +/* SIG is a revocation signature. Check if any of PK's designated + revokers generated it. If so, return 0. Note: this function + (correctly) doesn't care if the designated revoker is revoked. */ +int check_revocation_keys (ctrl_t ctrl, PKT_public_key *pk, PKT_signature *sig); +/* Check that the backsig BACKSIG from the subkey SUB_PK to its + primary key MAIN_PK is valid. */ +int check_backsig(PKT_public_key *main_pk,PKT_public_key *sub_pk, + PKT_signature *backsig); +/* Check that the signature SIG over a key (e.g., a key binding or a + key revocation) is valid. (To check signatures over data, use + check_signature.) */ +int check_key_signature (ctrl_t ctrl, kbnode_t root, kbnode_t sig, + int *is_selfsig ); +/* Like check_key_signature, but with the ability to specify some + additional parameters and get back additional information. See the + documentation for the implementation for details. */ +int check_key_signature2 (ctrl_t ctrl, kbnode_t root, kbnode_t node, + PKT_public_key *check_pk, PKT_public_key *ret_pk, + int *is_selfsig, u32 *r_expiredate, int *r_expired); + +/* Returns whether SIGNER generated the signature SIG over the packet + PACKET, which is a key, subkey or uid, and comes from the key block + KB. If SIGNER is NULL, it is looked up based on the information in + SIG. If not NULL, sets *IS_SELFSIG to indicate whether the + signature is a self-signature and *RET_PK to a copy of the signer's + key. */ +gpg_error_t check_signature_over_key_or_uid (ctrl_t ctrl, + PKT_public_key *signer, + PKT_signature *sig, + KBNODE kb, PACKET *packet, + int *is_selfsig, + PKT_public_key *ret_pk); + + +/*-- delkey.c --*/ +gpg_error_t delete_keys (ctrl_t ctrl, + strlist_t names, int secret, int allow_both); + +/*-- keygen.c --*/ +const char *get_default_pubkey_algo (void); +u32 parse_expire_string(const char *string); +u32 ask_expire_interval(int object,const char *def_expire); +u32 ask_expiredate(void); +unsigned int ask_key_flags (int algo, int subkey, unsigned int current); +const char *ask_curve (int *algo, int *subkey_algo, const char *current); +void quick_generate_keypair (ctrl_t ctrl, const char *uid, const char *algostr, + const char *usagestr, const char *expirestr); +void generate_keypair (ctrl_t ctrl, int full, const char *fname, + const char *card_serialno, int card_backup_key); +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_key_flags (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); +gpg_error_t make_backsig (ctrl_t ctrl, + PKT_signature *sig, PKT_public_key *pk, + PKT_public_key *sub_pk, PKT_public_key *sub_psk, + u32 timestamp, const char *cache_nonce); +gpg_error_t generate_subkeypair (ctrl_t ctrl, kbnode_t keyblock, + const char *algostr, + const char *usagestr, + const char *expirestr); +#ifdef ENABLE_CARD_SUPPORT +gpg_error_t generate_card_subkeypair (ctrl_t ctrl, kbnode_t pub_keyblock, + int keyno, const char *serialno); +#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 (int inp_fd, const char *iname, int mode, + int restrictedperm, iobuf_t *a); +char *get_matching_datafile (const char *sigfilename); +iobuf_t open_sigfile (const char *sigfilename, progress_filter_context_t *pfx); +void try_make_homedir( const char *fname ); +char *get_openpgp_revocdir (const char *home); + +/*-- seskey.c --*/ +void make_session_key( DEK *dek ); +gcry_mpi_t encode_session_key( int openpgp_pk_algo, DEK *dek, unsigned nbits ); +gcry_mpi_t encode_md_value (PKT_public_key *pk, + gcry_md_hd_t md, int hash_algo ); + +/*-- import.c --*/ +struct import_stats_s; +typedef struct import_stats_s *import_stats_t; +struct import_filter_s; +typedef struct import_filter_s *import_filter_t; +typedef gpg_error_t (*import_screener_t)(kbnode_t keyblock, void *arg); + +int parse_import_options(char *str,unsigned int *options,int noisy); + +gpg_error_t parse_and_set_import_filter (const char *string); +import_filter_t save_and_clear_import_filter (void); +void restore_import_filter (import_filter_t filt); + +gpg_error_t read_key_from_file_or_buffer (ctrl_t ctrl, const char *fname, + const void *buffer, size_t buflen, + kbnode_t *r_keyblock); +gpg_error_t import_included_key_block (ctrl_t ctrl, kbnode_t keyblock); +void import_keys (ctrl_t ctrl, char **fnames, int nnames, + import_stats_t stats_hd, unsigned int options, + int origin, const char *url); +gpg_error_t import_keys_es_stream (ctrl_t ctrl, estream_t fp, + import_stats_t stats_handle, + unsigned char **fpr, size_t *fpr_len, + unsigned int options, + import_screener_t screener, void *screener_arg, + int origin, const char *url); +gpg_error_t import_old_secring (ctrl_t ctrl, const char *fname); +import_stats_t import_new_stats_handle (void); +void import_release_stats_handle (import_stats_t hd); +void import_print_stats (import_stats_t hd); +/* Communication for impex_filter_getval */ +struct impex_filter_parm_s +{ + ctrl_t ctrl; + kbnode_t node; + char hexfpr[2*MAX_FINGERPRINT_LEN + 1]; +}; + +const char *impex_filter_getval (void *cookie, const char *propname); +gpg_error_t transfer_secret_keys (ctrl_t ctrl, struct import_stats_s *stats, + kbnode_t sec_keyblock, int batch, int force, + int only_marked); + +int collapse_uids( KBNODE *keyblock ); + +int get_revocation_reason (PKT_signature *sig, char **r_reason, + char **r_comment, size_t *r_commentlen); + + +/*-- export.c --*/ +struct export_stats_s; +typedef struct export_stats_s *export_stats_t; + +export_stats_t export_new_stats (void); +void export_release_stats (export_stats_t stats); +void export_print_stats (export_stats_t stats); + +int parse_export_options(char *str,unsigned int *options,int noisy); +gpg_error_t parse_and_set_export_filter (const char *string); +void push_export_filters (void); +void pop_export_filters (void); + +int exact_subkey_match_p (KEYDB_SEARCH_DESC *desc, kbnode_t node); + +int export_pubkeys (ctrl_t ctrl, strlist_t users, unsigned int options, + export_stats_t stats); +int export_seckeys (ctrl_t ctrl, strlist_t users, unsigned int options, + export_stats_t stats); +int export_secsubkeys (ctrl_t ctrl, strlist_t users, unsigned int options, + export_stats_t stats); + +gpg_error_t export_pubkey_buffer (ctrl_t ctrl, const char *keyspec, + unsigned int options, + const void *prefix, size_t prefixlen, + export_stats_t stats, + kbnode_t *r_keyblock, + void **r_data, size_t *r_datalen); + +gpg_error_t receive_seckey_from_agent (ctrl_t ctrl, gcry_cipher_hd_t cipherhd, + int cleartext, + char **cache_nonce_addr, + const char *hexgrip, + PKT_public_key *pk); + +gpg_error_t write_keyblock_to_output (kbnode_t keyblock, + int with_armor, unsigned int options); + +gpg_error_t export_ssh_key (ctrl_t ctrl, const char *userid); + +/*-- dearmor.c --*/ +int dearmor_file( const char *fname ); +int enarmor_file( const char *fname ); + +/*-- revoke.c --*/ +struct revocation_reason_info; + +int gen_standard_revoke (ctrl_t ctrl, + PKT_public_key *psk, const char *cache_nonce); +int gen_revoke (ctrl_t ctrl, const char *uname); +int gen_desig_revoke (ctrl_t ctrl, const char *uname, strlist_t 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 ); +struct revocation_reason_info * get_default_uid_revocation_reason (void); +struct revocation_reason_info * get_default_sig_revocation_reason (void); +void release_revocation_reason_info (struct revocation_reason_info *reason); + +/*-- keylist.c --*/ +void public_key_list (ctrl_t ctrl, strlist_t list, + int locate_mode, int no_local); +void secret_key_list (ctrl_t ctrl, strlist_t list ); +void print_subpackets_colon(PKT_signature *sig); +void reorder_keyblock (KBNODE keyblock); +void list_keyblock_direct (ctrl_t ctrl, kbnode_t keyblock, int secret, + int has_secret, int fpr, int no_validity); +int cmp_signodes (const void *av, const void *bv); +void print_fingerprint (ctrl_t ctrl, estream_t fp, + PKT_public_key *pk, int mode); +void print_revokers (estream_t fp, 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); +void set_attrib_fd(int fd); +char *format_seckey_info (ctrl_t ctrl, PKT_public_key *pk); +void print_seckey_info (ctrl_t ctrl, PKT_public_key *pk); +void print_pubkey_info (ctrl_t ctrl, estream_t fp, PKT_public_key *pk); +void print_card_key_info (estream_t fp, KBNODE keyblock); +void print_key_line (ctrl_t ctrl, estream_t fp, PKT_public_key *pk, int secret); + +/*-- verify.c --*/ +void print_file_status( int status, const char *name, int what ); +int verify_signatures (ctrl_t ctrl, int nfiles, char **files ); +int verify_files (ctrl_t ctrl, int nfiles, char **files ); +int gpg_verify (ctrl_t ctrl, int sig_fd, int data_fd, estream_t out_fp); + +/*-- decrypt.c --*/ +int decrypt_message (ctrl_t ctrl, const char *filename ); +gpg_error_t decrypt_message_fd (ctrl_t ctrl, int input_fd, int output_fd); +void decrypt_messages (ctrl_t ctrl, int nfiles, char *files[]); + +/*-- plaintext.c --*/ +int hash_datafiles( gcry_md_hd_t md, gcry_md_hd_t md2, + strlist_t files, const char *sigfilename, int textmode); +int hash_datafile_by_fd ( gcry_md_hd_t md, gcry_md_hd_t md2, int data_fd, + int textmode ); +PKT_plaintext *setup_plaintext_name(const char *filename,IOBUF iobuf); + +/*-- server.c --*/ +int gpg_server (ctrl_t); +gpg_error_t gpg_proxy_pinentry_notify (ctrl_t ctrl, + const unsigned char *line); + +#ifdef ENABLE_CARD_SUPPORT +/*-- card-util.c --*/ +void change_pin (int no, int allow_admin); +void card_status (ctrl_t ctrl, estream_t fp, const char *serialno); +void card_edit (ctrl_t ctrl, strlist_t commands); +gpg_error_t card_generate_subkey (ctrl_t ctrl, kbnode_t pub_keyblock); +int card_store_subkey (KBNODE node, int use); +#endif + +#define S2K_DECODE_COUNT(_val) ((16ul + ((_val) & 15)) << (((_val) >> 4) + 6)) + +/*-- migrate.c --*/ +void migrate_secring (ctrl_t ctrl); + + +#endif /*G10_MAIN_H*/ diff --git a/g10/mainproc.c b/g10/mainproc.c new file mode 100644 index 0000000..8e4d848 --- /dev/null +++ b/g10/mainproc.c @@ -0,0 +1,2914 @@ +/* mainproc.c - handle packets + * Copyright (C) 1998-2009 Free Software Foundation, Inc. + * Copyright (C) 2013-2014 Werner Koch + * Copyright (C) 2020 g10 Code GmbH + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include +#include +#include +#include +#include + +#include "gpg.h" +#include "../common/util.h" +#include "packet.h" +#include "../common/iobuf.h" +#include "options.h" +#include "keydb.h" +#include "filter.h" +#include "main.h" +#include "../common/status.h" +#include "../common/i18n.h" +#include "trustdb.h" +#include "keyserver-internal.h" +#include "photoid.h" +#include "../common/mbox-util.h" +#include "call-dirmngr.h" +#include "../common/compliance.h" + +/* Put an upper limit on nested packets. The 32 is an arbitrary + value, a much lower should actually be sufficient. */ +#define MAX_NESTING_DEPTH 32 + + +/* An object to build a list of keyid related info. */ +struct kidlist_item +{ + struct kidlist_item *next; + u32 kid[2]; + int pubkey_algo; + int reason; +}; + +/* An object to build a list of symkey packet info. */ +struct symlist_item +{ + struct symlist_item *next; + int cipher_algo; + int cfb_mode; + int other_error; +}; + + +/* + * Object to hold the processing context. + */ +typedef struct mainproc_context *CTX; +struct mainproc_context +{ + ctrl_t ctrl; + struct mainproc_context *anchor; /* May be useful in the future. */ + PKT_public_key *last_pubkey; + 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. */ + + /* Name of the file with the complete signature or the file with the + detached signature. This is currently only used to deduce the + file name of the data file if that has not been given. */ + const char *sigfilename; + + /* A structure to describe the signed data in case of a detached + signature. */ + struct + { + /* A file descriptor of the signed data. Only used if not -1. */ + int data_fd; + /* A list of filenames with the data files or NULL. This is only + used if DATA_FD is -1. */ + strlist_t data_names; + /* Flag to indicated that either one of the next previous fields + is used. This is only needed for better readability. */ + int used; + } signed_data; + + DEK *dek; + int last_was_session_key; + kbnode_t list; /* The current list of packets. */ + iobuf_t iobuf; /* Used to get the filename etc. */ + int trustletter; /* Temporary usage in list_node. */ + ulong symkeys; /* Number of symmetrically encrypted session keys. */ + struct kidlist_item *pkenc_list; /* List of encryption packets. */ + struct symlist_item *symenc_list; /* List of sym. encryption packets. */ + int seen_pkt_encrypted_aead; /* PKT_ENCRYPTED_AEAD packet seen. */ + int seen_pkt_encrypted_mdc; /* PKT_ENCRYPTED_MDC packet seen. */ + struct { + unsigned int sig_seen:1; /* Set to true if a signature packet + has been seen. */ + unsigned int data:1; /* Any data packet seen */ + unsigned int uncompress_failed:1; + } any; +}; + + +/* Counter with the number of literal data packets seen. Note that + * this is also bumped at the end of an encryption. This counter is + * used for a basic consistency check of a received PGP message. */ +static int literals_seen; + + +/*** Local prototypes. ***/ +static int do_proc_packets (ctrl_t ctrl, CTX c, iobuf_t a); +static void list_node (CTX c, kbnode_t node); +static void proc_tree (CTX c, kbnode_t node); + + +/*** Functions. ***/ + +/* Reset the literal data counter. This is required to setup a new + * decryption or verification context. */ +void +reset_literals_seen(void) +{ + literals_seen = 0; +} + + +static void +release_list( CTX c ) +{ + 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; + while (c->symenc_list) + { + struct symlist_item *tmp = c->symenc_list->next; + xfree (c->symenc_list); + c->symenc_list = tmp; + } + c->symenc_list = NULL; + c->list = NULL; + c->any.data = 0; + c->any.uncompress_failed = 0; + c->last_was_session_key = 0; + c->seen_pkt_encrypted_aead = 0; + c->seen_pkt_encrypted_mdc = 0; + xfree (c->dek); + c->dek = NULL; +} + + +static int +add_onepass_sig (CTX c, PACKET *pkt) +{ + kbnode_t 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); + } + + 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_t 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 item at the end. */ + node = new_kbnode (pkt); + add_kbnode (c->list, node); + + return 1; +} + + +static gpg_error_t +symkey_decrypt_seskey (DEK *dek, byte *seskey, size_t slen) +{ + gpg_error_t err; + gcry_cipher_hd_t hd; + enum gcry_cipher_modes ciphermode; + unsigned int noncelen, keylen; + + if (dek->use_aead) + { + err = openpgp_aead_algo_info (dek->use_aead, &ciphermode, &noncelen); + if (err) + return err; + } + else + { + ciphermode = GCRY_CIPHER_MODE_CFB; + noncelen = 0; + } + + /* Check that the session key has a size of 16 to 32 bytes. */ + if ((dek->use_aead && (slen < (noncelen + 16 + 16) + || slen > (noncelen + 32 + 16))) + || (!dek->use_aead && (slen < 17 || slen > 33))) + { + log_error ( _("weird size for an encrypted session key (%d)\n"), + (int)slen); + return gpg_error (GPG_ERR_BAD_KEY); + } + + err = openpgp_cipher_open (&hd, dek->algo, ciphermode, 1); + if (!err) + err = gcry_cipher_setkey (hd, dek->key, dek->keylen); + if (!err) + err = gcry_cipher_setiv (hd, noncelen? seskey : NULL, noncelen); + if (err) + goto leave; + + if (dek->use_aead) + { + byte ad[4]; + + ad[0] = (0xc0 | PKT_SYMKEY_ENC); + ad[1] = 5; + ad[2] = dek->algo; + ad[3] = dek->use_aead; + err = gcry_cipher_authenticate (hd, ad, 4); + if (err) + goto leave; + gcry_cipher_final (hd); + keylen = slen - noncelen - 16; + err = gcry_cipher_decrypt (hd, seskey+noncelen, keylen, NULL, 0); + if (err) + goto leave; + err = gcry_cipher_checktag (hd, seskey+noncelen+keylen, 16); + if (err) + goto leave; + /* Now we replace the dek components with the real session key to + * decrypt the contents of the sequencing packet. */ + if (keylen > DIM(dek->key)) + { + err = gpg_error (GPG_ERR_TOO_LARGE); + goto leave; + } + dek->keylen = keylen; + memcpy (dek->key, seskey + noncelen, dek->keylen); + } + else + { + gcry_cipher_decrypt (hd, seskey, slen, NULL, 0); + + /* Here we can only test whether the algo given in decrypted + * session key is a valid OpenPGP algo. With 11 defined + * symmetric algorithms we will miss 4.3% of wrong passphrases + * here. The actual checking is done later during bulk + * decryption; we can't bring this check forward easily. We + * need to use the GPG_ERR_CHECKSUM so that we won't run into + * the gnupg < 2.2 bug compatible case which would terminate the + * process on GPG_ERR_CIPHER_ALGO. Note that with AEAD (above) + * we will have a reliable test here. */ + if (openpgp_cipher_test_algo (seskey[0]) + || openpgp_cipher_get_algo_keylen (seskey[0]) != slen - 1) + { + err = gpg_error (GPG_ERR_CHECKSUM); + goto leave; + } + + /* Now we replace the dek components with the real session key to + * decrypt the contents of the sequencing packet. */ + keylen = slen-1; + if (keylen > DIM(dek->key)) + { + err = gpg_error (GPG_ERR_TOO_LARGE); + goto leave; + } + dek->algo = seskey[0]; + dek->keylen = slen-1; + memcpy (dek->key, seskey + 1, dek->keylen); + } + + /*log_hexdump( "thekey", dek->key, dek->keylen );*/ + + leave: + gcry_cipher_close (hd); + return 0; +} + + +static void +proc_symkey_enc (CTX c, PACKET *pkt) +{ + gpg_error_t err; + 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 = openpgp_cipher_algo_name (algo); + const char *a = (enc->aead_algo ? openpgp_aead_algo_name (enc->aead_algo) + /**/ : "CFB"); + + if (!openpgp_cipher_test_algo (algo)) + { + if (!opt.quiet) + { + /* Note: TMPSTR is only used to avoid i18n changes. */ + char *tmpstr = xstrconcat (s, ".", a, NULL); + if (enc->seskeylen) + log_info (_("%s encrypted session key\n"), tmpstr); + else + log_info (_("%s encrypted data\n"), tmpstr); + xfree (tmpstr); + } + } + else + { + log_error (_("encrypted with unknown algorithm %d\n"), algo); + s = NULL; /* Force a goto leave. */ + } + + if (openpgp_md_test_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 + { + c->dek = passphrase_to_dek (algo, &enc->s2k, 0, 0, NULL, + GETPASSWORD_FLAG_SYMDECRYPT, NULL); + if (c->dek) + { + c->dek->symmetric = 1; + c->dek->use_aead = enc->aead_algo; + + /* 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) + { + err = symkey_decrypt_seskey (c->dek, + enc->seskey, enc->seskeylen); + if (err) + { + log_info ("decryption of the symmetrically encrypted" + " session key failed: %s\n", + gpg_strerror (err)); + if (gpg_err_code (err) != GPG_ERR_BAD_KEY + && gpg_err_code (err) != GPG_ERR_CHECKSUM) + log_fatal ("process terminated to be bug compatible\n"); + else + write_status_text (STATUS_ERROR, + "symkey_decrypt.maybe_error" + " 11_BAD_PASSPHRASE"); + + if (c->dek->s2k_cacheid[0]) + { + if (opt.debug) + log_debug ("cleared passphrase cached with ID:" + " %s\n", c->dek->s2k_cacheid); + passphrase_clear_cache (c->dek->s2k_cacheid); + } + xfree (c->dek); + c->dek = NULL; + } + } + else + c->dek->algo_info_printed = 1; + } + } + } + + leave: + /* Record infos from the packet. */ + { + struct symlist_item *symitem; + symitem = xcalloc (1, sizeof *symitem); + if (enc) + { + symitem->cipher_algo = enc->cipher_algo; + symitem->cfb_mode = !enc->aead_algo; + } + else + symitem->other_error = 1; + symitem->next = c->symenc_list; + c->symenc_list = symitem; + } + c->symkeys++; + free_packet (pkt, NULL); +} + + +static void +proc_pubkey_enc (ctrl_t ctrl, 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]; + /* FIXME: For ECC support we need to map the OpenPGP algo number + to the Libgcrypt defined one. This is due a chicken-egg + problem: We need to have code in Libgcrypt for a new + algorithm so to implement a proposed new algorithm before the + IANA will finally assign an OpenPGP identifier. */ + snprintf (buf, sizeof 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 (enc->pubkey_algo == PUBKEY_ALGO_ELGAMAL_E + || enc->pubkey_algo == PUBKEY_ALGO_ECDH + || enc->pubkey_algo == PUBKEY_ALGO_RSA + || enc->pubkey_algo == PUBKEY_ALGO_RSA_E + || enc->pubkey_algo == PUBKEY_ALGO_ELGAMAL) + { + /* Note that we also allow type 20 Elgamal keys for decryption. + There are still a couple of those keys in active use as a + subkey. */ + + /* FIXME: Store this all in a list and process it later so that + we can prioritize what key to use. This gives a better user + experience if wildcard keyids are used. */ + if (!c->dek && ((!enc->keyid[0] && !enc->keyid[1]) + || opt.try_all_secrets + || have_secret_key_with_kid (enc->keyid))) + { + if(opt.list_only) + result = GPG_ERR_MISSING_ACTION; /* fixme: Use better error code. */ + else + { + c->dek = xmalloc_secure_clear (sizeof *c->dek); + if ((result = get_session_key (ctrl, enc, c->dek))) + { + /* Error: Delete the DEK. */ + xfree (c->dek); + c->dek = NULL; + } + } + } + else + result = GPG_ERR_NO_SECKEY; + } + else + result = GPG_ERR_PUBKEY_ALGO; + + if (1) + { + /* 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, NULL); +} + + +/* + * Print the list of public key encrypted packets which we could + * not decrypt. + */ +static void +print_pkenc_list (ctrl_t ctrl, 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 = openpgp_pk_algo_name (list->pubkey_algo); + pk = xmalloc_clear (sizeof *pk); + + if (!algstr) + algstr = "[?]"; + pk->pubkey_algo = list->pubkey_algo; + if (!get_pubkey (ctrl, 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 (ctrl, list->kid); + log_printf (_(" \"%s\"\n"), p); + xfree (p); + } + else + log_info (_("encrypted with %s key, ID %s\n"), + algstr, keystr(list->kid)); + + free_public_key (pk); + + if (gpg_err_code (list->reason) == GPG_ERR_NO_SECKEY) + { + if (is_status_enabled()) + { + char buf[20]; + snprintf (buf, sizeof buf, "%08lX%08lX", + (ulong)list->kid[0], (ulong)list->kid[1]); + write_status_text (STATUS_NO_SECKEY, buf); + } + } + else if (gpg_err_code (list->reason) == GPG_ERR_MISSING_ACTION) + { + /* Not tested for secret key due to --list-only mode. */ + } + else if (list->reason) + { + log_info (_("public key decryption failed: %s\n"), + gpg_strerror (list->reason)); + if (gpg_err_source (list->reason) == GPG_ERR_SOURCE_SCD + && gpg_err_code (list->reason) == GPG_ERR_INV_ID) + print_further_info ("a reason might be a card with replaced keys"); + write_status_error ("pkdecrypt_failed", list->reason); + } + } +} + + +static void +proc_encrypted (CTX c, PACKET *pkt) +{ + int result = 0; + int early_plaintext = literals_seen; + unsigned int compliance_de_vs = 0; + + if (pkt->pkttype == PKT_ENCRYPTED_AEAD) + c->seen_pkt_encrypted_aead = 1; + if (pkt->pkttype == PKT_ENCRYPTED_MDC) + c->seen_pkt_encrypted_mdc = 1; + + if (early_plaintext) + { + log_info (_("WARNING: multiple plaintexts seen\n")); + write_status_errcode ("decryption.early_plaintext", GPG_ERR_BAD_DATA); + /* We fail only later so that we can print some more info first. */ + } + + 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->ctrl, c->pkenc_list, 1 ); + print_pkenc_list (c->ctrl, 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; + STRING2KEY *s2k = NULL; + int canceled; + + 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. */ + algo = opt.def_cipher_algo; + if (algo) + log_info (_("assuming %s encrypted data\n"), + openpgp_cipher_algo_name (algo)); + else if (openpgp_cipher_test_algo (CIPHER_ALGO_IDEA)) + { + algo = opt.def_cipher_algo; + if (!algo) + algo = opt.s2k_cipher_algo; + log_info (_("IDEA cipher unavailable, " + "optimistically attempting to use %s instead\n"), + openpgp_cipher_algo_name (algo)); + } + else + { + algo = CIPHER_ALGO_IDEA; + if (!opt.s2k_digest_algo) + { + /* If no digest is given we assume SHA-1. */ + s2kbuf.mode = 0; + s2kbuf.hash_algo = DIGEST_ALGO_SHA1; + s2k = &s2kbuf; + } + log_info (_("assuming %s encrypted data\n"), "IDEA"); + } + + c->dek = passphrase_to_dek (algo, s2k, 0, 0, NULL, + GETPASSWORD_FLAG_SYMDECRYPT, &canceled); + if (c->dek) + c->dek->algo_info_printed = 1; + else if (canceled) + result = gpg_error (GPG_ERR_CANCELED); + else + result = gpg_error (GPG_ERR_INV_PASSPHRASE); + } + } + else if (!c->dek) + result = GPG_ERR_NO_SECKEY; + + /* Compute compliance with CO_DE_VS. */ + if (!result && is_status_enabled () + /* Overriding session key voids compliance. */ + && !opt.override_session_key + /* Check symmetric cipher. */ + && gnupg_gcrypt_is_compliant (CO_DE_VS) + && gnupg_cipher_is_compliant (CO_DE_VS, c->dek->algo, + GCRY_CIPHER_MODE_CFB)) + { + struct kidlist_item *i; + struct symlist_item *si; + int compliant = 1; + PKT_public_key *pk = xmalloc (sizeof *pk); + + if ( !(c->pkenc_list || c->symkeys) ) + log_debug ("%s: where else did the session key come from?\n", __func__); + + /* Check that all seen symmetric key packets use compliant + * algos. This is so that no non-compliant encrypted session + * key can be sneaked in. */ + for (si = c->symenc_list; si && compliant; si = si->next) + { + if (!si->cfb_mode + || !gnupg_cipher_is_compliant (CO_DE_VS, si->cipher_algo, + GCRY_CIPHER_MODE_CFB)) + compliant = 0; + } + + /* Check that every known public key used to encrypt the session key + * is compliant. */ + for (i = c->pkenc_list; i && compliant; i = i->next) + { + memset (pk, 0, sizeof *pk); + pk->pubkey_algo = i->pubkey_algo; + if (!get_pubkey (c->ctrl, pk, i->kid) + && !gnupg_pk_is_compliant (CO_DE_VS, pk->pubkey_algo, 0, + pk->pkey, nbits_from_pk (pk), NULL)) + compliant = 0; + release_public_key_parts (pk); + } + + xfree (pk); + + if (compliant) + compliance_de_vs |= 1; + } + + + if (!result) + { + int compl_error; + result = decrypt_data (c->ctrl, c, pkt->pkt.encrypted, c->dek, + &compl_error); + if (!result && !compl_error) + compliance_de_vs |= 2; + } + + /* Trigger the deferred error. */ + if (!result && early_plaintext) + result = gpg_error (GPG_ERR_BAD_DATA); + + if (result == -1) + ; + else if (!result + && !opt.ignore_mdc_error + && !pkt->pkt.encrypted->mdc_method + && !pkt->pkt.encrypted->aead_algo) + { + /* The message has been decrypted but does not carry an MDC. + * The option --ignore-mdc-error has also not been used. To + * avoid attacks changing an MDC message to a non-MDC message, + * we fail here. */ + log_error (_("WARNING: message was not integrity protected\n")); + if (!pkt->pkt.encrypted->mdc_method + && (openpgp_cipher_get_algo_blklen (c->dek->algo) == 8 + || c->dek->algo == CIPHER_ALGO_TWOFISH)) + { + /* Before 2.2.8 we did not fail hard for a missing MDC if + * one of the old ciphers where used. Although these cases + * are rare in practice we print a hint on how to decrypt + * such messages. */ + log_string + (GPGRT_LOG_INFO, + _("Hint: If this message was created before the year 2003 it is\n" + "likely that this message is legitimate. This is because back\n" + "then integrity protection was not widely used.\n")); + log_info (_("Use the option '%s' to decrypt anyway.\n"), + "--ignore-mdc-error"); + write_status_errcode ("nomdc_with_legacy_cipher", + GPG_ERR_DECRYPT_FAILED); + } + log_info (_("decryption forced to fail!\n")); + write_status (STATUS_DECRYPTION_FAILED); + } + else if (!result || (gpg_err_code (result) == GPG_ERR_BAD_SIGNATURE + && !pkt->pkt.encrypted->aead_algo + && opt.ignore_mdc_error)) + { + /* All is fine or for an MDC message the MDC failed but the + * --ignore-mdc-error option is active. For compatibility + * reasons we issue GOODMDC also for AEAD messages. */ + write_status (STATUS_DECRYPTION_OKAY); + if (opt.verbose > 1) + log_info(_("decryption okay\n")); + + if (pkt->pkt.encrypted->aead_algo) + { + write_status (STATUS_GOODMDC); + compliance_de_vs |= 4; + } + else if (pkt->pkt.encrypted->mdc_method && !result) + { + write_status (STATUS_GOODMDC); + compliance_de_vs |= 4; + } + else + log_info (_("WARNING: message was not integrity protected\n")); + } + else if (gpg_err_code (result) == GPG_ERR_BAD_SIGNATURE + || gpg_err_code (result) == GPG_ERR_TRUNCATED) + { + glo_ctrl.lasterr = result; + log_error (_("WARNING: encrypted message has been manipulated!\n")); + write_status (STATUS_BADMDC); + write_status (STATUS_DECRYPTION_FAILED); + } + else + { + if (gpg_err_code (result) == GPG_ERR_BAD_KEY + || gpg_err_code (result) == GPG_ERR_CHECKSUM + || gpg_err_code (result) == GPG_ERR_CIPHER_ALGO) + { + if (c->symkeys) + write_status_text (STATUS_ERROR, + "symkey_decrypt.maybe_error" + " 11_BAD_PASSPHRASE"); + + if (*c->dek->s2k_cacheid != '\0') + { + if (opt.debug) + log_debug ("cleared passphrase cached with ID: %s\n", + c->dek->s2k_cacheid); + passphrase_clear_cache (c->dek->s2k_cacheid); + } + } + glo_ctrl.lasterr = result; + write_status (STATUS_DECRYPTION_FAILED); + log_error (_("decryption failed: %s\n"), gpg_strerror (result)); + /* Hmmm: does this work when we have encrypted using multiple + * ways to specify the session key (symmmetric and PK). */ + } + + + /* If we concluded that the decryption was compliant, issue a + * compliance status before the end of the decryption status. */ + if (compliance_de_vs == (4|2|1)) + { + write_status_strings (STATUS_DECRYPTION_COMPLIANCE_MODE, + gnupg_status_compliance_flag (CO_DE_VS), + NULL); + } + + xfree (c->dek); + c->dek = NULL; + free_packet (pkt, NULL); + c->last_was_session_key = 0; + write_status (STATUS_END_DECRYPTION); + + /* Bump the counter even if we have not seen a literal data packet + * inside an encryption container. This acts as a sentinel in case + * a misplace extra literal data packets follows after this + * encrypted packet. */ + literals_seen++; + + /* The --require-compliance option allows to simplify decryption in + * de-vs compliance mode by just looking at the exit status. */ + if (opt.flags.require_compliance + && opt.compliance == CO_DE_VS + && compliance_de_vs != (4|2|1)) + { + compliance_failure (); + } +} + + +static int +have_seen_pkt_encrypted_aead_or_mdc( CTX c ) +{ + CTX cc; + + for (cc = c; cc; cc = cc->anchor) + { + if (cc->seen_pkt_encrypted_aead) + return 1; + if (cc->seen_pkt_encrypted_mdc) + return 1; + } + + return 0; +} + + +static void +proc_plaintext( CTX c, PACKET *pkt ) +{ + PKT_plaintext *pt = pkt->pkt.plaintext; + int any, clearsig, rc; + kbnode_t n; + + /* This is a literal data packet. Bumb a counter for later checks. */ + literals_seen++; + + if (pt->namelen == 8 && !memcmp( pt->name, "_CONSOLE", 8)) + log_info (_("Note: sender requested \"for-your-eyes-only\"\n")); + else if (opt.verbose) + { + /* We don't use print_utf8_buffer because that would require a + * string change which we don't want in 2.2. It is also not + * clear whether the filename is always utf-8 encoded. */ + char *tmp = make_printable_string (pt->name, pt->namelen, 0); + log_info (_("original file name='%.*s'\n"), (int)strlen (tmp), tmp); + xfree (tmp); + } + + free_md_filter_context (&c->mfx); + if (gcry_md_open (&c->mfx.md, 0, 0)) + BUG (); + /* 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 = 0; + for (n=c->list; n; n = n->next ) + { + if (n->pkt->pkttype == PKT_ONEPASS_SIG) + { + /* The onepass signature case. */ + if (n->pkt->pkt.onepass_sig->digest_algo) + { + if (!opt.skip_verify) + gcry_md_enable (c->mfx.md, + n->pkt->pkt.onepass_sig->digest_algo); + + any = 1; + } + } + else if (n->pkt->pkttype == PKT_GPG_CONTROL + && n->pkt->pkt.gpg_control->control == CTRLPKT_CLEARSIGN_START) + { + /* 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++) + if (!opt.skip_verify) + gcry_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) + { + /* The SIG+LITERAL case that PGP used to use. */ + if (!opt.skip_verify) + gcry_md_enable (c->mfx.md, n->pkt->pkt.signature->digest_algo); + any = 1; + } + } + + if (!any && !opt.skip_verify && !have_seen_pkt_encrypted_aead_or_mdc(c)) + { + /* 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 there are the historical answer. */ + gcry_md_enable (c->mfx.md, DIGEST_ALGO_RMD160); + gcry_md_enable (c->mfx.md, DIGEST_ALGO_SHA1); + } + if (DBG_HASHING) + { + gcry_md_debug (c->mfx.md, "verify"); + if (c->mfx.md2) + gcry_md_debug (c->mfx.md2, "verify2"); + } + + rc=0; + + if (literals_seen > 1) + { + log_info (_("WARNING: multiple plaintexts seen\n")); + + if (!opt.flags.allow_multiple_messages) + { + write_status_text (STATUS_ERROR, "proc_pkt.plaintext 89_BAD_DATA"); + log_inc_errorcount (); + rc = gpg_error (GPG_ERR_UNEXPECTED); + } + } + + if (!rc) + { + /* It we are in --verify mode, we do not want to output the + * signed text. However, if --output is also used we do what + * has been requested and write out the signed data. */ + rc = handle_plaintext (pt, &c->mfx, + (opt.outfp || opt.outfile)? 0 : c->sigs_only, + clearsig); + if (gpg_err_code (rc) == GPG_ERR_EACCES && !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", gpg_strerror (rc)); + + free_packet (pkt, NULL); + 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_t a, void *info) +{ + if ( ((CTX)info)->signed_data.used + && ((CTX)info)->signed_data.data_fd != -1) + return proc_signature_packets_by_fd (((CTX)info)->ctrl, info, a, + ((CTX)info)->signed_data.data_fd); + else + return proc_signature_packets (((CTX)info)->ctrl, info, a, + ((CTX)info)->signed_data.data_names, + ((CTX)info)->sigfilename ); +} + + +static int +proc_encrypt_cb (iobuf_t a, void *info ) +{ + CTX c = info; + return proc_encryption_packets (c->ctrl, info, a ); +} + + +static int +proc_compressed (CTX c, PACKET *pkt) +{ + PKT_compressed *zd = pkt->pkt.compressed; + int rc; + + /*printf("zip: compressed data packet\n");*/ + if (c->sigs_only) + rc = handle_compressed (c->ctrl, c, zd, proc_compressed_cb, c); + else if( c->encrypt_only ) + rc = handle_compressed (c->ctrl, c, zd, proc_encrypt_cb, c); + else + rc = handle_compressed (c->ctrl, c, zd, NULL, NULL); + + if (gpg_err_code (rc) == GPG_ERR_BAD_DATA) + { + if (!c->any.uncompress_failed) + { + CTX cc; + + for (cc=c; cc; cc = cc->anchor) + cc->any.uncompress_failed = 1; + log_error ("uncompressing failed: %s\n", gpg_strerror (rc)); + } + } + else if (rc) + log_error ("uncompressing failed: %s\n", gpg_strerror (rc)); + + free_packet (pkt, NULL); + c->last_was_session_key = 0; + return rc; +} + + +/* + * Check the signature. If R_PK is not NULL a copy of the public key + * used to verify the signature will be stored there, or NULL if not + * found. If FORCED_PK is not NULL, this public key is used to verify + * _data signatures_ and no key lookup is done. Returns: 0 = valid + * signature or an error code + */ +static int +do_check_sig (CTX c, kbnode_t node, + PKT_public_key *forced_pk, int *is_selfsig, + int *is_expkey, int *is_revkey, PKT_public_key **r_pk) +{ + PKT_signature *sig; + gcry_md_hd_t md = NULL; + gcry_md_hd_t md2 = NULL; + gcry_md_hd_t md_good = NULL; + int algo, rc; + + if (r_pk) + *r_pk = NULL; + + log_assert (node->pkt->pkttype == PKT_SIGNATURE); + if (is_selfsig) + *is_selfsig = 0; + sig = node->pkt->pkt.signature; + + algo = sig->digest_algo; + rc = openpgp_md_test_algo (algo); + if (rc) + return rc; + + if (sig->sig_class == 0x00) + { + if (c->mfx.md) + { + if (gcry_md_copy (&md, c->mfx.md )) + BUG (); + } + else /* detached signature */ + { + /* check_signature() will enable the md. */ + if (gcry_md_open (&md, 0, 0 )) + BUG (); + } + } + 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) + { + if (gcry_md_copy (&md, c->mfx.md )) + BUG (); + if (c->mfx.md2 && gcry_md_copy (&md2, c->mfx.md2)) + BUG (); + } + else /* detached signature */ + { + log_debug ("Do we really need this here?"); + /* check_signature() will enable the md*/ + if (gcry_md_open (&md, 0, 0 )) + BUG (); + if (gcry_md_open (&md2, 0, 0 )) + BUG (); + } + } + 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->ctrl, c->list, node, is_selfsig); + } + else if (sig->sig_class == 0x20) + { + log_error (_("standalone revocation - " + "use \"gpg --import\" to apply\n")); + return GPG_ERR_NOT_PROCESSED; + } + else + { + log_error ("invalid root packet for sigclass %02x\n", sig->sig_class); + return GPG_ERR_SIG_CLASS; + } + } + else + return GPG_ERR_SIG_CLASS; + + /* We only get here if we are checking the signature of a binary + (0x00) or text document (0x01). */ + rc = check_signature2 (c->ctrl, sig, md, + forced_pk, + NULL, is_expkey, is_revkey, r_pk); + if (! rc) + md_good = md; + else if (gpg_err_code (rc) == GPG_ERR_BAD_SIGNATURE && md2) + { + PKT_public_key *pk2; + + rc = check_signature2 (c->ctrl, sig, md2, + forced_pk, + NULL, is_expkey, is_revkey, + r_pk? &pk2 : NULL); + if (!rc) + { + md_good = md2; + if (r_pk) + { + free_public_key (*r_pk); + *r_pk = pk2; + } + } + } + + if (md_good) + { + unsigned char *buffer = gcry_md_read (md_good, sig->digest_algo); + sig->digest_len = gcry_md_get_algo_dlen (map_md_openpgp_to_gcry (algo)); + memcpy (sig->digest, buffer, sig->digest_len); + } + + gcry_md_close (md); + gcry_md_close (md2); + + return rc; +} + + +static void +print_userid (PACKET *pkt) +{ + if (!pkt) + BUG(); + + if (pkt->pkttype != PKT_USER_ID) + { + es_printf ("ERROR: unexpected packet type %d", pkt->pkttype ); + return; + } + if (opt.with_colons) + { + if (pkt->pkt.user_id->attrib_data) + es_printf("%u %lu", + pkt->pkt.user_id->numattribs, + pkt->pkt.user_id->attrib_len); + else + es_write_sanitized (es_stdout, pkt->pkt.user_id->name, + pkt->pkt.user_id->len, ":", NULL); + } + else + print_utf8_buffer (es_stdout, pkt->pkt.user_id->name, + pkt->pkt.user_id->len ); +} + + +/* + * List the keyblock in a user friendly way + */ +static void +list_node (CTX c, kbnode_t node) +{ + if (!node) + ; + else if (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 (pk->flags.primary) + c->trustletter = (opt.fast_list_mode + ? 0 + : get_validity_info + (c->ctrl, + node->pkt->pkttype == PKT_PUBLIC_KEY + ? node : NULL, + pk, NULL)); + es_printf ("%s:", pk->flags.primary? "pub":"sub" ); + if (c->trustletter) + es_putc (c->trustletter, es_stdout); + es_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 (pk->flags.primary && !opt.fast_list_mode) + es_putc (get_ownertrust_info (c->ctrl, pk, 1), es_stdout); + es_putc (':', es_stdout); + es_putc ('\n', es_stdout); + } + else + { + print_key_line (c->ctrl, es_stdout, pk, 0); + } + + if (opt.keyid_format == KF_NONE && !opt.with_colons) + ; /* Already printed. */ + else if ((pk->flags.primary && opt.fingerprint) || opt.fingerprint > 1) + print_fingerprint (c->ctrl, NULL, pk, 0); + + if (pk->flags.primary) + { + int kl = opt.keyid_format == KF_NONE? 0 : keystrlen (); + + /* Now list all userids with their signatures. */ + for (node = node->next; node; node = node->next) + { + if (node->pkt->pkttype == PKT_SIGNATURE) + { + list_node (c, node ); + } + else if (node->pkt->pkttype == PKT_USER_ID) + { + if (opt.with_colons) + es_printf ("%s:::::::::", + node->pkt->pkt.user_id->attrib_data?"uat":"uid"); + else + es_printf ("uid%*s", + kl + (opt.legacy_list_mode? 9:11), + "" ); + print_userid (node->pkt); + if (opt.with_colons) + es_putc (':', es_stdout); + es_putc ('\n', es_stdout); + } + else if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY) + { + list_node(c, node ); + } + } + } + } + else if (node->pkt->pkttype == PKT_SECRET_KEY + || node->pkt->pkttype == PKT_SECRET_SUBKEY) + { + + log_debug ("FIXME: No way to print secret key packets here\n"); + /* fixme: We may use a function to turn a secret key packet into + a public key one and use that here. */ + } + 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) + es_fputs ("rev", es_stdout); + else + es_fputs ("sig", es_stdout); + if (opt.check_sigs) + { + fflush (stdout); + rc2 = do_check_sig (c, node, NULL, &is_selfsig, NULL, NULL, NULL); + switch (gpg_err_code (rc2)) + { + case 0: sigrc = '!'; break; + case GPG_ERR_BAD_SIGNATURE: sigrc = '-'; break; + case GPG_ERR_NO_PUBKEY: + case GPG_ERR_UNUSABLE_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 ) + { + keyid_from_pk (c->list->pkt->pkt.public_key, keyid); + + if (keyid[0] == sig->keyid[0] && keyid[1] == sig->keyid[1]) + is_selfsig = 1; + } + } + + if (opt.with_colons) + { + es_putc (':', es_stdout); + if (sigrc != ' ') + es_putc (sigrc, es_stdout); + es_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) + es_printf ("%d %d",sig->trust_depth,sig->trust_value); + es_putc (':', es_stdout); + + if (sig->trust_regexp) + es_write_sanitized (es_stdout, sig->trust_regexp, + strlen (sig->trust_regexp), ":", NULL); + es_putc (':', es_stdout); + } + else + es_printf ("%c %s %s ", + sigrc, keystr (sig->keyid), datestr_from_sig(sig)); + if (sigrc == '%') + es_printf ("[%s] ", gpg_strerror (rc2) ); + else if (sigrc == '?') + ; + else if (is_selfsig) + { + if (opt.with_colons) + es_putc (':', es_stdout); + es_fputs (sig->sig_class == 0x18? "[keybind]":"[selfsig]", es_stdout); + if (opt.with_colons) + es_putc (':', es_stdout); + } + else if (!opt.fast_list_mode) + { + p = get_user_id (c->ctrl, sig->keyid, &n, NULL); + es_write_sanitized (es_stdout, p, n, + opt.with_colons?":":NULL, NULL ); + xfree (p); + } + if (opt.with_colons) + es_printf (":%02x%c:", sig->sig_class, sig->flags.exportable?'x':'l'); + es_putc ('\n', es_stdout); + } + else + log_error ("invalid node with packet of type %d\n", node->pkt->pkttype); +} + + +int +proc_packets (ctrl_t ctrl, void *anchor, iobuf_t a ) +{ + int rc; + CTX c = xmalloc_clear (sizeof *c); + + c->ctrl = ctrl; + c->anchor = anchor; + rc = do_proc_packets (ctrl, c, a); + xfree (c); + + return rc; +} + + +int +proc_signature_packets (ctrl_t ctrl, void *anchor, iobuf_t a, + strlist_t signedfiles, const char *sigfilename ) +{ + CTX c = xmalloc_clear (sizeof *c); + int rc; + + c->ctrl = ctrl; + c->anchor = anchor; + c->sigs_only = 1; + + c->signed_data.data_fd = -1; + c->signed_data.data_names = signedfiles; + c->signed_data.used = !!signedfiles; + + c->sigfilename = sigfilename; + rc = do_proc_packets (ctrl, 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 = GPG_ERR_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_signature_packets_by_fd (ctrl_t ctrl, + void *anchor, iobuf_t a, int signed_data_fd ) +{ + int rc; + CTX c; + + c = xtrycalloc (1, sizeof *c); + if (!c) + return gpg_error_from_syserror (); + + c->ctrl = ctrl; + c->anchor = anchor; + c->sigs_only = 1; + + c->signed_data.data_fd = signed_data_fd; + c->signed_data.data_names = NULL; + c->signed_data.used = (signed_data_fd != -1); + + rc = do_proc_packets (ctrl, 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 = gpg_error (GPG_ERR_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 (ctrl_t ctrl, void *anchor, iobuf_t a ) +{ + CTX c = xmalloc_clear (sizeof *c); + int rc; + + c->ctrl = ctrl; + c->anchor = anchor; + c->encrypt_only = 1; + rc = do_proc_packets (ctrl, c, a); + xfree (c); + return rc; +} + + +static int +check_nesting (CTX c) +{ + int level; + + for (level=0; c; c = c->anchor) + level++; + + if (level > MAX_NESTING_DEPTH) + { + log_error ("input data with too deeply nested packets\n"); + write_status_text (STATUS_UNEXPECTED, "1"); + return GPG_ERR_BAD_DATA; + } + + return 0; +} + + +static int +do_proc_packets (ctrl_t ctrl, CTX c, iobuf_t a) +{ + PACKET *pkt; + struct parse_packet_ctx_s parsectx; + int rc = 0; + int any_data = 0; + int newpkt; + + rc = check_nesting (c); + if (rc) + return rc; + + pkt = xmalloc( sizeof *pkt ); + c->iobuf = a; + init_packet(pkt); + init_parse_packet (&parsectx, a); + while ((rc=parse_packet (&parsectx, pkt)) != -1) + { + any_data = 1; + if (rc) + { + free_packet (pkt, &parsectx); + /* Stop processing when an invalid packet has been encountered + * but don't do so when we are doing a --list-packets. */ + if (gpg_err_code (rc) == GPG_ERR_INV_PACKET + && opt.list_packets == 0) + break; + continue; + } + newpkt = -1; + if (opt.list_packets) + { + switch (pkt->pkttype) + { + case PKT_PUBKEY_ENC: proc_pubkey_enc (ctrl, c, pkt); break; + case PKT_SYMKEY_ENC: proc_symkey_enc (c, pkt); break; + case PKT_ENCRYPTED: + case PKT_ENCRYPTED_MDC: + case PKT_ENCRYPTED_AEAD:proc_encrypted (c, pkt); break; + case PKT_COMPRESSED: rc = 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: + case PKT_ENCRYPTED_AEAD: + write_status_text( STATUS_UNEXPECTED, "0" ); + rc = GPG_ERR_UNEXPECTED; + goto leave; + + case PKT_SIGNATURE: newpkt = add_signature (c, pkt); break; + case PKT_PLAINTEXT: proc_plaintext (c, pkt); break; + case PKT_COMPRESSED: rc = 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 = GPG_ERR_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 (ctrl, c, pkt); break; + case PKT_ENCRYPTED: + case PKT_ENCRYPTED_MDC: + case PKT_ENCRYPTED_AEAD: proc_encrypted (c, pkt); break; + case PKT_PLAINTEXT: proc_plaintext (c, pkt); break; + case PKT_COMPRESSED: rc = 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 (ctrl, c, pkt); break; + case PKT_SYMKEY_ENC: proc_symkey_enc (c, pkt); break; + case PKT_ENCRYPTED: + case PKT_ENCRYPTED_MDC: + case PKT_ENCRYPTED_AEAD: proc_encrypted (c, pkt); break; + case PKT_PLAINTEXT: proc_plaintext (c, pkt); break; + case PKT_COMPRESSED: rc = 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; + } + } + + if (rc) + goto leave; + + /* 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 between which adds just + * an extra layer. + * Hmmm: Rewrite this whole module here?? + */ + if (pkt->pkttype != PKT_SIGNATURE && pkt->pkttype != PKT_MDC) + c->any.data = (pkt->pkttype == PKT_PLAINTEXT); + + if (newpkt == -1) + ; + else if (newpkt) + { + pkt = xmalloc (sizeof *pkt); + init_packet (pkt); + } + else + free_packet (pkt, &parsectx); + } + + if (rc == GPG_ERR_INV_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, &parsectx); + deinit_parse_packet (&parsectx); + 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 (CTX c, PKT_signature *sig) +{ + if (!sig->flags.pka_tried) + { + log_assert (!sig->pka_info); + sig->flags.pka_tried = 1; + sig->pka_info = get_pka_address (sig); + if (sig->pka_info) + { + char *url; + unsigned char *fpr; + size_t fprlen; + + if (!gpg_dirmngr_get_pka (c->ctrl, sig->pka_info->email, + &fpr, &fprlen, &url)) + { + if (fpr && fprlen == sizeof sig->pka_info->fpr) + { + memcpy (sig->pka_info->fpr, fpr, fprlen); + if (url) + { + sig->pka_info->valid = 1; + if (!*url) + xfree (url); + else + sig->pka_info->uri = url; + url = NULL; + } + } + xfree (fpr); + xfree (url); + } + } + } + return sig->pka_info? sig->pka_info->uri : NULL; +} + + +/* Return true if the AKL has the WKD method specified. */ +static int +akl_has_wkd_method (void) +{ + struct akl *akl; + + for (akl = opt.auto_key_locate; akl; akl = akl->next) + if (akl->type == AKL_WKD) + return 1; + return 0; +} + + +/* Return the ISSUER fingerprint buffer and its lenbgth at R_LEN. + * Returns NULL if not available. The returned buffer is valid as + * long as SIG is not modified. */ +const byte * +issuer_fpr_raw (PKT_signature *sig, size_t *r_len) +{ + const byte *p; + size_t n; + + p = parse_sig_subpkt (sig->hashed, SIGSUBPKT_ISSUER_FPR, &n); + if (p && n == 21 && p[0] == 4) + { + *r_len = n - 1; + return p+1; + } + *r_len = 0; + return NULL; +} + + +/* Return the ISSUER fingerprint string in human readable format if + * available. Caller must release the string. */ +/* FIXME: Move to another file. */ +char * +issuer_fpr_string (PKT_signature *sig) +{ + const byte *p; + size_t n; + + p = issuer_fpr_raw (sig, &n); + return p? bin2hex (p, n, NULL) : NULL; +} + + +static void +print_good_bad_signature (int statno, const char *keyid_str, kbnode_t un, + PKT_signature *sig, int rc) +{ + char *p; + + 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); + + xfree (p); +} + + +static int +check_sig_and_print (CTX c, kbnode_t node) +{ + PKT_signature *sig = node->pkt->pkt.signature; + const char *astr; + int rc; + int is_expkey = 0; + int is_revkey = 0; + char *issuer_fpr = NULL; + PKT_public_key *pk = NULL; /* The public key for the signature or NULL. */ + kbnode_t included_keyblock = NULL; + + 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_t n; + int n_onepass, n_sig; + +/* log_debug ("checking signature packet composition\n"); */ +/* dump_kbnode (c->list); */ + + n = c->list; + log_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 we wanted to disallow multiple sig verification, we'd do + something like this: + + if (n && !opt.allow_multisig_verification) + goto ambiguous; + + However, now that we have --allow-multiple-messages, this + can stay allowable as we can't get here unless multiple + messages (i.e. multiple literals) are allowed. */ + + 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; + } + } + + if (sig->signers_uid) + write_status_buffer (STATUS_NEWSIG, + sig->signers_uid, strlen (sig->signers_uid), 0); + else + write_status_text (STATUS_NEWSIG, NULL); + + astr = openpgp_pk_algo_name ( sig->pubkey_algo ); + issuer_fpr = issuer_fpr_string (sig); + + if (issuer_fpr) + { + log_info (_("Signature made %s\n"), asctimestamp(sig->timestamp)); + log_info (_(" using %s key %s\n"), + astr? astr: "?", issuer_fpr); + + } + else if (!keystrlen () || keystrlen () > 8) + { + log_info (_("Signature made %s\n"), asctimestamp(sig->timestamp)); + log_info (_(" using %s key %s\n"), + astr? astr: "?", keystr(sig->keyid)); + } + else /* Legacy format. */ + log_info (_("Signature made %s using %s key ID %s\n"), + asctimestamp(sig->timestamp), astr? astr: "?", + keystr(sig->keyid)); + + /* In verbose mode print the signers UID. */ + if (sig->signers_uid) + log_info (_(" issuer \"%s\"\n"), sig->signers_uid); + + rc = do_check_sig (c, node, NULL, NULL, &is_expkey, &is_revkey, &pk); + + /* If the key is not found but the signature includes a key block we + * use that key block for verification and on success import it. */ + if (gpg_err_code (rc) == GPG_ERR_NO_PUBKEY + && sig->flags.key_block + && opt.flags.auto_key_import) + { + PKT_public_key *included_pk; + const byte *kblock; + size_t kblock_len; + + included_pk = xcalloc (1, sizeof *included_pk); + kblock = parse_sig_subpkt (sig->hashed, SIGSUBPKT_KEY_BLOCK, &kblock_len); + if (kblock && kblock_len > 1 + && !get_pubkey_from_buffer (c->ctrl, included_pk, + kblock+1, kblock_len-1, + sig->keyid, &included_keyblock)) + { + rc = do_check_sig (c, node, included_pk, + NULL, &is_expkey, &is_revkey, &pk); + if (!rc) + { + /* The keyblock has been verified, we now import it. */ + rc = import_included_key_block (c->ctrl, included_keyblock); + } + + } + free_public_key (included_pk); + } + + /* If the key isn't found, check for a preferred keyserver. Note + * that this is only done if honor-keyserver-url has been set. We + * test for this in the loop so that we can show info about the + * preferred keyservers. */ + if (gpg_err_code (rc) == GPG_ERR_NO_PUBKEY + && sig->flags.pref_ks) + { + const byte *p; + int seq = 0; + size_t n; + int any_pref_ks = 0; + + 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_buffer (log_get_stream(), p, n); + log_printf ("\n"); + any_pref_ks = 1; + + 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; + + if (DBG_LOOKUP) + log_debug ("trying auto-key-retrieve method %s\n", + "Pref-KS"); + + free_public_key (pk); + pk = NULL; + glo_ctrl.in_auto_key_retrieve++; + res = keyserver_import_keyid (c->ctrl, sig->keyid,spec, + KEYSERVER_IMPORT_FLAG_QUICK); + glo_ctrl.in_auto_key_retrieve--; + if (!res) + rc = do_check_sig (c, node, NULL, + NULL, &is_expkey, &is_revkey, &pk); + else if (DBG_LOOKUP) + log_debug ("lookup via %s failed: %s\n", "Pref-KS", + gpg_strerror (res)); + free_keyserver_spec (spec); + + if (!rc) + break; + } + } + } + + if (any_pref_ks + && (opt.keyserver_options.options&KEYSERVER_AUTO_KEY_RETRIEVE) + && !(opt.keyserver_options.options&KEYSERVER_HONOR_KEYSERVER_URL)) + log_info (_("Note: Use '%s' to make use of this info\n"), + "--keyserver-option honor-keyserver-url"); + } + + /* If the above methods didn't work, our next try is to retrieve the + * key from the WKD. This requires that WKD is in the AKL and the + * Signer's UID is in the signature. */ + if (gpg_err_code (rc) == GPG_ERR_NO_PUBKEY + && (opt.keyserver_options.options & KEYSERVER_AUTO_KEY_RETRIEVE) + && !opt.flags.disable_signer_uid + && akl_has_wkd_method () + && sig->signers_uid) + { + int res; + + if (DBG_LOOKUP) + log_debug ("trying auto-key-retrieve method %s\n", "WKD"); + free_public_key (pk); + pk = NULL; + glo_ctrl.in_auto_key_retrieve++; + res = keyserver_import_wkd (c->ctrl, sig->signers_uid, + KEYSERVER_IMPORT_FLAG_QUICK, NULL, NULL); + glo_ctrl.in_auto_key_retrieve--; + /* Fixme: If the fingerprint is embedded in the signature, + * compare it to the fingerprint of the returned key. */ + if (!res) + rc = do_check_sig (c, node, NULL, NULL, &is_expkey, &is_revkey, &pk); + else if (DBG_LOOKUP) + log_debug ("lookup via %s failed: %s\n", "WKD", gpg_strerror (res)); + } + + /* If the avove methods didn't work, our next try is to use the URI + * from a DNS PKA record. This is a legacy method which will + * eventually be removed. */ + if (gpg_err_code (rc) == GPG_ERR_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 (c, 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); + if (spec) + { + if (DBG_LOOKUP) + log_debug ("trying auto-key-retrieve method %s\n", "PKA"); + + free_public_key (pk); + pk = NULL; + glo_ctrl.in_auto_key_retrieve++; + res = keyserver_import_keyid (c->ctrl, sig->keyid, spec, 1); + glo_ctrl.in_auto_key_retrieve--; + free_keyserver_spec (spec); + if (!res) + rc = do_check_sig (c, node, NULL, + NULL, &is_expkey, &is_revkey, &pk); + else if (DBG_LOOKUP) + log_debug ("lookup via %s failed: %s\n", "PKA", + gpg_strerror (res)); + } + } + } + + /* If the above methods didn't work, our next try is to locate + * the key via its fingerprint from a keyserver. This requires + * that the signers fingerprint is encoded in the signature. */ + if (gpg_err_code (rc) == GPG_ERR_NO_PUBKEY + && (opt.keyserver_options.options&KEYSERVER_AUTO_KEY_RETRIEVE) + && keyserver_any_configured (c->ctrl)) + { + int res; + const byte *p; + size_t n; + + p = issuer_fpr_raw (sig, &n); + if (p) + { + /* v4 packet with a SHA-1 fingerprint. */ + if (DBG_LOOKUP) + log_debug ("trying auto-key-retrieve method %s\n", "KS"); + + free_public_key (pk); + pk = NULL; + glo_ctrl.in_auto_key_retrieve++; + res = keyserver_import_fprint (c->ctrl, p, n, opt.keyserver, + KEYSERVER_IMPORT_FLAG_QUICK); + glo_ctrl.in_auto_key_retrieve--; + if (!res) + rc = do_check_sig (c, node, NULL, + NULL, &is_expkey, &is_revkey, &pk); + else if (DBG_LOOKUP) + log_debug ("lookup via %s failed: %s\n", "KS", gpg_strerror (res)); + } + } + + if (!rc || gpg_err_code (rc) == GPG_ERR_BAD_SIGNATURE) + { + kbnode_t un, keyblock; + int count = 0; + int statno; + char keyid_str[50]; + PKT_public_key *mainpk = 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; + + /* FIXME: We should have the public key in PK and thus the + * keyblock has already been fetched. Thus we could use the + * fingerprint or PK itself to lookup the entire keyblock. That + * would best be done with a cache. */ + if (included_keyblock) + { + keyblock = included_keyblock; + included_keyblock = NULL; + } + else + keyblock = get_pubkeyblock_for_sig (c->ctrl, sig); + + snprintf (keyid_str, sizeof keyid_str, "%08lX%08lX [uncertain] ", + (ulong)sig->keyid[0], (ulong)sig->keyid[1]); + + /* Find and print the primary user ID along with the + "Good|Expired|Bad signature" line. */ + for (un=keyblock; un; un = un->next) + { + int valid; + + if (un->pkt->pkttype==PKT_PUBLIC_KEY) + { + mainpk = 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->flags.revoked) + continue; + if (un->pkt->pkt.user_id->flags.expired) + continue; + if (!un->pkt->pkt.user_id->flags.primary) + continue; + /* We want the textual primary user ID here */ + if (un->pkt->pkt.user_id->attrib_data) + continue; + + log_assert (mainpk); + + /* Since this is just informational, don't actually ask the + user to update any trust information. (Note: we register + the signature later.) Because print_good_bad_signature + does not print a LF we need to compute the validity + before calling that function. */ + if ((opt.verify_options & VERIFY_SHOW_UID_VALIDITY)) + valid = get_validity (c->ctrl, keyblock, mainpk, + un->pkt->pkt.user_id, NULL, 0); + else + valid = 0; /* Not used. */ + + keyid_str[17] = 0; /* cut off the "[uncertain]" part */ + + print_good_bad_signature (statno, keyid_str, un, sig, rc); + + if ((opt.verify_options & VERIFY_SHOW_UID_VALIDITY)) + log_printf (" [%s]\n",trust_value_to_string(valid)); + else + log_printf ("\n"); + + count++; + } + + log_assert (mainpk); + + /* In case we did not found a valid textual userid above + we print the first user id packet or a "[?]" instead along + with the "Good|Expired|Bad signature" line. */ + if (!count) + { + /* 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 */ + + print_good_bad_signature (statno, keyid_str, un, sig, rc); + + if (opt.trust_model != TM_ALWAYS && un) + log_printf (" %s",_("[uncertain]") ); + log_printf ("\n"); + } + + /* If we have a good signature and already printed + * the primary user ID, print all the other user IDs */ + if (count + && !rc + && !(opt.verify_options & VERIFY_SHOW_PRIMARY_UID_ONLY)) + { + char *p; + for( un=keyblock; un; un = un->next) + { + if (un->pkt->pkttype != PKT_USER_ID) + continue; + if ((un->pkt->pkt.user_id->flags.revoked + || un->pkt->pkt.user_id->flags.expired) + && !(opt.verify_options & VERIFY_SHOW_UNUSABLE_UIDS)) + continue; + /* Skip textual primary user ids which we printed above. */ + if (un->pkt->pkt.user_id->flags.primary + && !un->pkt->pkt.user_id->attrib_data ) + continue; + + /* If this user id has attribute data, print that. */ + if (un->pkt->pkt.user_id->attrib_data) + { + dump_attribs (un->pkt->pkt.user_id, mainpk); + + if (opt.verify_options&VERIFY_SHOW_PHOTOS) + show_photos (c->ctrl, + un->pkt->pkt.user_id->attribs, + un->pkt->pkt.user_id->numattribs, + mainpk ,un->pkt->pkt.user_id); + } + + 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->flags.revoked) + valid = _("revoked"); + else if (un->pkt->pkt.user_id->flags.expired) + valid = _("expired"); + else + /* Since this is just informational, don't + actually ask the user to update any trust + information. */ + valid = (trust_value_to_string + (get_validity (c->ctrl, keyblock, mainpk, + un->pkt->pkt.user_id, NULL, 0))); + log_printf (" [%s]\n",valid); + } + else + log_printf ("\n"); + } + } + + /* For good signatures print notation data. */ + 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); + } + + /* For good signatures print the VALIDSIG status line. */ + if (!rc && is_status_enabled () && pk) + { + char pkhex[MAX_FINGERPRINT_LEN*2+1]; + char mainpkhex[MAX_FINGERPRINT_LEN*2+1]; + + hexfingerprint (pk, pkhex, sizeof pkhex); + hexfingerprint (mainpk, mainpkhex, sizeof mainpkhex); + + /* TODO: Replace the reserved '0' in the field below with + bits for status flags (policy url, notation, etc.). */ + write_status_printf (STATUS_VALIDSIG, + "%s %s %lu %lu %d 0 %d %d %02X %s", + pkhex, + strtimestamp (sig->timestamp), + (ulong)sig->timestamp, + (ulong)sig->expiredate, + sig->version, sig->pubkey_algo, + sig->digest_algo, + sig->sig_class, + mainpkhex); + } + + /* Print compliance warning for Good signatures. */ + if (!rc && pk && !opt.quiet + && !gnupg_pk_is_compliant (opt.compliance, pk->pubkey_algo, 0, + pk->pkey, nbits_from_pk (pk), NULL)) + { + log_info (_("WARNING: This key is not suitable for signing" + " in %s mode\n"), + gnupg_compliance_option_string (opt.compliance)); + } + + /* For good signatures compute and print the trust information. + Note that in the Tofu trust model this may ask the user on + how to resolve a conflict. */ + if (!rc) + { + if ((opt.verify_options & VERIFY_PKA_LOOKUPS)) + pka_uri_from_sig (c, sig); /* Make sure PKA info is available. */ + rc = check_signatures_trust (c->ctrl, sig); + } + + /* Print extra information about the signature. */ + if (sig->flags.expired) + { + log_info (_("Signature expired %s\n"), asctimestamp(sig->expiredate)); + rc = GPG_ERR_GENERAL; /* Need a better error here? */ + } + else if (sig->expiredate) + log_info (_("Signature expires %s\n"), asctimestamp(sig->expiredate)); + + if (opt.verbose) + { + char pkstrbuf[PUBKEY_STRING_SIZE]; + + if (pk) + pubkey_string (pk, pkstrbuf, sizeof pkstrbuf); + else + *pkstrbuf = 0; + + log_info (_("%s signature, digest algorithm %s%s%s\n"), + sig->sig_class==0x00?_("binary"): + sig->sig_class==0x01?_("textmode"):_("unknown"), + gcry_md_algo_name (sig->digest_algo), + *pkstrbuf?_(", key algorithm "):"", pkstrbuf); + } + + /* Print final warnings. */ + if (!rc && !c->signed_data.used) + { + /* Signature is basically good but we test whether the + deprecated command + gpg --verify FILE.sig + was used instead of + gpg --verify FILE.sig FILE + to verify a detached signature. If we figure out that a + data file with a matching name exists, we print a warning. + + The problem is that the first form would also verify a + standard signature. This behavior could be used to + create a made up .sig file for a tarball by creating a + standard signature from a valid detached signature packet + (for example from a signed git tag). Then replace the + sig file on the FTP server along with a changed tarball. + Using the first form the verify command would correctly + verify the signature but don't even consider the tarball. */ + kbnode_t n; + char *dfile; + + dfile = get_matching_datafile (c->sigfilename); + if (dfile) + { + for (n = c->list; n; n = n->next) + if (n->pkt->pkttype != PKT_SIGNATURE) + break; + if (n) + { + /* Not only signature packets in the tree thus this + is not a detached signature. */ + log_info (_("WARNING: not a detached signature; " + "file '%s' was NOT verified!\n"), dfile); + } + xfree (dfile); + } + } + + /* Compute compliance with CO_DE_VS. */ + if (pk && is_status_enabled () + && gnupg_gcrypt_is_compliant (CO_DE_VS) + && gnupg_pk_is_compliant (CO_DE_VS, pk->pubkey_algo, 0, pk->pkey, + nbits_from_pk (pk), NULL) + && gnupg_digest_is_compliant (CO_DE_VS, sig->digest_algo)) + write_status_strings (STATUS_VERIFICATION_COMPLIANCE_MODE, + gnupg_status_compliance_flag (CO_DE_VS), + NULL); + else if (opt.flags.require_compliance + && opt.compliance == CO_DE_VS) + { + compliance_failure (); + if (!rc) + rc = gpg_error (GPG_ERR_FORBIDDEN); + } + + + free_public_key (pk); + pk = NULL; + release_kbnode( keyblock ); + if (rc) + g10_errors_seen = 1; + if (opt.batch && rc) + g10_exit (1); + } + else + { + write_status_printf (STATUS_ERRSIG, "%08lX%08lX %d %d %02x %lu %d %s", + (ulong)sig->keyid[0], (ulong)sig->keyid[1], + sig->pubkey_algo, sig->digest_algo, + sig->sig_class, (ulong)sig->timestamp, + gpg_err_code (rc), + issuer_fpr? issuer_fpr:"-"); + if (gpg_err_code (rc) == GPG_ERR_NO_PUBKEY) + { + write_status_printf (STATUS_NO_PUBKEY, "%08lX%08lX", + (ulong)sig->keyid[0], (ulong)sig->keyid[1]); + } + if (gpg_err_code (rc) != GPG_ERR_NOT_PROCESSED) + log_error (_("Can't check signature: %s\n"), gpg_strerror (rc)); + } + + free_public_key (pk); + release_kbnode (included_keyblock); + xfree (issuer_fpr); + return rc; +} + + +/* + * Process the tree which starts at node + */ +static void +proc_tree (CTX c, kbnode_t node) +{ + kbnode_t n1; + int rc; + + if (opt.list_packets || opt.list_only) + return; + + /* We must skip our special plaintext marker packets here because + they may be the root packet. These packets are only used in + additional 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 (c->ctrl, node); + list_node (c, node); + } + else if (node->pkt->pkttype == PKT_SECRET_KEY) + { + merge_keys_and_selfsig (c->ctrl, node); + list_node (c, node); + } + else if (node->pkt->pkttype == PKT_ONEPASS_SIG) + { + /* Check all signatures. */ + if (!c->any.data) + { + int use_textmode = 0; + + free_md_filter_context (&c->mfx); + /* Prepare to create all requested message digests. */ + rc = gcry_md_open (&c->mfx.md, 0, 0); + if (rc) + goto hash_err; + + /* Fixme: why looking for the signature packet and not the + one-pass packet? */ + for (n1 = node; (n1 = find_next_kbnode (n1, PKT_SIGNATURE));) + gcry_md_enable (c->mfx.md, n1->pkt->pkt.signature->digest_algo); + + if (n1 && n1->pkt->pkt.onepass_sig->sig_class == 0x01) + use_textmode = 1; + + /* Ask for file and hash it. */ + if (c->sigs_only) + { + if (c->signed_data.used && c->signed_data.data_fd != -1) + rc = hash_datafile_by_fd (c->mfx.md, NULL, + c->signed_data.data_fd, + use_textmode); + else + rc = hash_datafiles (c->mfx.md, NULL, + c->signed_data.data_names, + c->sigfilename, + use_textmode); + } + else + { + rc = ask_for_detached_datafile (c->mfx.md, c->mfx.md2, + iobuf_get_real_fname (c->iobuf), + use_textmode); + } + + hash_err: + if (rc) + { + log_error ("can't hash datafile: %s\n", gpg_strerror (rc)); + return; + } + } + else if (c->signed_data.used) + { + 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->any.data) + { + log_error ("cleartext signature without data\n"); + return; + } + else if (c->signed_data.used) + { + 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 (we'd pretty much have to run a + * different hash context for each), but if they are all + * the same and it is detached signature, we make an + * exception. Note that the old code also disallowed + * multiple signatures if the digest algorithms are + * different. We softened this restriction only for + * detached signatures, to be on the safe side. */ + if (n1->pkt->pkt.signature->sig_class != class + || (c->any.data + && 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->any.data) + { + /* Detached signature */ + free_md_filter_context (&c->mfx); + rc = gcry_md_open (&c->mfx.md, sig->digest_algo, 0); + if (rc) + goto detached_hash_err; + + if (multiple_ok) + { + /* If we have and want to handle multiple signatures we + * need to enable all hash algorithms for the context. */ + for (n1 = node; (n1 = find_next_kbnode (n1, PKT_SIGNATURE)); ) + if (!openpgp_md_test_algo (n1->pkt->pkt.signature->digest_algo)) + gcry_md_enable (c->mfx.md, + map_md_openpgp_to_gcry + (n1->pkt->pkt.signature->digest_algo)); + } + + if (RFC2440 || RFC4880) + ; /* Strict RFC mode. */ + else if (sig->digest_algo == DIGEST_ALGO_SHA1 + && sig->pubkey_algo == PUBKEY_ALGO_DSA + && sig->sig_class == 0x01) + { + /* Enable a workaround for a pgp5 bug when the detached + * signature has been created in textmode. Note that we + * do not implement this for multiple signatures with + * different hash algorithms. */ + rc = gcry_md_open (&c->mfx.md2, sig->digest_algo, 0); + if (rc) + goto detached_hash_err; + } + + /* Here we used to have another hack to work around a pgp + * 2 bug: It worked by not using the textmode for detached + * signatures; this would let the first signature check + * (on md) fail but the second one (on md2), which adds an + * extra CR would then have produced the "correct" hash. + * This is very, very ugly hack but it may haved help in + * some cases (and break others). + * c->mfx.md2? 0 :(sig->sig_class == 0x01) + */ + + if (DBG_HASHING) + { + gcry_md_debug (c->mfx.md, "verify"); + if (c->mfx.md2) + gcry_md_debug (c->mfx.md2, "verify2"); + } + + if (c->sigs_only) + { + if (c->signed_data.used && c->signed_data.data_fd != -1) + rc = hash_datafile_by_fd (c->mfx.md, c->mfx.md2, + c->signed_data.data_fd, + (sig->sig_class == 0x01)); + else + rc = hash_datafiles (c->mfx.md, c->mfx.md2, + c->signed_data.data_names, + 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)); + } + + detached_hash_err: + if (rc) + { + log_error ("can't hash datafile: %s\n", gpg_strerror (rc)); + return; + } + } + else if (c->signed_data.used) + { + log_error (_("not a detached signature\n")); + return; + } + 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..f3318f1 --- /dev/null +++ b/g10/mdfilter.c @@ -0,0 +1,73 @@ +/* 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 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include +#include +#include +#include +#include + +#include "gpg.h" +#include "../common/status.h" +#include "../common/iobuf.h" +#include "../common/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 ) { + gcry_md_write(mfx->md, buf, i ); + if( mfx->md2 ) + gcry_md_write(mfx->md2, buf, i ); + } + else + rc = -1; /* eof */ + *ret_len = i; + } + else if( control == IOBUFCTRL_DESC ) + mem2str (buf, "md_filter", *ret_len); + return rc; +} + + +void +free_md_filter_context( md_filter_context_t *mfx ) +{ + gcry_md_close(mfx->md); + gcry_md_close(mfx->md2); + mfx->md = NULL; + mfx->md2 = NULL; + mfx->maxbuf_size = 0; +} diff --git a/g10/migrate.c b/g10/migrate.c new file mode 100644 index 0000000..38fb7bd --- /dev/null +++ b/g10/migrate.c @@ -0,0 +1,118 @@ +/* migrate.c - Migrate from earlier GnupG versions. + * Copyright (C) 2014 Werner Koch + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include +#include +#include +#include +#include +#include + +#include "gpg.h" +#include "options.h" +#include "keydb.h" +#include "../common/util.h" +#include "main.h" +#include "call-agent.h" + + +#ifdef HAVE_DOSISH_SYSTEM +# define V21_MIGRATION_FNAME "gpg-v21-migrated" +#else +# define V21_MIGRATION_FNAME ".gpg-v21-migrated" +#endif + + +/* Check whether a default secring.gpg from GnuPG < 2.1 exists and + import it if not yet done. */ +void +migrate_secring (ctrl_t ctrl) +{ + dotlock_t lockhd = NULL; + char *secring = NULL; + char *flagfile = NULL; + char *agent_version = NULL; + + secring = make_filename (gnupg_homedir (), "secring" EXTSEP_S "gpg", NULL); + if (gnupg_access (secring, F_OK)) + goto leave; /* Does not exist or is not readable. */ + flagfile = make_filename (gnupg_homedir (), V21_MIGRATION_FNAME, NULL); + if (!gnupg_access (flagfile, F_OK)) + goto leave; /* Does exist - fine. */ + + log_info ("starting migration from earlier GnuPG versions\n"); + + lockhd = dotlock_create (flagfile, 0); + if (!lockhd) + { + log_error ("can't allocate lock for '%s': %s\n", + flagfile, gpg_strerror (gpg_error_from_syserror ())); + goto leave; + } + if (dotlock_take (lockhd, -1)) + { + log_error ("can't lock '%s': %s\n", + flagfile, gpg_strerror (gpg_error_from_syserror ())); + dotlock_destroy (lockhd); + lockhd = NULL; + goto leave; + } + + if (!agent_get_version (ctrl, &agent_version)) + { + if (!gnupg_compare_version (agent_version, "2.1.0")) + { + log_error ("error: GnuPG agent version \"%s\" is too old. ", + agent_version); + log_info ("Please make sure that a recent gpg-agent is running.\n"); + log_info ("(restarting the user session may achieve this.)\n"); + log_info ("migration aborted\n"); + xfree (agent_version); + goto leave; + } + xfree (agent_version); + } + else + { + log_error ("error: GnuPG agent unusable. " + "Please check that a GnuPG agent can be started.\n"); + log_error ("migration aborted\n"); + goto leave; + } + + log_info ("porting secret keys from '%s' to gpg-agent\n", secring); + if (!import_old_secring (ctrl, secring)) + { + estream_t fp = es_fopen (flagfile, "w"); + if (!fp || es_fclose (fp)) + log_error ("error creating flag file '%s': %s\n", + flagfile, gpg_strerror (gpg_error_from_syserror ())); + else + log_info ("migration succeeded\n"); + } + + leave: + if (lockhd) + { + dotlock_release (lockhd); + dotlock_destroy (lockhd); + } + xfree (flagfile); + xfree (secring); +} diff --git a/g10/misc.c b/g10/misc.c new file mode 100644 index 0000000..0b19e1a --- /dev/null +++ b/g10/misc.c @@ -0,0 +1,1905 @@ +/* misc.c - miscellaneous functions + * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, + * 2008, 2009, 2010 Free Software Foundation, Inc. + * Copyright (C) 2014 Werner Koch + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include +#include +#include +#include +#include +#include +#if defined(__linux__) && defined(__alpha__) && __GLIBC__ < 2 +#include +#include +#endif +#ifdef HAVE_SETRLIMIT +#include +#include +#include +#endif +#ifdef ENABLE_SELINUX_HACKS +#include +#endif + +#ifdef HAVE_W32_SYSTEM +#include +#include +#ifdef HAVE_WINSOCK2_H +# define WIN32_LEAN_AND_MEAN 1 +# include +#endif +#include +#include +#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 +#endif /*HAVE_W32_SYSTEM*/ + +#include "gpg.h" +#ifdef HAVE_W32_SYSTEM +# include "../common/status.h" +#endif /*HAVE_W32_SYSTEM*/ +#include "../common/util.h" +#include "main.h" +#include "photoid.h" +#include "options.h" +#include "call-agent.h" +#include "../common/i18n.h" +#include "../common/zb32.h" + +/* FIXME: Libgcrypt 1.9 will support EAX. Until we name this a + * requirement we hardwire the enum used for EAX. */ +#define MY_GCRY_CIPHER_MODE_EAX 14 + + +#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*/ + + + + +/* 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 immediately if something goes wrong here. */ + if (gnupg_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; +#else /*!ENABLE_SELINUX_HACKS*/ + (void)fname; +#endif /*!ENABLE_SELINUX_HACKS*/ +} + +/* Remove a file registered as secure. */ +void +unregister_secured_file (const char *fname) +{ +#ifdef ENABLE_SELINUX_HACKS + struct stat buf; + struct secured_file_item *sf, *sfprev; + + if (gnupg_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; + } + } +#else /*!ENABLE_SELINUX_HACKS*/ + (void)fname; +#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. */ + } +#else /*!ENABLE_SELINUX_HACKS*/ + (void)fd; +#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 (gnupg_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. */ + } +#else /*!ENABLE_SELINUX_HACKS*/ + (void)fname; +#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 (gcry_mpi_t a) +{ + u16 csum; + byte *buffer; + size_t nbytes; + + if ( gcry_mpi_print (GCRYMPI_FMT_PGP, NULL, 0, &nbytes, a) ) + BUG (); + /* Fixme: For numbers not in secure memory we should use a stack + * based buffer and only allocate a larger one if mpi_print returns + * an error. */ + buffer = (gcry_is_secure(a)? + gcry_xmalloc_secure (nbytes) : gcry_xmalloc (nbytes)); + if ( gcry_mpi_print (GCRYMPI_FMT_PGP, buffer, nbytes, NULL, a) ) + BUG (); + csum = checksum (buffer, nbytes); + xfree (buffer); + return csum; +} + + +void +print_pubkey_algo_note (pubkey_algo_t algo) +{ + if(algo >= 100 && algo <= 110) + { + static int warn=0; + if(!warn) + { + warn=1; + es_fflush (es_stdout); + log_info (_("WARNING: using experimental public key algorithm %s\n"), + openpgp_pk_algo_name (algo)); + } + } + else if (algo == PUBKEY_ALGO_ELGAMAL) + { + es_fflush (es_stdout); + log_info (_("WARNING: Elgamal sign+encrypt keys are deprecated\n")); + } +} + +void +print_cipher_algo_note (cipher_algo_t algo) +{ + if(algo >= 100 && algo <= 110) + { + static int warn=0; + if(!warn) + { + warn=1; + es_fflush (es_stdout); + log_info (_("WARNING: using experimental cipher algorithm %s\n"), + openpgp_cipher_algo_name (algo)); + } + } +} + +void +print_digest_algo_note (digest_algo_t algo) +{ + if(algo >= 100 && algo <= 110) + { + static int warn=0; + const enum gcry_md_algos galgo = map_md_openpgp_to_gcry (algo); + + if(!warn) + { + warn=1; + es_fflush (es_stdout); + log_info (_("WARNING: using experimental digest algorithm %s\n"), + gcry_md_algo_name (galgo)); + } + } + else if (is_weak_digest (algo)) + { + const enum gcry_md_algos galgo = map_md_openpgp_to_gcry (algo); + es_fflush (es_stdout); + log_info (_("WARNING: digest algorithm %s is deprecated\n"), + gcry_md_algo_name (galgo)); + } +} + + +void +print_digest_rejected_note (enum gcry_md_algos algo) +{ + struct weakhash* weak; + int show = 1; + + if (opt.quiet) + return; + + for (weak = opt.weak_digests; weak; weak = weak->next) + if (weak->algo == algo) + { + if (weak->rejection_shown) + show = 0; + else + weak->rejection_shown = 1; + break; + } + + if (show) + { + es_fflush (es_stdout); + log_info + (_("Note: signatures using the %s algorithm are rejected\n"), + gcry_md_algo_name(algo)); + } +} + + +void +print_sha1_keysig_rejected_note (void) +{ + static int shown; + + if (shown || opt.quiet) + return; + + shown = 1; + es_fflush (es_stdout); + log_info (_("Note: third-party key signatures using" + " the %s algorithm are rejected\n"), + gcry_md_algo_name (GCRY_MD_SHA1)); + print_further_info ("use option \"%s\" to override", + "--allow-weak-key-signatures"); +} + + +/* Print a message + * "(reported error: %s)\n + * in verbose mode to further explain an error. If the error code has + * the value IGNORE_EC no message is printed. A message is also not + * printed if ERR is 0. */ +void +print_reported_error (gpg_error_t err, gpg_err_code_t ignore_ec) +{ + if (!opt.verbose) + return; + + if (!gpg_err_code (err)) + ; + else if (gpg_err_code (err) == ignore_ec) + ; + else if (gpg_err_source (err) == GPG_ERR_SOURCE_DEFAULT) + log_info (_("(reported error: %s)\n"), + gpg_strerror (err)); + else + log_info (_("(reported error: %s <%s>)\n"), + gpg_strerror (err), gpg_strsource (err)); + +} + + +/* Print a message + * "(further info: %s)\n + * in verbose mode to further explain an error. That message is + * intended to help debug a problem and should not be translated. + */ +void +print_further_info (const char *format, ...) +{ + va_list arg_ptr; + + if (!opt.verbose) + return; + + log_info (_("(further info: ")); + va_start (arg_ptr, format); + log_logv (GPGRT_LOG_CONT, format, arg_ptr); + va_end (arg_ptr); + log_printf (")\n"); +} + + +/* Map OpenPGP algo numbers to those used by Libgcrypt. We need to do + this for algorithms we implemented in Libgcrypt after they become + part of OpenPGP. */ +enum gcry_cipher_algos +map_cipher_openpgp_to_gcry (cipher_algo_t algo) +{ + switch (algo) + { + case CIPHER_ALGO_NONE: return GCRY_CIPHER_NONE; + +#ifdef GPG_USE_IDEA + case CIPHER_ALGO_IDEA: return GCRY_CIPHER_IDEA; +#else + case CIPHER_ALGO_IDEA: return 0; +#endif + + case CIPHER_ALGO_3DES: return GCRY_CIPHER_3DES; + +#ifdef GPG_USE_CAST5 + case CIPHER_ALGO_CAST5: return GCRY_CIPHER_CAST5; +#else + case CIPHER_ALGO_CAST5: return 0; +#endif + +#ifdef GPG_USE_BLOWFISH + case CIPHER_ALGO_BLOWFISH: return GCRY_CIPHER_BLOWFISH; +#else + case CIPHER_ALGO_BLOWFISH: return 0; +#endif + +#ifdef GPG_USE_AES128 + case CIPHER_ALGO_AES: return GCRY_CIPHER_AES; +#else + case CIPHER_ALGO_AES: return 0; +#endif + +#ifdef GPG_USE_AES192 + case CIPHER_ALGO_AES192: return GCRY_CIPHER_AES192; +#else + case CIPHER_ALGO_AES192: return 0; +#endif + +#ifdef GPG_USE_AES256 + case CIPHER_ALGO_AES256: return GCRY_CIPHER_AES256; +#else + case CIPHER_ALGO_AES256: return 0; +#endif + +#ifdef GPG_USE_TWOFISH + case CIPHER_ALGO_TWOFISH: return GCRY_CIPHER_TWOFISH; +#else + case CIPHER_ALGO_TWOFISH: return 0; +#endif + +#ifdef GPG_USE_CAMELLIA128 + case CIPHER_ALGO_CAMELLIA128: return GCRY_CIPHER_CAMELLIA128; +#else + case CIPHER_ALGO_CAMELLIA128: return 0; +#endif + +#ifdef GPG_USE_CAMELLIA192 + case CIPHER_ALGO_CAMELLIA192: return GCRY_CIPHER_CAMELLIA192; +#else + case CIPHER_ALGO_CAMELLIA192: return 0; +#endif + +#ifdef GPG_USE_CAMELLIA256 + case CIPHER_ALGO_CAMELLIA256: return GCRY_CIPHER_CAMELLIA256; +#else + case CIPHER_ALGO_CAMELLIA256: return 0; +#endif + default: return 0; + } +} + +/* The inverse function of above. */ +static cipher_algo_t +map_cipher_gcry_to_openpgp (enum gcry_cipher_algos algo) +{ + switch (algo) + { + case GCRY_CIPHER_NONE: return CIPHER_ALGO_NONE; + case GCRY_CIPHER_IDEA: return CIPHER_ALGO_IDEA; + case GCRY_CIPHER_3DES: return CIPHER_ALGO_3DES; + case GCRY_CIPHER_CAST5: return CIPHER_ALGO_CAST5; + case GCRY_CIPHER_BLOWFISH: return CIPHER_ALGO_BLOWFISH; + case GCRY_CIPHER_AES: return CIPHER_ALGO_AES; + case GCRY_CIPHER_AES192: return CIPHER_ALGO_AES192; + case GCRY_CIPHER_AES256: return CIPHER_ALGO_AES256; + case GCRY_CIPHER_TWOFISH: return CIPHER_ALGO_TWOFISH; + case GCRY_CIPHER_CAMELLIA128: return CIPHER_ALGO_CAMELLIA128; + case GCRY_CIPHER_CAMELLIA192: return CIPHER_ALGO_CAMELLIA192; + case GCRY_CIPHER_CAMELLIA256: return CIPHER_ALGO_CAMELLIA256; + default: return 0; + } +} + +/* Map Gcrypt public key algorithm numbers to those used by OpenPGP. + FIXME: This mapping is used at only two places - we should get rid + of it. */ +pubkey_algo_t +map_pk_gcry_to_openpgp (enum gcry_pk_algos algo) +{ + switch (algo) + { + case GCRY_PK_EDDSA: return PUBKEY_ALGO_EDDSA; + case GCRY_PK_ECDSA: return PUBKEY_ALGO_ECDSA; + case GCRY_PK_ECDH: return PUBKEY_ALGO_ECDH; + default: return algo < 110 ? (pubkey_algo_t)algo : 0; + } +} + + +/* Return the block length of an OpenPGP cipher algorithm. */ +int +openpgp_cipher_blocklen (cipher_algo_t algo) +{ + /* We use the numbers from OpenPGP to be sure that we get the right + block length. This is so that the packet parsing code works even + for unknown algorithms (for which we assume 8 due to tradition). + + NOTE: If you change the returned blocklen above 16, check + the callers because they may use a fixed size buffer of that + size. */ + switch (algo) + { + case CIPHER_ALGO_AES: + case CIPHER_ALGO_AES192: + case CIPHER_ALGO_AES256: + case CIPHER_ALGO_TWOFISH: + case CIPHER_ALGO_CAMELLIA128: + case CIPHER_ALGO_CAMELLIA192: + case CIPHER_ALGO_CAMELLIA256: + return 16; + + default: + return 8; + } +} + +/**************** + * Wrapper around the libgcrypt function with additional checks on + * the OpenPGP contraints for the algo ID. + */ +int +openpgp_cipher_test_algo (cipher_algo_t algo) +{ + enum gcry_cipher_algos ga; + + ga = map_cipher_openpgp_to_gcry (algo); + if (!ga) + return gpg_error (GPG_ERR_CIPHER_ALGO); + + return gcry_cipher_test_algo (ga); +} + +/* Map the OpenPGP cipher algorithm whose ID is contained in ALGORITHM to a + string representation of the algorithm name. For unknown algorithm + IDs this function returns "?". */ +const char * +openpgp_cipher_algo_name (cipher_algo_t algo) +{ + switch (algo) + { + case CIPHER_ALGO_IDEA: return "IDEA"; + case CIPHER_ALGO_3DES: return "3DES"; + case CIPHER_ALGO_CAST5: return "CAST5"; + case CIPHER_ALGO_BLOWFISH: return "BLOWFISH"; + case CIPHER_ALGO_AES: return "AES"; + case CIPHER_ALGO_AES192: return "AES192"; + case CIPHER_ALGO_AES256: return "AES256"; + case CIPHER_ALGO_TWOFISH: return "TWOFISH"; + case CIPHER_ALGO_CAMELLIA128: return "CAMELLIA128"; + case CIPHER_ALGO_CAMELLIA192: return "CAMELLIA192"; + case CIPHER_ALGO_CAMELLIA256: return "CAMELLIA256"; + case CIPHER_ALGO_NONE: + default: return "?"; + } +} + + +/* Same as openpgp_cipher_algo_name but returns a string in the form + * "ALGO.MODE" if AEAD is not 0. Note that in this version we do not + * print "ALGO.CFB" as we do in 2.3 to avoid confusing users. */ +const char * +openpgp_cipher_algo_mode_name (cipher_algo_t algo, aead_algo_t aead) +{ + + if (aead == AEAD_ALGO_NONE) + return openpgp_cipher_algo_name (algo); + + return map_static_strings ("openpgp_cipher_algo_mode_name", algo, aead, + openpgp_cipher_algo_name (algo), + ".", + openpgp_aead_algo_name (aead), + NULL); +} + + +/* Return 0 if ALGO is supported. Return an error if not. */ +gpg_error_t +openpgp_aead_test_algo (aead_algo_t algo) +{ + /* FIXME: We currently have no easy way to test whether libgcrypt + * implements a mode. The only way we can do this is to open a + * cipher context with that mode and close it immediately. That is + * a bit costly. So we look at the libgcrypt version and assume + * nothing has been patched out. */ + switch (algo) + { + case AEAD_ALGO_NONE: + break; + + case AEAD_ALGO_EAX: +#if GCRYPT_VERSION_NUMBER < 0x010900 + break; +#else + return 0; +#endif + + case AEAD_ALGO_OCB: + return 0; + } + + return gpg_error (GPG_ERR_INV_CIPHER_MODE); +} + + +/* Map the OpenPGP AEAD algorithm with ID ALGO to a string + * representation of the algorithm name. For unknown algorithm IDs + * this function returns "?". */ +const char * +openpgp_aead_algo_name (aead_algo_t algo) +{ + switch (algo) + { + case AEAD_ALGO_NONE: break; + case AEAD_ALGO_EAX: return "EAX"; + case AEAD_ALGO_OCB: return "OCB"; + } + + return "?"; +} + + +/* Return information for the AEAD algorithm ALGO. The corresponding + * Libgcrypt ciphermode is stored at R_MODE and the required number of + * octets for the nonce at R_NONCELEN. On error and error code is + * returned. Note that the taglen is always 128 bits. */ +gpg_error_t +openpgp_aead_algo_info (aead_algo_t algo, enum gcry_cipher_modes *r_mode, + unsigned int *r_noncelen) +{ + switch (algo) + { + case AEAD_ALGO_OCB: + *r_mode = GCRY_CIPHER_MODE_OCB; + *r_noncelen = 15; + break; + + case AEAD_ALGO_EAX: + *r_mode = MY_GCRY_CIPHER_MODE_EAX; + *r_noncelen = 16; + break; + + default: + log_error ("unsupported AEAD algo %d\n", algo); + return gpg_error (GPG_ERR_INV_CIPHER_MODE); + } + return 0; +} + + +/* Return 0 if ALGO is a supported OpenPGP public key algorithm. */ +int +openpgp_pk_test_algo (pubkey_algo_t algo) +{ + return openpgp_pk_test_algo2 (algo, 0); +} + + +/* Return 0 if ALGO is a supported OpenPGP public key algorithm and + allows the usage USE. */ +int +openpgp_pk_test_algo2 (pubkey_algo_t algo, unsigned int use) +{ + enum gcry_pk_algos ga = 0; + size_t use_buf = use; + + switch (algo) + { +#ifdef GPG_USE_RSA + case PUBKEY_ALGO_RSA: ga = GCRY_PK_RSA; break; + case PUBKEY_ALGO_RSA_E: ga = GCRY_PK_RSA_E; break; + case PUBKEY_ALGO_RSA_S: ga = GCRY_PK_RSA_S; break; +#else + case PUBKEY_ALGO_RSA: break; + case PUBKEY_ALGO_RSA_E: break; + case PUBKEY_ALGO_RSA_S: break; +#endif + + case PUBKEY_ALGO_ELGAMAL_E: ga = GCRY_PK_ELG; break; + case PUBKEY_ALGO_DSA: ga = GCRY_PK_DSA; break; + +#ifdef GPG_USE_ECDH + case PUBKEY_ALGO_ECDH: ga = GCRY_PK_ECC; break; +#else + case PUBKEY_ALGO_ECDH: break; +#endif + +#ifdef GPG_USE_ECDSA + case PUBKEY_ALGO_ECDSA: ga = GCRY_PK_ECC; break; +#else + case PUBKEY_ALGO_ECDSA: break; +#endif + +#ifdef GPG_USE_EDDSA + case PUBKEY_ALGO_EDDSA: ga = GCRY_PK_ECC; break; +#else + case PUBKEY_ALGO_EDDSA: break; +#endif + + case PUBKEY_ALGO_ELGAMAL: + /* Dont't allow type 20 keys unless in rfc2440 mode. */ + if (RFC2440) + ga = GCRY_PK_ELG; + break; + + default: + break; + } + if (!ga) + return gpg_error (GPG_ERR_PUBKEY_ALGO); + + /* Elgamal in OpenPGP used to support signing and Libgcrypt still + * does. However, we removed the signing capability from gpg ages + * ago. This function should reflect this so that errors are thrown + * early and not only when we try to sign using Elgamal. */ + if (ga == GCRY_PK_ELG && (use & (PUBKEY_USAGE_CERT | PUBKEY_USAGE_SIG))) + return gpg_error (GPG_ERR_WRONG_PUBKEY_ALGO); + + /* Now check whether Libgcrypt has support for the algorithm. */ + return gcry_pk_algo_info (ga, GCRYCTL_TEST_ALGO, NULL, &use_buf); +} + + +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: + case PUBKEY_ALGO_ECDH: + use = PUBKEY_USAGE_ENC; + break; + case PUBKEY_ALGO_RSA_S: + use = PUBKEY_USAGE_CERT | PUBKEY_USAGE_SIG; + break; + case PUBKEY_ALGO_ELGAMAL: + if (RFC2440) + use = PUBKEY_USAGE_ENC; + 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; + case PUBKEY_ALGO_ECDSA: + case PUBKEY_ALGO_EDDSA: + use = PUBKEY_USAGE_CERT | PUBKEY_USAGE_SIG | PUBKEY_USAGE_AUTH; + default: + break; + } + return use; +} + +/* Map the OpenPGP pubkey algorithm whose ID is contained in ALGO to a + string representation of the algorithm name. For unknown algorithm + IDs this function returns "?". */ +const char * +openpgp_pk_algo_name (pubkey_algo_t algo) +{ + switch (algo) + { + case PUBKEY_ALGO_RSA: + case PUBKEY_ALGO_RSA_E: + case PUBKEY_ALGO_RSA_S: return "RSA"; + case PUBKEY_ALGO_ELGAMAL: + case PUBKEY_ALGO_ELGAMAL_E: return "ELG"; + case PUBKEY_ALGO_DSA: return "DSA"; + case PUBKEY_ALGO_ECDH: return "ECDH"; + case PUBKEY_ALGO_ECDSA: return "ECDSA"; + case PUBKEY_ALGO_EDDSA: return "EDDSA"; + default: return "?"; + } +} + + +/* Explicit mapping of OpenPGP digest algos to Libgcrypt. */ +/* FIXME: We do not yes use it everywhere. */ +enum gcry_md_algos +map_md_openpgp_to_gcry (digest_algo_t algo) +{ + switch (algo) + { +#ifdef GPG_USE_MD5 + case DIGEST_ALGO_MD5: return GCRY_MD_MD5; +#else + case DIGEST_ALGO_MD5: return 0; +#endif + + case DIGEST_ALGO_SHA1: return GCRY_MD_SHA1; + +#ifdef GPG_USE_RMD160 + case DIGEST_ALGO_RMD160: return GCRY_MD_RMD160; +#else + case DIGEST_ALGO_RMD160: return 0; +#endif + +#ifdef GPG_USE_SHA224 + case DIGEST_ALGO_SHA224: return GCRY_MD_SHA224; +#else + case DIGEST_ALGO_SHA224: return 0; +#endif + + case DIGEST_ALGO_SHA256: return GCRY_MD_SHA256; + +#ifdef GPG_USE_SHA384 + case DIGEST_ALGO_SHA384: return GCRY_MD_SHA384; +#else + case DIGEST_ALGO_SHA384: return 0; +#endif + +#ifdef GPG_USE_SHA512 + case DIGEST_ALGO_SHA512: return GCRY_MD_SHA512; +#else + case DIGEST_ALGO_SHA512: return 0; +#endif + default: return 0; + } +} + + +/* Return 0 if ALGO is suitable and implemented OpenPGP hash + algorithm. */ +int +openpgp_md_test_algo (digest_algo_t algo) +{ + enum gcry_md_algos ga; + + ga = map_md_openpgp_to_gcry (algo); + if (!ga) + return gpg_error (GPG_ERR_DIGEST_ALGO); + + return gcry_md_test_algo (ga); +} + + +/* Map the OpenPGP digest algorithm whose ID is contained in ALGO to a + string representation of the algorithm name. For unknown algorithm + IDs this function returns "?". */ +const char * +openpgp_md_algo_name (int algo) +{ + switch (algo) + { + case DIGEST_ALGO_MD5: return "MD5"; + case DIGEST_ALGO_SHA1: return "SHA1"; + case DIGEST_ALGO_RMD160: return "RIPEMD160"; + case DIGEST_ALGO_SHA256: return "SHA256"; + case DIGEST_ALGO_SHA384: return "SHA384"; + case DIGEST_ALGO_SHA512: return "SHA512"; + case DIGEST_ALGO_SHA224: return "SHA224"; + } + return "?"; +} + + +static unsigned long +get_signature_count (PKT_public_key *pk) +{ +#ifdef ENABLE_CARD_SUPPORT + struct agent_card_info_s info; + + (void)pk; + if (!agent_scd_getattr ("SIG-COUNTER",&info)) + return info.sig_counter; + else + return 0; +#else + (void)pk; + return 0; +#endif +} + +/* 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; + + /* The parser below would return NULL for an empty string, thus we + * catch it here. Also catch NULL here. */ + if (!string || !*string) + return xstrdup (""); + + if(args->pk) + keyid_from_pk(args->pk,pk_keyid); + + if(args->pksk) + keyid_from_pk (args->pksk, 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->pksk) + keyid_from_pk (args->pksk, pk_keyid); + + while(*ch!='\0') + { + 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+8namehash) + { + char *tmp = zb32_encode (args->namehash, 8*20); + if (tmp) + { + if (idx + strlen (tmp) < maxlen) + { + strcpy (ret+idx, tmp); + idx += strlen (tmp); + } + xfree (tmp); + done = 1; + } + } + break; + + case 'c': /* signature count from card, if any. */ + if(idx+10pksk)); + idx+=strlen(&ret[idx]); + done=1; + } + break; + + case 'f': /* Fingerprint of key being signed */ + case 'p': /* Fingerprint of the primary key making the signature. */ + case 'g': /* Fingerprint of the key making the signature. */ + { + byte array[MAX_FINGERPRINT_LEN]; + size_t len; + int i; + + if ((*(ch+1))=='f' && args->pk) + fingerprint_from_pk (args->pk, array, &len); + else if ((*(ch+1))=='p' && args->pksk) + { + if(args->pksk->flags.primary) + fingerprint_from_pk (args->pksk, array, &len); + else if (args->pksk->main_keyid[0] + || args->pksk->main_keyid[1]) + { + /* Not the primary key: Find the fingerprint + of the primary key. */ + PKT_public_key *pk= + xmalloc_clear(sizeof(PKT_public_key)); + + if (!get_pubkey_fast (pk,args->pksk->main_keyid)) + fingerprint_from_pk (pk, array, &len); + else + memset (array, 0, (len=MAX_FINGERPRINT_LEN)); + free_public_key (pk); + } + else /* Oops: info about the primary key missing. */ + memset(array,0,(len=MAX_FINGERPRINT_LEN)); + } + else if((*(ch+1))=='g' && args->pksk) + fingerprint_from_pk (args->pksk, array, &len); + else + memset(array,0,(len=MAX_FINGERPRINT_LEN)); + + if(idx+(len*2)validity_info && idx+1validity_info; + ret[idx]='\0'; + done=1; + } + break; + + /* The text string types */ + case 't': + case 'T': + case 'V': + { + const char *str=NULL; + + switch(*(ch+1)) + { + case 't': /* e.g. "jpg" */ + str=image_type_to_string(args->imagetype,0); + break; + + case 'T': /* e.g. "image/jpeg" */ + str=image_type_to_string(args->imagetype,2); + break; + + case 'V': /* e.g. "full", "expired", etc. */ + str=args->validity_string; + break; + } + + if(str && idx+strlen(str)='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; +} + + + +/* Return the number of public key parameters as used by OpenPGP. */ +int +pubkey_get_npkey (pubkey_algo_t algo) +{ + switch (algo) + { + case PUBKEY_ALGO_RSA: + case PUBKEY_ALGO_RSA_E: + case PUBKEY_ALGO_RSA_S: return 2; + case PUBKEY_ALGO_ELGAMAL_E: return 3; + case PUBKEY_ALGO_DSA: return 4; + case PUBKEY_ALGO_ECDH: return 3; + case PUBKEY_ALGO_ECDSA: return 2; + case PUBKEY_ALGO_ELGAMAL: return 3; + case PUBKEY_ALGO_EDDSA: return 2; + default: return 0; + } +} + + +/* Return the number of secret key parameters as used by OpenPGP. */ +int +pubkey_get_nskey (pubkey_algo_t algo) +{ + switch (algo) + { + case PUBKEY_ALGO_RSA: + case PUBKEY_ALGO_RSA_E: + case PUBKEY_ALGO_RSA_S: return 6; + case PUBKEY_ALGO_ELGAMAL_E: return 4; + case PUBKEY_ALGO_DSA: return 5; + case PUBKEY_ALGO_ECDH: return 4; + case PUBKEY_ALGO_ECDSA: return 3; + case PUBKEY_ALGO_ELGAMAL: return 4; + case PUBKEY_ALGO_EDDSA: return 3; + default: return 0; + } +} + +/* Temporary helper. */ +int +pubkey_get_nsig (pubkey_algo_t algo) +{ + switch (algo) + { + case PUBKEY_ALGO_RSA: + case PUBKEY_ALGO_RSA_E: + case PUBKEY_ALGO_RSA_S: return 1; + case PUBKEY_ALGO_ELGAMAL_E: return 0; + case PUBKEY_ALGO_DSA: return 2; + case PUBKEY_ALGO_ECDH: return 0; + case PUBKEY_ALGO_ECDSA: return 2; + case PUBKEY_ALGO_ELGAMAL: return 2; + case PUBKEY_ALGO_EDDSA: return 2; + default: return 0; + } +} + + +/* Temporary helper. */ +int +pubkey_get_nenc (pubkey_algo_t algo) +{ + switch (algo) + { + case PUBKEY_ALGO_RSA: + case PUBKEY_ALGO_RSA_E: + case PUBKEY_ALGO_RSA_S: return 1; + case PUBKEY_ALGO_ELGAMAL_E: return 2; + case PUBKEY_ALGO_DSA: return 0; + case PUBKEY_ALGO_ECDH: return 2; + case PUBKEY_ALGO_ECDSA: return 0; + case PUBKEY_ALGO_ELGAMAL: return 2; + case PUBKEY_ALGO_EDDSA: return 0; + default: return 0; + } +} + + +/* Temporary helper. */ +unsigned int +pubkey_nbits( int algo, gcry_mpi_t *key ) +{ + int rc, nbits; + gcry_sexp_t sexp; + + if (algo == PUBKEY_ALGO_DSA + && key[0] && key[1] && key[2] && key[3]) + { + rc = gcry_sexp_build (&sexp, NULL, + "(public-key(dsa(p%m)(q%m)(g%m)(y%m)))", + key[0], key[1], key[2], key[3] ); + } + else if ((algo == PUBKEY_ALGO_ELGAMAL || algo == PUBKEY_ALGO_ELGAMAL_E) + && key[0] && key[1] && key[2]) + { + rc = gcry_sexp_build (&sexp, NULL, + "(public-key(elg(p%m)(g%m)(y%m)))", + key[0], key[1], key[2] ); + } + else if (is_RSA (algo) + && key[0] && key[1]) + { + rc = gcry_sexp_build (&sexp, NULL, + "(public-key(rsa(n%m)(e%m)))", + key[0], key[1] ); + } + else if ((algo == PUBKEY_ALGO_ECDSA || algo == PUBKEY_ALGO_ECDH + || algo == PUBKEY_ALGO_EDDSA) + && key[0] && key[1]) + { + char *curve = openpgp_oid_to_str (key[0]); + if (!curve) + rc = gpg_error_from_syserror (); + else + { + rc = gcry_sexp_build (&sexp, NULL, + "(public-key(ecc(curve%s)(q%m)))", + curve, key[1]); + xfree (curve); + } + } + else + return 0; + + if (rc) + BUG (); + + nbits = gcry_pk_get_nbits (sexp); + gcry_sexp_release (sexp); + return nbits; +} + + + +int +mpi_print (estream_t fp, gcry_mpi_t a, int mode) +{ + int n = 0; + size_t nwritten; + + if (!a) + return es_fprintf (fp, "[MPI_NULL]"); + if (!mode) + { + unsigned int n1; + n1 = gcry_mpi_get_nbits(a); + n += es_fprintf (fp, "[%u bits]", n1); + } + else if (gcry_mpi_get_flag (a, GCRYMPI_FLAG_OPAQUE)) + { + unsigned int nbits; + unsigned char *p = gcry_mpi_get_opaque (a, &nbits); + if (!p) + n += es_fprintf (fp, "[invalid opaque value]"); + else + { + if (!es_write_hexstring (fp, p, (nbits + 7)/8, 0, &nwritten)) + n += nwritten; + } + } + else + { + unsigned char *buffer; + size_t buflen; + + if (gcry_mpi_aprint (GCRYMPI_FMT_USG, &buffer, &buflen, a)) + BUG (); + if (!es_write_hexstring (fp, buffer, buflen, 0, &nwritten)) + n += nwritten; + gcry_free (buffer); + } + return n; +} + + +/* pkey[1] or skey[1] is Q for ECDSA, which is an uncompressed point, + i.e. 04 */ +unsigned int +ecdsa_qbits_from_Q (unsigned int qbits) +{ + if ((qbits%8) > 3) + { + log_error (_("ECDSA public key is expected to be in SEC encoding " + "multiple of 8 bits\n")); + return 0; + } + qbits -= qbits%8; + qbits /= 2; + return qbits; +} + + +/* Ignore signatures and certifications made over certain digest + * algorithms by default, MD5 is considered weak. This allows users + * to deprecate support for other algorithms as well. + */ +void +additional_weak_digest (const char* digestname) +{ + struct weakhash *weak = NULL; + const enum gcry_md_algos algo = string_to_digest_algo(digestname); + + if (algo == GCRY_MD_NONE) + { + log_error (_("unknown weak digest '%s'\n"), digestname); + return; + } + + /* Check to ensure it's not already present. */ + for (weak = opt.weak_digests; weak; weak = weak->next) + if (algo == weak->algo) + return; + + /* Add it to the head of the list. */ + weak = xmalloc(sizeof(*weak)); + weak->algo = algo; + weak->rejection_shown = 0; + weak->next = opt.weak_digests; + opt.weak_digests = weak; +} + + +/* Return true if ALGO is in the list of weak digests. */ +int +is_weak_digest (digest_algo_t algo) +{ + const enum gcry_md_algos galgo = map_md_openpgp_to_gcry (algo); + const struct weakhash *weak; + + for (weak = opt.weak_digests; weak; weak = weak->next) + if (weak->algo == galgo) + return 1; + return 0; +} diff --git a/g10/openfile.c b/g10/openfile.c new file mode 100644 index 0000000..6f4e889 --- /dev/null +++ b/g10/openfile.c @@ -0,0 +1,403 @@ +/* openfile.c + * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2009, + * 2010 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gpg.h" +#include "../common/util.h" +#include "../common/ttyio.h" +#include "options.h" +#include "main.h" +#include "../common/status.h" +#include "../common/i18n.h" + +#ifdef HAVE_W32_SYSTEM +#define NAME_OF_DEV_NULL "nul" +#else +#define NAME_OF_DEV_NULL "/dev/null" +#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 + + +/* 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 ( gnupg_access( fname, F_OK ) ) + return 1; /* Does not exist. */ + + if ( !compare_filenames (fname, NAME_OF_DEV_NULL) ) + return 1; /* Does not do any harm. */ + + 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 known 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 GPGEXT_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 an output filename; use the given one as default. Return + NULL if no file has been given or if it is not possible to ask the + user. NAME is the template len which might contain enbedded Nuls. + NAMELEN is its actual length. + */ +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; + + defname = name && namelen? make_printable_string (name, namelen, 0) : NULL; + + s = _("Enter new filename"); + n = strlen(s) + (defname?strlen (defname):0) + 10; + prompt = xmalloc (n); + if (defname) + snprintf (prompt, n, "%s [%s]: ", s, defname ); + else + snprintf (prompt, n, "%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 = 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" + * 3 = use ".rev" + * + * If INP_FD is not -1 the function simply creates an IOBUF for that + * file descriptor and ignore INAME and MODE. Note that INP_FD won't + * be closed if the returned IOBUF is closed. With RESTRICTEDPERM a + * file will be created with mode 700 if possible. + */ +int +open_outfile (int inp_fd, const char *iname, int mode, int restrictedperm, + iobuf_t *a) +{ + int rc = 0; + + *a = NULL; + if (inp_fd != -1) + { + char xname[64]; + + *a = iobuf_fdopen_nc (inp_fd, "wb"); + if (!*a) + { + rc = gpg_error_from_syserror (); + snprintf (xname, sizeof xname, "[fd %d]", inp_fd); + log_error (_("can't open '%s': %s\n"), xname, gpg_strerror (rc)); + } + else if (opt.verbose) + { + snprintf (xname, sizeof xname, "[fd %d]", inp_fd); + log_info (_("writing to '%s'\n"), xname); + } + } + else if (iobuf_is_pipe_filename (iname) && !opt.outfile) + { + *a = iobuf_create (NULL, 0); + if ( !*a ) + { + rc = gpg_error_from_syserror (); + log_error (_("can't open '%s': %s\n"), "[stdout]", strerror(errno) ); + } + else if ( opt.verbose ) + log_info (_("writing to stdout\n")); + } + else + { + char *buf = NULL; + const char *name; + + if (opt.dry_run) + name = NAME_OF_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 systems to have only one + dot in a filename. If we have something like this, + we simple replace the suffix except in cases where + the suffix is larger than 3 characters and not the + same as the new one. We don't map the filenames to + 8.3 because this is a duty of the file system. */ + char *dot; + const char *newsfx; + + newsfx = (mode==1 ? ".asc" : + mode==2 ? ".sig" : + mode==3 ? ".rev" : ".gpg"); + + buf = xmalloc (strlen(iname)+4+1); + strcpy (buf, iname); + dot = strchr (buf, '.' ); + if ( dot && dot > buf && dot[1] && strlen(dot) <= 4 + && CMP_FILENAME (newsfx, dot) ) + strcpy (dot, newsfx); + else if (dot && !dot[1]) /* Do not duplicate a dot. */ + strcpy (dot, newsfx+1); + else + strcat (buf, newsfx); + } + if (!buf) +#endif /* USE_ONLY_8DOT3 */ + { + buf = xstrconcat (iname, + (mode==1 ? EXTSEP_S "asc" : + mode==2 ? EXTSEP_S "sig" : + mode==3 ? EXTSEP_S "rev" : + /* */ EXTSEP_S GPGEXT_GPG), + NULL); + } + name = buf; + } + + rc = 0; + while ( !overwrite_filep (name) ) + { + char *tmp = ask_outfile_name (NULL, 0); + if ( !tmp || !*tmp ) + { + xfree (tmp); + rc = gpg_error (GPG_ERR_EEXIST); + break; + } + xfree (buf); + name = buf = tmp; + } + + if ( !rc ) + { + if (is_secured_filename (name) ) + { + *a = NULL; + gpg_err_set_errno (EPERM); + } + else + *a = iobuf_create (name, restrictedperm); + if (!*a) + { + rc = gpg_error_from_syserror (); + log_error(_("can't create '%s': %s\n"), name, strerror(errno) ); + } + else if( opt.verbose ) + log_info (_("writing to '%s'\n"), name ); + } + xfree(buf); + } + + if (*a) + iobuf_ioctl (*a, IOBUF_IOCTL_NO_CACHE, 1, NULL); + + return rc; +} + + +/* Find a matching data file for the signature file SIGFILENAME and + return it as a malloced string. If no matching data file is found, + return NULL. */ +char * +get_matching_datafile (const char *sigfilename) +{ + char *fname = NULL; + size_t len; + + if (iobuf_is_pipe_filename (sigfilename)) + return NULL; + + len = strlen (sigfilename); + if (len > 4 + && (!strcmp (sigfilename + len - 4, EXTSEP_S "sig") + || (len > 5 && !strcmp(sigfilename + len - 5, EXTSEP_S "sign")) + || !strcmp(sigfilename + len - 4, EXTSEP_S "asc"))) + { + + fname = xstrdup (sigfilename); + fname[len-(fname[len-1]=='n'?5:4)] = 0 ; + if (gnupg_access (fname, R_OK )) + { + /* Not found or other error. */ + xfree (fname); + fname = NULL; + } + } + + return fname; +} + + +/* + * Try to open a file without the extension ".sig" or ".asc" + * Return NULL if such a file is not available. + */ +iobuf_t +open_sigfile (const char *sigfilename, progress_filter_context_t *pfx) +{ + iobuf_t a = NULL; + char *buf; + + buf = get_matching_datafile (sigfilename); + if (buf) + { + a = iobuf_open (buf); + if (a && is_secured_file (iobuf_get_fd (a))) + { + iobuf_close (a); + a = NULL; + gpg_err_set_errno (EPERM); + } + if (a) + log_info (_("assuming signed data in '%s'\n"), buf); + if (a && pfx) + handle_progress (pfx, a, buf); + xfree (buf); + } + + return a; +} + + +/* Create the directory only if the supplied directory name is the + same as the default one. This way we avoid to create arbitrary + directories when a non-default home directory is used. To cope + with HOME, we do compare only the suffix if we see that the default + homedir does start with a tilde. */ +void +try_make_homedir (const char *fname) +{ + if ( opt.dry_run || opt.no_homedir_creation ) + return; + + gnupg_maybe_make_homedir (fname, opt.quiet); +} + + +/* Get and if needed create a string with the directory used to store + openpgp revocations. */ +char * +get_openpgp_revocdir (const char *home) +{ + char *fname; + struct stat statbuf; + + fname = make_filename (home, GNUPG_OPENPGP_REVOC_DIR, NULL); + if (gnupg_stat (fname, &statbuf) && errno == ENOENT) + { + if (gnupg_mkdir (fname, "-rwx")) + log_error (_("can't create directory '%s': %s\n"), + fname, strerror (errno) ); + else if (!opt.quiet) + log_info (_("directory '%s' created\n"), fname); + } + return fname; +} diff --git a/g10/options.h b/g10/options.h new file mode 100644 index 0000000..b11e91c --- /dev/null +++ b/g10/options.h @@ -0,0 +1,423 @@ +/* options.h + * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, + * 2007, 2010, 2011 Free Software Foundation, Inc. + * Copyright (C) 2015 g10 Code GmbH + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ +#ifndef G10_OPTIONS_H +#define G10_OPTIONS_H + +#include +#include "../common/types.h" +#include +#include "main.h" +#include "packet.h" +#include "tofu.h" +#include "../common/session-env.h" +#include "../common/compliance.h" + + +/* Object to hold information pertaining to a keyserver; it also + allows building a list of keyservers. For historic reasons this is + not a strlist_t. */ +struct keyserver_spec +{ + struct keyserver_spec *next; + char *uri; +}; +typedef struct keyserver_spec *keyserver_spec_t; + + +/* Global options for GPG. */ +EXTERN_UNLESS_MAIN_MODULE +struct +{ + int verbose; + int quiet; + unsigned debug; + int armor; + char *outfile; + estream_t outfp; /* Hack, sometimes used in place of outfile. */ + off_t max_output; + + /* If > 0 a hint with the expected number of input data bytes. This + * is not necessary an exact number but intended to be used for + * progress info and to decide on how to allocate buffers. */ + uint64_t input_size_hint; + + int dry_run; + int autostart; + int list_only; + int mimemode; + 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_icao_spelling; /* Print ICAO spelling with fingerprints. */ + int with_fingerprint; /* Option --with-fingerprint active. */ + int with_subkey_fingerprint; /* Option --with-subkey-fingerprint active. */ + int with_keygrip; /* Option --with-keygrip active. */ + int with_tofu_info; /* Option --with-tofu_info active. */ + int with_secret; /* Option --with-secret active. */ + int with_wkd_hash; /* Option --with-wkd-hash. */ + int with_key_origin; /* Option --with-key-origin. */ + int fingerprint; /* list fingerprints */ + int list_sigs; /* list signatures */ + int no_armor; + int list_packets; /* Option --list-packets active. */ + int def_cipher_algo; + int def_digest_algo; + int cert_digest_algo; + int compress_algo; + int compress_level; + int bz2_compress_level; + int bz2_decompress_lowmem; + strlist_t def_secret_key; + char *def_recipient; + int def_recipient_self; + strlist_t secret_keys_to_try; + + /* A list of mail addresses (addr-spec) provided by the user with + * the option --sender. */ + strlist_t sender_list; + + int def_cert_level; + int min_cert_level; + int ask_cert_level; + int emit_version; /* 0 = none, + 1 = major only, + 2 = major and minor, + 3 = full version, + 4 = full version plus OS string. */ + int marginals_needed; + int completes_needed; + int max_cert_depth; + const char *agent_program; + const char *dirmngr_program; + int disable_dirmngr; + + const char *def_new_key_algo; + + /* Options to be passed to the gpg-agent */ + session_env_t session_env; + char *lc_ctype; + char *lc_messages; + + int skip_verify; + int skip_hidden_recipients; + + /* TM_CLASSIC must be zero to accommodate trustdbsg 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, TM_TOFU, TM_TOFU_PGP + } trust_model; + enum tofu_policy tofu_default_policy; + int force_ownertrust; + enum gnupg_compliance_mode compliance; + enum + { + KF_DEFAULT, KF_NONE, KF_SHORT, KF_LONG, KF_0xSHORT, KF_0xLONG + } keyid_format; + const char *set_filename; + strlist_t comments; + int throw_keyids; + 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 not_dash_escaped; + int escape_from; + int lock_once; + keyserver_spec_t keyserver; /* The list of configured keyservers. */ + struct + { + unsigned int options; + unsigned int import_options; + unsigned int export_options; + char *http_proxy; + } 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; + struct weakhash *weak_digests; + int no_perm_warn; + char *temp_dir; + int no_encrypt_to; + int encrypt_to_default_key; + int interactive; + struct notation *sig_notations; + struct notation *cert_notations; + strlist_t sig_policy_url; + strlist_t cert_policy_url; + strlist_t sig_keyserver_url; + strlist_t cert_subpackets; + strlist_t sig_subpackets; + int allow_non_selfsigned_uid; + int allow_freeform_uid; + int no_literal; + ulong set_filesize; + int fast_list_mode; + int legacy_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; + + const char *gpg_agent_info; + int try_all_secrets; + int no_expensive_trust_checks; + int no_sig_cache; + int no_auto_check_trustdb; + int preserve_permissions; + int no_homedir_creation; + struct groupitem *grouplist; + int mangle_dos_filenames; + int enable_progress_filter; + unsigned int screen_columns; + unsigned int screen_lines; + byte *show_subpackets; + int rfc2440_text; + unsigned int min_rsa_length; /* Used for compliance checks. */ + + /* 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; + + 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; + unsigned int allow_multiple_messages:1; + unsigned int allow_weak_digest_algos:1; + unsigned int allow_weak_key_signatures:1; + unsigned int override_compliance_check:1; + unsigned int large_rsa:1; + unsigned int disable_signer_uid:1; + unsigned int include_key_block:1; + unsigned int auto_key_import:1; + /* Flag to enable experimental features from RFC4880bis. */ + unsigned int rfc4880bis:1; + /* Hack: --output is not given but OUTFILE was temporary set to "-". */ + unsigned int dummy_outfile:1; + /* Force the use of the OpenPGP card and do not allow the use of + * another card. */ + unsigned int use_only_openpgp_card:1; + /* Force signing keys even if a key signature already exists. */ + unsigned int force_sign_key:1; + /* The next flag is set internally iff IMPORT_SELF_SIGS_ONLY has + * been set by the user and is not the default value. */ + unsigned int expl_import_self_sigs_only:1; + /* Fail if an operation can't be done in the requested compliance + * mode. */ + unsigned int require_compliance:1; + } flags; + + /* Linked list of ways to find a key if the key isn't on the local + keyring. */ + struct akl + { + enum { + AKL_NODEFAULT, + AKL_LOCAL, + AKL_CERT, + AKL_PKA, + AKL_DANE, + AKL_WKD, + AKL_LDAP, + AKL_NTDS, + AKL_KEYSERVER, + AKL_SPEC + } type; + keyserver_spec_t spec; + struct akl *next; + } *auto_key_locate; + + /* The value of --key-origin. See parse_key_origin(). */ + int key_origin; + char *key_origin_url; + + int passphrase_repeat; + int pinentry_mode; + int request_origin; + + int unwrap_encryption; + int only_sign_text_ids; + + int no_symkey_cache; /* Disable the cache used for --symmetric. */ +} 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. */ + /* Hack to store the last error. We currently need it because the + proc_packet machinery is not able to reliabale return error + codes. Thus for the --server purposes we store some of the error + codes here. FIXME! */ + gpg_error_t lasterr; + + /* Kludge to silence some warnings using --secret-key-list. */ + int silence_parse_warnings; +} glo_ctrl; + +#define DBG_PACKET_VALUE 1 /* debug packet reading/writing */ +#define DBG_MPI_VALUE 2 /* debug mpi details */ +#define DBG_CRYPTO_VALUE 4 /* debug crypto 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 caching */ +#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_IPC_VALUE 1024 /* debug assuan communication */ +#define DBG_CLOCK_VALUE 4096 +#define DBG_LOOKUP_VALUE 8192 /* debug the key lookup */ +#define DBG_EXTPROG_VALUE 16384 /* debug external program calls */ + +/* Tests for the debugging flags. */ +#define DBG_PACKET (opt.debug & DBG_PACKET_VALUE) +#define DBG_MPI (opt.debug & DBG_MPI_VALUE) +#define DBG_CRYPTO (opt.debug & DBG_CRYPTO_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_IPC (opt.debug & DBG_IPC_VALUE) +#define DBG_IPC (opt.debug & DBG_IPC_VALUE) +#define DBG_CLOCK (opt.debug & DBG_CLOCK_VALUE) +#define DBG_LOOKUP (opt.debug & DBG_LOOKUP_VALUE) +#define DBG_EXTPROG (opt.debug & DBG_EXTPROG_VALUE) + +/* FIXME: We need to check why we did not put this into opt. */ +#define DBG_MEMORY memory_debug_mode +#define DBG_MEMSTAT memory_stat_debug_mode + +EXTERN_UNLESS_MAIN_MODULE int memory_debug_mode; +EXTERN_UNLESS_MAIN_MODULE int memory_stat_debug_mode; + + +/* Compatibility flags. */ +#define GNUPG (opt.compliance==CO_GNUPG || opt.compliance==CO_DE_VS) +#define RFC2440 (opt.compliance==CO_RFC2440) +#define RFC4880 (opt.compliance==CO_RFC4880) +#define PGP6 (opt.compliance==CO_PGP6) +#define PGP7 (opt.compliance==CO_PGP7) +#define PGP8 (opt.compliance==CO_PGP8) +#define PGPX (PGP6 || PGP7 || 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_SHOW (1<<3) +#define IMPORT_MERGE_ONLY (1<<4) +#define IMPORT_MINIMAL (1<<5) +#define IMPORT_CLEAN (1<<6) +#define IMPORT_NO_SECKEY (1<<7) +#define IMPORT_KEEP_OWNERTTRUST (1<<8) +#define IMPORT_EXPORT (1<<9) +#define IMPORT_RESTORE (1<<10) +#define IMPORT_REPAIR_KEYS (1<<11) +#define IMPORT_DRY_RUN (1<<12) +#define IMPORT_SELF_SIGS_ONLY (1<<14) + +#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 EXPORT_PKA_FORMAT (1<<6) +#define EXPORT_DANE_FORMAT (1<<7) +#define EXPORT_BACKUP (1<<10) + +#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 LIST_SHOW_USAGE (1<<11) +#define LIST_SHOW_ONLY_FPR_MBOX (1<<12) + +#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 VERIFY_SHOW_PRIMARY_UID_ONLY (1<<9) + +#define KEYSERVER_HTTP_PROXY (1<<0) +#define KEYSERVER_TIMEOUT (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/packet.h b/g10/packet.h new file mode 100644 index 0000000..7f7608c --- /dev/null +++ b/g10/packet.h @@ -0,0 +1,943 @@ +/* packet.h - OpenPGP packet definitions + * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, + * 2007 Free Software Foundation, Inc. + * Copyright (C) 2015 g10 Code GmbH + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#ifndef G10_PACKET_H +#define G10_PACKET_H + +#include "../common/types.h" +#include "../common/iobuf.h" +#include "../common/strlist.h" +#include "dek.h" +#include "filter.h" +#include "../common/openpgpdefs.h" +#include "../common/userids.h" +#include "../common/util.h" + +#define DEBUG_PARSE_PACKET 1 + +/* Maximum length of packets to avoid excessive memory allocation. */ +#define MAX_KEY_PACKET_LENGTH (256 * 1024) +#define MAX_UID_PACKET_LENGTH ( 2 * 1024) +#define MAX_COMMENT_PACKET_LENGTH ( 64 * 1024) +#define MAX_ATTR_PACKET_LENGTH ( 16 * 1024*1024) + +/* Constants to allocate static MPI arrays. */ +#define PUBKEY_MAX_NPKEY OPENPGP_MAX_NPKEY +#define PUBKEY_MAX_NSKEY OPENPGP_MAX_NSKEY +#define PUBKEY_MAX_NSIG OPENPGP_MAX_NSIG +#define PUBKEY_MAX_NENC OPENPGP_MAX_NENC + +/* Usage flags */ +#define PUBKEY_USAGE_SIG GCRY_PK_USAGE_SIGN /* Good for signatures. */ +#define PUBKEY_USAGE_ENC GCRY_PK_USAGE_ENCR /* Good for encryption. */ +#define PUBKEY_USAGE_CERT GCRY_PK_USAGE_CERT /* Also good to certify keys.*/ +#define PUBKEY_USAGE_AUTH GCRY_PK_USAGE_AUTH /* Good for authentication. */ +#define PUBKEY_USAGE_UNKNOWN GCRY_PK_USAGE_UNKN /* Unknown usage flag. */ +#define PUBKEY_USAGE_NONE 256 /* No usage given. */ +#if (GCRY_PK_USAGE_SIGN | GCRY_PK_USAGE_ENCR | GCRY_PK_USAGE_CERT \ + | GCRY_PK_USAGE_AUTH | GCRY_PK_USAGE_UNKN) >= 256 +# error Please choose another value for PUBKEY_USAGE_NONE +#endif + +/* Helper macros. */ +#define is_RSA(a) ((a)==PUBKEY_ALGO_RSA || (a)==PUBKEY_ALGO_RSA_E \ + || (a)==PUBKEY_ALGO_RSA_S ) +#define is_ELGAMAL(a) ((a)==PUBKEY_ALGO_ELGAMAL_E) +#define is_DSA(a) ((a)==PUBKEY_ALGO_DSA) + +/* A pointer to the packet object. */ +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_AEAD = 4 +} preftype_t; + +typedef struct { + byte type; + byte value; +} prefitem_t; + +/* A string-to-key specifier as defined in RFC 4880, Section 3.7. */ +typedef struct +{ + int mode; /* Must be an integer due to the GNU modes 1001 et al. */ + byte hash_algo; + byte salt[8]; + /* The *coded* (i.e., the serialized version) iteration count. */ + u32 count; +} STRING2KEY; + +/* A symmetric-key encrypted session key packet as defined in RFC + 4880, Section 5.3. All fields are serialized. */ +typedef struct { + /* RFC 4880: this must be 4. */ + byte version; + /* The cipher algorithm used to encrypt the session key. (This may + be different from the algorithm that is used to encrypt the SED + packet.) */ + byte cipher_algo; + /* The AEAD algorithm or 0 for CFB encryption. */ + byte aead_algo; + /* The string-to-key specifier. */ + STRING2KEY s2k; + /* The length of SESKEY in bytes or 0 if this packet does not + encrypt a session key. (In the latter case, the results of the + S2K function on the password is the session key. See RFC 4880, + Section 5.3.) */ + byte seskeylen; + /* The session key as encrypted by the S2K specifier. For AEAD this + * includes the nonce and the authentication tag. */ + byte seskey[1]; +} PKT_symkey_enc; + +/* A public-key encrypted session key packet as defined in RFC 4880, + Section 5.1. All fields are serialized. */ +typedef struct { + /* The 64-bit keyid. */ + u32 keyid[2]; + /* The packet's version. Currently, only version 3 is defined. */ + byte version; + /* The algorithm used for the public key encryption scheme. */ + byte pubkey_algo; + /* Whether to hide the key id. This value is not directly + serialized. */ + byte throw_keyid; + /* The session key. */ + gcry_mpi_t data[PUBKEY_MAX_NENC]; +} PKT_pubkey_enc; + + +/* A one-pass signature packet as defined in RFC 4880, Section + 5.4. All fields are serialized. */ +typedef struct { + u32 keyid[2]; /* The 64-bit keyid */ + /* The signature's classification (RFC 4880, Section 5.2.1). */ + byte sig_class; + byte digest_algo; /* algorithm used for digest */ + byte pubkey_algo; /* algorithm used for public key scheme */ + /* A message can be signed by multiple keys. In this case, there + are n one-pass signature packets before the message to sign and + n signatures packets after the message. It is conceivable that + someone wants to not only sign the message, but all of the + signatures. Now we need to distinguish between signing the + message and signing the message plus the surrounding + signatures. This is the point of this flag. If set, it means: + I sign all of the data starting at the next packet. */ + byte last; +} PKT_onepass_sig; + + +/* A v4 OpenPGP signature has a hashed and unhashed area containing + co-called signature subpackets (RFC 4880, Section 5.2.3). These + areas are described by this data structure. Use enum_sig_subpkt to + parse this area. */ +typedef struct { + size_t size; /* allocated */ + size_t len; /* used (serialized) */ + byte data[1]; /* the serialized subpackes (serialized) */ +} subpktarea_t; + +/* The in-memory representation of a designated revoker signature + subpacket (RFC 4880, Section 5.2.3.15). */ +struct revocation_key { + /* A bit field. 0x80 must be set. 0x40 means this information is + sensitive (and should not be uploaded to a keyserver by + default). */ + byte class; + /* The public-key algorithm ID. */ + byte algid; + /* The fingerprint of the authorized key. */ + 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; + + +/* A signature packet (RFC 4880, Section 5.2). Only a subset of these + fields are directly serialized (these are marked as such); the rest + are read from the subpackets, which are not synthesized when + serializing this data structure (i.e., when using build_packet()). + Instead, the subpackets must be created by hand. */ +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 key_block:1; /* A key block subpacket is present. */ + unsigned expired:1; + unsigned pka_tried:1; /* Set if we tried to retrieve the PKA record. */ + } flags; + /* The key that allegedly generated this signature. (Directly + serialized in v3 sigs; for v4 sigs, this must be explicitly added + as an issuer subpacket (5.2.3.5.) */ + u32 keyid[2]; + /* When the signature was made (seconds since the Epoch). (Directly + serialized in v3 sigs; for v4 sigs, this must be explicitly added + as a signature creation time subpacket (5.2.3.4).) */ + u32 timestamp; + u32 expiredate; /* Expires at this date or 0 if not at all. */ + /* The serialization format used / to use. If 0, then defaults to + version 3. (Serialized.) */ + byte version; + /* The signature type. (See RFC 4880, Section 5.2.1.) */ + byte sig_class; + /* Algorithm used for public key scheme (e.g., PUBKEY_ALGO_RSA). + (Serialized.) */ + byte pubkey_algo; + /* Algorithm used for digest (e.g., DIGEST_ALGO_SHA1). + (Serialized.) */ + byte digest_algo; + byte trust_depth; + byte trust_value; + const byte *trust_regexp; + struct revocation_key *revkey; + int numrevkeys; + int help_counter; /* Used internally bu some fucntions. */ + pka_info_t *pka_info; /* Malloced PKA data or NULL if not + available. See also flags.pka_tried. */ + char *signers_uid; /* Malloced value of the SIGNERS_UID + * subpacket or NULL. This string has + * already been sanitized. */ + subpktarea_t *hashed; /* All subpackets with hashed data (v4 only). */ + subpktarea_t *unhashed; /* Ditto for unhashed data. */ + /* First 2 bytes of the digest. (Serialized. Note: this is not + automatically filled in when serializing a signature!) */ + byte digest_start[2]; + /* The signature. (Serialized.) */ + gcry_mpi_t data[PUBKEY_MAX_NSIG]; + /* The message digest and its length (in bytes). Note the maximum + digest length is 512 bits (64 bytes). If DIGEST_LEN is 0, then + the digest's value has not been saved here. */ + byte digest[512 / 8]; + int digest_len; +} PKT_signature; + +#define ATTRIB_IMAGE 1 + +/* This is the cooked form of attributes. */ +struct user_attribute { + byte type; + const byte *data; + u32 len; +}; + + +/* A user id (RFC 4880, Section 5.11) or a user attribute packet (RFC + 4880, Section 5.12). Only a subset of these fields are directly + serialized (these are marked as such); the rest are read from the + self-signatures in merge_keys_and_selfsig()). */ +typedef struct +{ + int ref; /* reference counter */ + /* The length of NAME. */ + int len; + struct user_attribute *attribs; + int numattribs; + /* If this is not NULL, the packet is a user attribute rather than a + user id (See RFC 4880 5.12). (Serialized.) */ + byte *attrib_data; + /* The length of ATTRIB_DATA. */ + unsigned long attrib_len; + byte *namehash; + int help_key_usage; + u32 help_key_expire; + int help_full_count; + int help_marginal_count; + 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 */ + u32 keyupdate; /* From the ring trust packet. */ + char *updateurl; /* NULL or the URL of the last update origin. */ + byte keyorg; /* From the ring trust packet. */ + byte selfsigversion; + struct + { + unsigned int mdc:1; + unsigned int aead:1; + unsigned int ks_modify:1; + unsigned int compacted:1; + unsigned int primary:2; /* 2 if set via the primary flag, 1 if calculated */ + unsigned int revoked:1; + unsigned int expired:1; + } flags; + + char *mbox; /* NULL or the result of mailbox_from_userid. */ + + /* The text contained in the user id packet, which is normally the + * name and email address of the key holder (See RFC 4880 5.11). + * (Serialized.). For convenience an extra Nul is always appended. */ + 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; +}; + + +/* Information pertaining to secret keys. */ +struct seckey_info +{ + int is_protected:1; /* 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() */ + int sha1chk:1; /* SHA1 is used instead of a 16 bit checksum */ + u16 csum; /* Checksum for old protection modes. */ + byte algo; /* Cipher used to protect the secret information. */ + STRING2KEY s2k; /* S2K parameter. */ + byte ivlen; /* Used length of the IV. */ + byte iv[16]; /* Initialization vector for CFB mode. */ +}; + + +/**************** + * The in-memory representation of a public key (RFC 4880, Section + * 5.5). Note: this structure contains significantly more information + * than is contained in an OpenPGP public key packet. This + * information is derived from the self-signed signatures (by + * merge_keys_and_selfsig()) and is ignored when serializing the + * packet. The fields that are actually written out when serializing + * this packet are marked as accordingly. + * + * We assume that secret keys have the same number of parameters as + * the public key and that the public parameters are the first items + * in the PKEY array. Thus NPKEY is always less than NSKEY and it is + * possible to compare the secret and public keys by comparing the + * first NPKEY elements of the PKEY array. Note that since GnuPG 2.1 + * we don't use secret keys anymore directly because they are managed + * by gpg-agent. However for parsing OpenPGP key files we need a way + * to temporary store those secret keys. We do this by putting them + * into the public key structure and extending the PKEY field to NSKEY + * elements; the extra secret key information are stored in the + * SECKEY_INFO field. + */ +typedef struct +{ + /* When the key was created. (Serialized.) */ + u32 timestamp; + 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; + /* An OpenPGP packet consists of a header and a body. This is the + size of the header. If this is 0, an appropriate size is + automatically chosen based on the size of the body. + (Serialized.) */ + byte hdrbytes; + /* The serialization format. If 0, the default version (4) is used + when serializing. (Serialized.) */ + byte version; + byte selfsigversion; /* highest version of all of the self-sigs */ + /* The public key algorithm. (Serialized.) */ + byte pubkey_algo; + byte pubkey_usage; /* for now only used to pass it to getkey() */ + byte req_usage; /* hack to pass a request to getkey() */ + u32 has_expired; /* set to the expiration date if expired */ + /* keyid of the primary key. Never access this value directly. + Instead, use pk_main_keyid(). */ + u32 main_keyid[2]; + /* keyid of this key. Never access this value directly! Instead, + use pk_keyid(). */ + u32 keyid[2]; + prefitem_t *prefs; /* list of preferences (may be NULL) */ + struct + { + unsigned int mdc:1; /* MDC feature set. */ + unsigned int aead:1; /* AEAD feature set. */ + unsigned int disabled_valid:1;/* The next flag is valid. */ + unsigned int disabled:1; /* The key has been disabled. */ + unsigned int primary:1; /* This is a primary key. */ + unsigned int revoked:2; /* Key has been revoked. + 1 = revoked by the owner + 2 = revoked by designated revoker. */ + unsigned int maybe_revoked:1; /* A designated revocation is + present, but without the key to + check it. */ + unsigned int valid:1; /* Key (especially subkey) is valid. */ + unsigned int dont_cache:1; /* Do not cache this key. */ + unsigned int backsig:2; /* 0=none, 1=bad, 2=good. */ + unsigned int serialno_valid:1;/* SERIALNO below is valid. */ + unsigned int exact:1; /* Found via exact (!) search. */ + } flags; + 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; + byte keyorg; /* From the ring trust packet. */ + u32 keyupdate; /* From the ring trust packet. */ + char *updateurl; /* NULL or the URL of the last update origin. */ + const byte *trust_regexp; + char *serialno; /* Malloced hex string or NULL if it is + likely not on a card. See also + flags.serialno_valid. */ + /* If not NULL this malloced structure describes a secret key. + (Serialized.) */ + struct seckey_info *seckey_info; + /* The public key. Contains pubkey_get_npkey (pubkey_algo) + + pubkey_get_nskey (pubkey_algo) MPIs. (If pubkey_get_npkey + returns 0, then the algorithm is not understood and the PKEY + contains a single opaque MPI.) (Serialized.) */ + gcry_mpi_t pkey[PUBKEY_MAX_NSKEY]; /* Right, NSKEY elements. */ +} 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)->flags.disabled_valid)? \ + ((a)->flags.disabled):(cache_disabled_value(ctrl,(a)))) + + +typedef struct { + int len; /* length of data */ + char data[1]; +} PKT_comment; + +/* A compression packet (RFC 4880, Section 5.6). */ +typedef struct { + /* Not used. */ + u32 len; + /* Whether the serialized version of the packet used / should use + the new format. */ + byte new_ctb; + /* The compression algorithm. */ + byte algorithm; + /* An iobuf holding the data to be decompressed. (This is not used + for compression!) */ + iobuf_t buf; +} PKT_compressed; + +/* A symmetrically encrypted data packet (RFC 4880, Section 5.7) or a + symmetrically encrypted integrity protected data packet (Section + 5.13) */ +typedef struct { + /* Remaining length of encrypted data. */ + u32 len; + /* When encrypting in CFB mode, the first block size bytes of data + * are random data and the following 2 bytes are copies of the last + * two bytes of the random data (RFC 4880, Section 5.7). This + * provides a simple check that the key is correct. EXTRALEN is the + * size of this extra data or, in AEAD mode, the length of the + * headers and the tags. This is used by build_packet when writing + * out the packet's header. */ + int extralen; + /* Whether the serialized version of the packet used / should use + the new format. */ + byte new_ctb; + /* Whether the packet has an indeterminate length (old format) or + was encoded using partial body length headers (new format). + Note: this is ignored when encrypting. */ + byte is_partial; + /* If 0, MDC is disabled. Otherwise, the MDC method that was used + (currently, only DIGEST_ALGO_SHA1 is supported). */ + byte mdc_method; + /* If 0, AEAD is not used. Otherwise, the used AEAD algorithm. + * MDC_METHOD (above) shall be zero if AEAD is used. */ + byte aead_algo; + /* The cipher algo for/from the AEAD packet. 0 for other encryption + * packets. */ + byte cipher_algo; + /* The chunk byte from the AEAD packet. */ + byte chunkbyte; + + /* An iobuf holding the data to be decrypted. (This is not used for + encryption!) */ + iobuf_t buf; +} PKT_encrypted; + +typedef struct { + byte hash[20]; +} PKT_mdc; + + +/* Subtypes for the ring trust packet. */ +#define RING_TRUST_SIG 0 /* The classical signature cache. */ +#define RING_TRUST_KEY 1 /* A KEYORG on a primary key. */ +#define RING_TRUST_UID 2 /* A KEYORG on a user id. */ + +/* The local only ring trust packet which OpenPGP declares as + * implementation defined. GnuPG uses this to cache signature + * verification status and since 2.1.18 also to convey information + * about the origin of a key. Note that this packet is not part + * struct packet_struct because we use it only local in the packet + * parser and builder. */ +typedef struct { + unsigned int trustval; + unsigned int sigcache; + unsigned char subtype; /* The subtype of this ring trust packet. */ + unsigned char keyorg; /* The origin of the key (KEYORG_*). */ + u32 keyupdate; /* The wall time the key was last updated. */ + char *url; /* NULL or the URL of the source. */ +} PKT_ring_trust; + + +/* A plaintext packet (see RFC 4880, 5.9). */ +typedef struct { + /* The length of data in BUF or 0 if unknown. */ + u32 len; + /* A buffer containing the data stored in the packet's body. */ + iobuf_t buf; + byte new_ctb; + byte is_partial; /* partial length encoded */ + /* The data's formatting. This is either 'b', 't', 'u', 'l' or '1' + (however, the last two are deprecated). */ + int mode; + u32 timestamp; + /* The name of the file. This can be at most 255 characters long, + since namelen is just a byte in the serialized format. */ + 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_public_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_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) + + +/* A notation. See RFC 4880, Section 5.2.3.16. */ +struct notation +{ + /* The notation's name. */ + char *name; + /* If the notation is human readable, then the value is stored here + as a NUL-terminated string. If it is not human readable a human + readable approximation of the binary value _may_ be stored + here. */ + char *value; + /* Sometimes we want to %-expand the value. In these cases, we save + that transformed value here. */ + char *altvalue; + /* If the notation is not human readable, then the value is stored + here. */ + unsigned char *bdat; + /* The amount of data stored in BDAT. + + Note: if this is 0 and BDAT is NULL, this does not necessarily + mean that the value is human readable. It could be that we have + a 0-length value. To determine whether the notation is human + readable, always check if VALUE is not NULL. This works, because + if a human-readable value has a length of 0, we will still + allocate space for the NUL byte. */ + size_t blen; + struct + { + /* The notation is critical. */ + unsigned int critical:1; + /* The notation is human readable. */ + unsigned int human:1; + /* The notation should be deleted. */ + unsigned int ignore:1; + } flags; + + /* A field to facilitate creating a list of notations. */ + struct notation *next; +}; +typedef struct notation *notation_t; + +/*-- mainproc.c --*/ +void reset_literals_seen(void); +int proc_packets (ctrl_t ctrl, void *ctx, iobuf_t a ); +int proc_signature_packets (ctrl_t ctrl, void *ctx, iobuf_t a, + strlist_t signedfiles, const char *sigfile ); +int proc_signature_packets_by_fd (ctrl_t ctrl, + void *anchor, IOBUF a, int signed_data_fd ); +int proc_encryption_packets (ctrl_t ctrl, void *ctx, iobuf_t a); +int list_packets( iobuf_t a ); + +const byte *issuer_fpr_raw (PKT_signature *sig, size_t *r_len); +char *issuer_fpr_string (PKT_signature *sig); + +/*-- parse-packet.c --*/ + + +void register_known_notation (const char *string); + +/* Sets the packet list mode to MODE (i.e., whether we are dumping a + packet or not). Returns the current mode. This allows for + temporarily suspending dumping by doing the following: + + int saved_mode = set_packet_list_mode (0); + ... + set_packet_list_mode (saved_mode); +*/ +int set_packet_list_mode( int mode ); + + +/* A context used with parse_packet. */ +struct parse_packet_ctx_s +{ + iobuf_t inp; /* The input stream with the packets. */ + struct packet_struct last_pkt; /* The last parsed packet. */ + int free_last_pkt; /* Indicates that LAST_PKT must be freed. */ + int skip_meta; /* Skip ring trust packets. */ + unsigned int n_parsed_packets; /* Number of parsed packets. */ +}; +typedef struct parse_packet_ctx_s *parse_packet_ctx_t; + +#define init_parse_packet(a,i) do { \ + (a)->inp = (i); \ + (a)->last_pkt.pkttype = 0; \ + (a)->last_pkt.pkt.generic= NULL;\ + (a)->free_last_pkt = 0; \ + (a)->skip_meta = 0; \ + (a)->n_parsed_packets = 0; \ + } while (0) + +#define deinit_parse_packet(a) do { \ + if ((a)->free_last_pkt) \ + free_packet (NULL, (a)); \ + } while (0) + + +#if DEBUG_PARSE_PACKET +/* There are debug functions and should not be used directly. */ +int dbg_search_packet (parse_packet_ctx_t ctx, PACKET *pkt, + off_t *retpos, int with_uid, + const char* file, int lineno ); +int dbg_parse_packet (parse_packet_ctx_t ctx, PACKET *ret_pkt, + const char *file, int lineno); +int dbg_copy_all_packets( iobuf_t inp, iobuf_t out, + const char* file, int lineno ); +int dbg_copy_some_packets( iobuf_t inp, iobuf_t out, off_t stopoff, + const char* file, int lineno ); +int dbg_skip_some_packets( iobuf_t 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 +/* Return the next valid OpenPGP packet in *PKT. (This function will + * skip any packets whose type is 0.) CTX must have been setup prior to + * calling this function. + * + * Returns 0 on success, -1 if EOF is reached, and an error code + * otherwise. In the case of an error, the packet in *PKT may be + * partially constructed. As such, even if there is an error, it is + * necessary to free *PKT to avoid a resource leak. To detect what + * has been allocated, clear *PKT before calling this function. */ +int parse_packet (parse_packet_ctx_t ctx, PACKET *pkt); + +/* Return the first OpenPGP packet in *PKT that contains a key (either + * a public subkey, a public key, a secret subkey or a secret key) or, + * if WITH_UID is set, a user id. + * + * Saves the position in the pipeline of the start of the returned + * packet (according to iobuf_tell) in RETPOS, if it is not NULL. + * + * The return semantics are the same as parse_packet. */ +int search_packet (parse_packet_ctx_t ctx, PACKET *pkt, + off_t *retpos, int with_uid); + +/* Copy all packets (except invalid packets, i.e., those with a type + * of 0) from INP to OUT until either an error occurs or EOF is + * reached. + * + * Returns -1 when end of file is reached or an error code, if an + * error occurred. (Note: this function never returns 0, because it + * effectively keeps going until it gets an EOF.) */ +int copy_all_packets (iobuf_t inp, iobuf_t out ); + +/* Like copy_all_packets, but stops at the first packet that starts at + * or after STOPOFF (as indicated by iobuf_tell). + * + * Example: if STOPOFF is 100, the first packet in INP goes from + * 0 to 110 and the next packet starts at offset 111, then the packet + * starting at offset 0 will be completely processed (even though it + * extends beyond STOPOFF) and the packet starting at offset 111 will + * not be processed at all. */ +int copy_some_packets (iobuf_t inp, iobuf_t out, off_t stopoff); + +/* Skips the next N packets from INP. + * + * If parsing a packet returns an error code, then the function stops + * immediately and returns the error code. Note: in the case of an + * error, this function does not indicate how many packets were + * successfully processed. */ +int skip_some_packets (iobuf_t inp, unsigned int n); +#endif + +/* Parse a signature packet and store it in *SIG. + + The signature packet is read from INP. The OpenPGP header (the tag + and the packet's length) have already been read; the next byte read + from INP should be the first byte of the packet's contents. The + packet's type (as extract from the tag) must be passed as PKTTYPE + and the packet's length must be passed as PKTLEN. This is used as + the upper bound on the amount of data read from INP. If the packet + is shorter than PKTLEN, the data at the end will be silently + skipped. If an error occurs, an error code will be returned. -1 + means the EOF was encountered. 0 means parsing was successful. */ +int parse_signature( iobuf_t inp, int pkttype, unsigned long pktlen, + PKT_signature *sig ); + +/* Given a subpacket area (typically either PKT_signature.hashed or + PKT_signature.unhashed), either: + + - test whether there are any subpackets with the critical bit set + that we don't understand, + + - list the subpackets, or, + + - find a subpacket with a specific type. + + REQTYPE indicates the type of operation. + + If REQTYPE is SIGSUBPKT_TEST_CRITICAL, then this function checks + whether there are any subpackets that have the critical bit and + which GnuPG cannot handle. If GnuPG understands all subpackets + whose critical bit is set, then this function returns simply + returns SUBPKTS. If there is a subpacket whose critical bit is set + and which GnuPG does not understand, then this function returns + NULL and, if START is not NULL, sets *START to the 1-based index of + the subpacket that violates the constraint. + + If REQTYPE is SIGSUBPKT_LIST_HASHED or SIGSUBPKT_LIST_UNHASHED, the + packets are dumped. Note: if REQTYPE is SIGSUBPKT_LIST_HASHED, + this function does not check whether the hash is correct; this is + merely an indication of the section that the subpackets came from. + + If REQTYPE is anything else, then this function interprets the + values as a subpacket type and looks for the first subpacket with + that type. If such a packet is found, *CRITICAL (if not NULL) is + set if the critical bit was set, *RET_N is set to the offset of the + subpacket's content within the SUBPKTS buffer, *START is set to the + 1-based index of the subpacket within the buffer, and returns + &SUBPKTS[*RET_N]. + + *START is the number of initial subpackets to not consider. Thus, + if *START is 2, then the first 2 subpackets are ignored. */ +const byte *enum_sig_subpkt ( const subpktarea_t *subpkts, + sigsubpkttype_t reqtype, + size_t *ret_n, int *start, int *critical ); + +/* Shorthand for: + + enum_sig_subpkt (buffer, reqtype, ret_n, NULL, NULL); */ +const byte *parse_sig_subpkt ( const subpktarea_t *buffer, + sigsubpkttype_t reqtype, + size_t *ret_n ); + +/* This calls parse_sig_subpkt first on the hashed signature area in + SIG and then, if that returns NULL, calls parse_sig_subpkt on the + unhashed subpacket area in SIG. */ +const byte *parse_sig_subpkt2 ( PKT_signature *sig, + sigsubpkttype_t reqtype); + +/* Returns whether the N byte large buffer BUFFER is sufficient to + hold a subpacket of type TYPE. Note: the buffer refers to the + contents of the subpacket (not the header) and it must already be + initialized: for some subpackets, it checks some internal + constraints. + + Returns 0 if the size is acceptable. Returns -2 if the buffer is + definitely too short. To check for an error, check whether the + return value is less than 0. */ +int parse_one_sig_subpkt( const byte *buffer, size_t n, int type ); + +/* Looks for revocation key subpackets (see RFC 4880 5.2.3.15) in the + hashed area of the signature packet. Any that are found are added + to SIG->REVKEY and SIG->NUMREVKEYS is updated appropriately. */ +void parse_revkeys(PKT_signature *sig); + +/* Extract the attributes from the buffer at UID->ATTRIB_DATA and + update UID->ATTRIBS and UID->NUMATTRIBS accordingly. */ +int parse_attribute_subpkts(PKT_user_id *uid); + +/* Set the UID->NAME field according to the attributes. MAX_NAMELEN + must be at least 71. */ +void make_attribute_uidname(PKT_user_id *uid, size_t max_namelen); + +/* Allocate and initialize a new GPG control packet. DATA is the data + to save in the packet. */ +PACKET *create_gpg_control ( ctrlpkttype_t type, + const byte *data, + size_t datalen ); + +/*-- build-packet.c --*/ +int build_packet (iobuf_t out, PACKET *pkt); +gpg_error_t build_packet_and_meta (iobuf_t out, PACKET *pkt); +gpg_error_t gpg_mpi_write (iobuf_t out, gcry_mpi_t a); +gpg_error_t gpg_mpi_write_nohdr (iobuf_t out, gcry_mpi_t a); +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, PKT_public_key *pksk); +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 *blob_to_notation(const char *name, + const char *data, size_t len); +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 ); +void release_public_key_parts( PKT_public_key *pk ); +void free_public_key( PKT_public_key *key ); +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, parse_packet_ctx_t parsectx); +prefitem_t *copy_prefs (const prefitem_t *prefs); +PKT_public_key *copy_public_key( PKT_public_key *d, PKT_public_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_signatures( PKT_signature *a, PKT_signature *b ); +int cmp_user_ids( PKT_user_id *a, PKT_user_id *b ); + + +/*-- sig-check.c --*/ +/* Check a signature. This is shorthand for check_signature2 with + the unnamed arguments passed as NULL. */ +int check_signature (ctrl_t ctrl, PKT_signature *sig, gcry_md_hd_t digest); + +/* Check a signature. Looks up the public key from the key db. (If + * R_PK is not NULL, it is stored at RET_PK.) DIGEST contains a + * valid hash context that already includes the signed data. This + * function adds the relevant meta-data to the hash before finalizing + * it and verifying the signature. FOCRED_PK is usually NULL. */ +gpg_error_t check_signature2 (ctrl_t ctrl, + PKT_signature *sig, gcry_md_hd_t digest, + PKT_public_key *forced_pk, + u32 *r_expiredate, int *r_expired, int *r_revoked, + PKT_public_key **r_pk); + + +/*-- pubkey-enc.c --*/ +gpg_error_t get_session_key (ctrl_t ctrl, PKT_pubkey_enc *k, DEK *dek); +gpg_error_t get_override_session_key (DEK *dek, const char *string); + +/*-- compress.c --*/ +int handle_compressed (ctrl_t ctrl, void *ctx, PKT_compressed *cd, + int (*callback)(iobuf_t, void *), void *passthru ); + +/*-- decrypt-data.c --*/ +int decrypt_data (ctrl_t ctrl, void *ctx, PKT_encrypted *ed, DEK *dek, + int *compliance_error); + +/*-- plaintext.c --*/ +gpg_error_t get_output_file (const byte *embedded_name, int embedded_namelen, + iobuf_t data, char **fnamep, estream_t *fpp); +int handle_plaintext( PKT_plaintext *pt, md_filter_context_t *mfx, + int nooutput, int clearsig ); +int ask_for_detached_datafile( gcry_md_hd_t md, gcry_md_hd_t md2, + const char *inname, int textmode ); + +/*-- sign.c --*/ +int make_keysig_packet (ctrl_t ctrl, + PKT_signature **ret_sig, PKT_public_key *pk, + PKT_user_id *uid, PKT_public_key *subpk, + PKT_public_key *pksk, int sigclass, int digest_algo, + u32 timestamp, u32 duration, + int (*mksubpkt)(PKT_signature *, void *), + void *opaque, + const char *cache_nonce); +gpg_error_t update_keysig_packet (ctrl_t ctrl, + PKT_signature **ret_sig, + PKT_signature *orig_sig, + PKT_public_key *pk, + PKT_user_id *uid, + PKT_public_key *subpk, + PKT_public_key *pksk, + int (*mksubpkt)(PKT_signature *, void *), + void *opaque ); + +/*-- keygen.c --*/ +PKT_user_id *generate_user_id (kbnode_t keyblock, const char *uidstr); + +#endif /*G10_PACKET_H*/ diff --git a/g10/parse-packet.c b/g10/parse-packet.c new file mode 100644 index 0000000..5fea1ac --- /dev/null +++ b/g10/parse-packet.c @@ -0,0 +1,3663 @@ +/* parse-packet.c - read packets + * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, + * 2007, 2009, 2010 Free Software Foundation, Inc. + * Copyright (C) 2014 Werner Koch + * Copyright (C) 2015 g10 Code GmbH + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include +#include +#include +#include + +#include "gpg.h" +#include "../common/util.h" +#include "packet.h" +#include "../common/iobuf.h" +#include "filter.h" +#include "photoid.h" +#include "options.h" +#include "main.h" +#include "../common/i18n.h" +#include "../common/host2net.h" +#include "../common/mbox-util.h" + + +static int mpi_print_mode; +static int list_mode; +static estream_t listfp; + +/* A linked list of known notation names. Note that the FLAG is used + * to store the length of the name to speed up the check. */ +static strlist_t known_notations_list; + + +static int parse (parse_packet_ctx_t ctx, PACKET *pkt, int onlykeypkts, + off_t * retpos, int *skip, IOBUF out, int do_skip +#if 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); +static int parse_marker (IOBUF inp, int pkttype, unsigned long pktlen); +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 gpg_error_t parse_ring_trust (parse_packet_ctx_t ctx, + unsigned long pktlen); +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 gpg_error_t parse_encrypted_aead (IOBUF inp, int pkttype, + unsigned long pktlen, PACKET *packet, + 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); + +/* Read a 16-bit value in MSB order (big endian) from an iobuf. */ +static unsigned short +read_16 (IOBUF inp) +{ + unsigned short a; + a = (unsigned short)iobuf_get_noeof (inp) << 8; + a |= iobuf_get_noeof (inp); + return a; +} + + +/* Read a 32-bit value in MSB order (big endian) from an iobuf. */ +static unsigned long +read_32 (IOBUF inp) +{ + unsigned long a; + a = (unsigned long)iobuf_get_noeof (inp) << 24; + a |= iobuf_get_noeof (inp) << 16; + a |= iobuf_get_noeof (inp) << 8; + a |= iobuf_get_noeof (inp); + return a; +} + + +/* Read an external representation of an MPI and return the MPI. The + external format is a 16-bit unsigned value stored in network byte + order giving the number of bits for the following integer. The + integer is stored MSB first and is left padded with zero bits to + align on a byte boundary. + + The caller must set *RET_NREAD to the maximum number of bytes to + read from the pipeline INP. This function sets *RET_NREAD to be + the number of bytes actually read from the pipeline. + + If SECURE is true, the integer is stored in secure memory + (allocated using gcry_xmalloc_secure). */ +static gcry_mpi_t +mpi_read (iobuf_t inp, unsigned int *ret_nread, int secure) +{ + int c, c1, c2, i; + unsigned int nmax = *ret_nread; + unsigned int nbits, nbytes; + size_t nread = 0; + gcry_mpi_t a = NULL; + byte *buf = NULL; + byte *p; + + if (!nmax) + goto overflow; + + if ((c = c1 = iobuf_get (inp)) == -1) + goto leave; + if (++nread == nmax) + goto overflow; + nbits = c << 8; + if ((c = c2 = iobuf_get (inp)) == -1) + goto leave; + ++nread; + nbits |= c; + if (nbits > MAX_EXTERN_MPI_BITS) + { + log_error ("mpi too large (%u bits)\n", nbits); + goto leave; + } + + nbytes = (nbits + 7) / 8; + buf = secure ? gcry_xmalloc_secure (nbytes + 2) : gcry_xmalloc (nbytes + 2); + p = buf; + p[0] = c1; + p[1] = c2; + for (i = 0; i < nbytes; i++) + { + if (nread == nmax) + goto overflow; + + c = iobuf_get (inp); + if (c == -1) + goto leave; + + p[i + 2] = c; + nread ++; + } + + if (gcry_mpi_scan (&a, GCRYMPI_FMT_PGP, buf, nread, &nread)) + a = NULL; + + *ret_nread = nread; + gcry_free(buf); + return a; + + overflow: + log_error ("mpi larger than indicated length (%u bits)\n", 8*nmax); + leave: + *ret_nread = nread; + gcry_free(buf); + return a; +} + + +/* Read an external representation (which is possibly an SOS) and + return the MPI. The external format is a 16-bit unsigned value + stored in network byte order giving information for the following + octets. + + The caller must set *RET_NREAD to the maximum number of bytes to + read from the pipeline INP. This function sets *RET_NREAD to be + the number of bytes actually read from the pipeline. + + If SECURE is true, the integer is stored in secure memory + (allocated using gcry_xmalloc_secure). */ +static gcry_mpi_t +mpi_read_detect_0_removal (iobuf_t inp, unsigned int *ret_nread, int secure, + u16 *r_csum_tweak) +{ + int c, c1, c2, i; + unsigned int nmax = *ret_nread; + unsigned int nbits, nbits1, nbytes; + size_t nread = 0; + gcry_mpi_t a = NULL; + byte *buf = NULL; + byte *p; + + if (!nmax) + goto overflow; + + if ((c = c1 = iobuf_get (inp)) == -1) + goto leave; + if (++nread == nmax) + goto overflow; + nbits = c << 8; + if ((c = c2 = iobuf_get (inp)) == -1) + goto leave; + ++nread; + nbits |= c; + if (nbits > MAX_EXTERN_MPI_BITS) + { + log_error ("mpi too large (%u bits)\n", nbits); + goto leave; + } + + nbytes = (nbits + 7) / 8; + buf = secure ? gcry_xmalloc_secure (nbytes + 2) : gcry_xmalloc (nbytes + 2); + p = buf; + p[0] = c1; + p[1] = c2; + for (i = 0; i < nbytes; i++) + { + if (nread == nmax) + goto overflow; + + c = iobuf_get (inp); + if (c == -1) + goto leave; + + p[i + 2] = c; + + nread ++; + } + + if (gcry_mpi_scan (&a, GCRYMPI_FMT_PGP, buf, nread, &nread)) + a = NULL; + + /* Possibly, it has leading zeros. */ + if (a) + { + nbits1 = gcry_mpi_get_nbits (a); + if (nbits > nbits1) + { + *r_csum_tweak -= (nbits >> 8); + *r_csum_tweak -= (nbits & 0xff); + *r_csum_tweak += (nbits1 >> 8); + *r_csum_tweak += (nbits1 & 0xff); + } + } + + *ret_nread = nread; + gcry_free(buf); + return a; + + overflow: + log_error ("mpi larger than indicated length (%u bits)\n", 8*nmax); + leave: + *ret_nread = nread; + gcry_free(buf); + return a; +} + + +/* Register STRING as a known critical notation name. */ +void +register_known_notation (const char *string) +{ + strlist_t sl; + + if (!known_notations_list) + { + sl = add_to_strlist (&known_notations_list, + "preferred-email-encoding@pgp.com"); + sl->flags = 32; + sl = add_to_strlist (&known_notations_list, "pka-address@gnupg.org"); + sl->flags = 21; + } + if (!string) + return; /* Only initialized the default known notations. */ + + /* In --set-notation we use an exclamation mark to indicate a + * critical notation. As a convenience skip this here. */ + if (*string == '!') + string++; + + if (!*string || strlist_find (known_notations_list, string)) + return; /* Empty string or already registered. */ + + sl = add_to_strlist (&known_notations_list, string); + sl->flags = strlen (string); +} + + +int +set_packet_list_mode (int mode) +{ + int old = list_mode; + list_mode = mode; + + /* We use stdout only if invoked by the --list-packets command + but switch to stderr in all other 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 there is code which switches + opt.list_mode back to 1 and we want to have all output to the + same stream. The MPI_PRINT_MODE will be enabled if the + corresponding debug flag is set or if we are in --list-packets + and --verbose is given. + + Using stderr is not actually very clean because it bypasses the + logging code but it is a special thing anyway. I am not sure + whether using log_stream() would be better. Perhaps we should + enable the list mode only with a special option. */ + if (!listfp) + { + if (opt.list_packets) + { + listfp = es_stdout; + if (opt.verbose) + mpi_print_mode = 1; + } + else + listfp = es_stderr; + + if (DBG_MPI) + mpi_print_mode = 1; + } + return old; +} + + +/* If OPT.VERBOSE is set, print a warning that the algorithm ALGO is + not suitable for signing and encryption. */ +static void +unknown_pubkey_warning (int algo) +{ + static byte unknown_pubkey_algos[256]; + + /* First check whether the algorithm is usable but not suitable for + encryption/signing. */ + if (pubkey_get_npkey (algo)) + { + if (opt.verbose && !glo_ctrl.silence_parse_warnings) + { + if (!pubkey_get_nsig (algo)) + log_info ("public key algorithm %s not suitable for %s\n", + openpgp_pk_algo_name (algo), "signing"); + if (!pubkey_get_nenc (algo)) + log_info ("public key algorithm %s not suitable for %s\n", + openpgp_pk_algo_name (algo), "encryption"); + } + } + else + { + algo &= 0xff; + if (!unknown_pubkey_algos[algo]) + { + if (opt.verbose && !glo_ctrl.silence_parse_warnings) + log_info (_("can't handle public key algorithm %d\n"), algo); + unknown_pubkey_algos[algo] = 1; + } + } +} + + +#if DEBUG_PARSE_PACKET +int +dbg_parse_packet (parse_packet_ctx_t ctx, PACKET *pkt, + const char *dbg_f, int dbg_l) +{ + int skip, rc; + + do + { + rc = parse (ctx, pkt, 0, NULL, &skip, NULL, 0, "parse", dbg_f, dbg_l); + } + while (skip && ! rc); + return rc; +} +#else /*!DEBUG_PARSE_PACKET*/ +int +parse_packet (parse_packet_ctx_t ctx, PACKET *pkt) +{ + int skip, rc; + + do + { + rc = parse (ctx, pkt, 0, NULL, &skip, NULL, 0); + } + while (skip && ! rc); + return rc; +} +#endif /*!DEBUG_PARSE_PACKET*/ + + +/* + * Like parse packet, but only return secret or public (sub)key + * packets. + */ +#if DEBUG_PARSE_PACKET +int +dbg_search_packet (parse_packet_ctx_t ctx, PACKET *pkt, + off_t * retpos, int with_uid, + const char *dbg_f, int dbg_l) +{ + int skip, rc; + + do + { + rc = parse (ctx, pkt, with_uid ? 2 : 1, retpos, &skip, NULL, 0, "search", + dbg_f, dbg_l); + } + while (skip && ! rc); + return rc; +} +#else /*!DEBUG_PARSE_PACKET*/ +int +search_packet (parse_packet_ctx_t ctx, PACKET *pkt, + off_t * retpos, int with_uid) +{ + int skip, rc; + + do + { + rc = parse (ctx, pkt, with_uid ? 2 : 1, retpos, &skip, NULL, 0); + } + while (skip && ! rc); + return rc; +} +#endif /*!DEBUG_PARSE_PACKET*/ + + +/* + * Copy all packets from INP to OUT, thereby removing unused spaces. + */ +#if DEBUG_PARSE_PACKET +int +dbg_copy_all_packets (iobuf_t inp, iobuf_t out, const char *dbg_f, int dbg_l) +{ + PACKET pkt; + struct parse_packet_ctx_s parsectx; + int skip, rc = 0; + + if (! out) + log_bug ("copy_all_packets: OUT may not be NULL.\n"); + + init_parse_packet (&parsectx, inp); + + do + { + init_packet (&pkt); + } + while (! + (rc = + parse (&parsectx, &pkt, 0, NULL, &skip, out, 0, "copy", + dbg_f, dbg_l))); + + deinit_parse_packet (&parsectx); + + return rc; +} +#else /*!DEBUG_PARSE_PACKET*/ +int +copy_all_packets (iobuf_t inp, iobuf_t out) +{ + PACKET pkt; + struct parse_packet_ctx_s parsectx; + int skip, rc = 0; + + if (! out) + log_bug ("copy_all_packets: OUT may not be NULL.\n"); + + init_parse_packet (&parsectx, inp); + + do + { + init_packet (&pkt); + } + while (!(rc = parse (&parsectx, &pkt, 0, NULL, &skip, out, 0))); + + deinit_parse_packet (&parsectx); + + return rc; +} +#endif /*!DEBUG_PARSE_PACKET*/ + + +/* + * 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) + */ +#if DEBUG_PARSE_PACKET +int +dbg_copy_some_packets (iobuf_t inp, iobuf_t out, off_t stopoff, + const char *dbg_f, int dbg_l) +{ + int rc = 0; + PACKET pkt; + int skip; + struct parse_packet_ctx_s parsectx; + + init_parse_packet (&parsectx, inp); + + do + { + if (iobuf_tell (inp) >= stopoff) + { + deinit_parse_packet (&parsectx); + return 0; + } + init_packet (&pkt); + } + while (!(rc = parse (&parsectx, &pkt, 0, NULL, &skip, out, 0, + "some", dbg_f, dbg_l))); + + deinit_parse_packet (&parsectx); + + return rc; +} +#else /*!DEBUG_PARSE_PACKET*/ +int +copy_some_packets (iobuf_t inp, iobuf_t out, off_t stopoff) +{ + int rc = 0; + PACKET pkt; + struct parse_packet_ctx_s parsectx; + int skip; + + init_parse_packet (&parsectx, inp); + + do + { + if (iobuf_tell (inp) >= stopoff) + { + deinit_parse_packet (&parsectx); + return 0; + } + init_packet (&pkt); + } + while (!(rc = parse (&parsectx, &pkt, 0, NULL, &skip, out, 0))); + + deinit_parse_packet (&parsectx); + + return rc; +} +#endif /*!DEBUG_PARSE_PACKET*/ + + +/* + * Skip over N packets + */ +#if DEBUG_PARSE_PACKET +int +dbg_skip_some_packets (iobuf_t inp, unsigned n, const char *dbg_f, int dbg_l) +{ + int rc = 0; + int skip; + PACKET pkt; + struct parse_packet_ctx_s parsectx; + + init_parse_packet (&parsectx, inp); + + for (; n && !rc; n--) + { + init_packet (&pkt); + rc = parse (&parsectx, &pkt, 0, NULL, &skip, NULL, 1, "skip", + dbg_f, dbg_l); + } + + deinit_parse_packet (&parsectx); + + return rc; +} +#else /*!DEBUG_PARSE_PACKET*/ +int +skip_some_packets (iobuf_t inp, unsigned int n) +{ + int rc = 0; + int skip; + PACKET pkt; + struct parse_packet_ctx_s parsectx; + + init_parse_packet (&parsectx, inp); + + for (; n && !rc; n--) + { + init_packet (&pkt); + rc = parse (&parsectx, &pkt, 0, NULL, &skip, NULL, 1); + } + + deinit_parse_packet (&parsectx); + + return rc; +} +#endif /*!DEBUG_PARSE_PACKET*/ + + +/* Parse a packet and save it in *PKT. + + If OUT is not NULL and the packet is valid (its type is not 0), + then the header, the initial length field and the packet's contents + are written to OUT. In this case, the packet is not saved in *PKT. + + ONLYKEYPKTS is a simple packet filter. If ONLYKEYPKTS is set to 1, + then only public subkey packets, public key packets, private subkey + packets and private key packets are parsed. The rest are skipped + (i.e., the header and the contents are read from the pipeline and + discarded). If ONLYKEYPKTS is set to 2, then in addition to the + above 4 types of packets, user id packets are also accepted. + + DO_SKIP is a more coarse grained filter. Unless ONLYKEYPKTS is set + to 2 and the packet is a user id packet, all packets are skipped. + + Finally, if a packet is invalid (it's type is 0), it is skipped. + + If a packet is skipped and SKIP is not NULL, then *SKIP is set to + 1. + + Note: ONLYKEYPKTS and DO_SKIP are only respected if OUT is NULL, + i.e., the packets are not simply being copied. + + If RETPOS is not NULL, then the position of CTX->INP (as returned by + iobuf_tell) is saved there before any data is read from CTX->INP. + */ +static int +parse (parse_packet_ctx_t ctx, PACKET *pkt, int onlykeypkts, off_t * retpos, + int *skip, IOBUF out, int do_skip +#if DEBUG_PARSE_PACKET + , const char *dbg_w, const char *dbg_f, int dbg_l +#endif + ) +{ + int rc = 0; + iobuf_t inp; + int c, ctb, pkttype, lenbytes; + unsigned long pktlen; + byte hdr[8]; + int hdrlen; + int new_ctb = 0, partial = 0; + int with_uid = (onlykeypkts == 2); + off_t pos; + + *skip = 0; + inp = ctx->inp; + + again: + log_assert (!pkt->pkt.generic); + if (retpos || list_mode) + { + pos = iobuf_tell (inp); + if (retpos) + *retpos = pos; + } + else + pos = 0; /* (silence compiler warning) */ + + /* The first byte of a packet is the so-called tag. The highest bit + must be set. */ + 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 = gpg_error (GPG_ERR_INV_PACKET); + goto leave; + } + + /* Immediately following the header is the length. There are two + formats: the old format and the new format. If bit 6 (where the + least significant bit is bit 0) is set in the tag, then we are + dealing with a new format packet. Otherwise, it is an old format + packet. */ + pktlen = 0; + new_ctb = !!(ctb & 0x40); + if (new_ctb) + { + /* Get the packet's type. This is encoded in the 6 least + significant bits of the tag. */ + pkttype = ctb & 0x3f; + + /* Extract the packet's length. New format packets have 4 ways + to encode the packet length. The value of the first byte + determines the encoding and partially determines the length. + See section 4.2.2 of RFC 4880 for details. */ + if ((c = iobuf_get (inp)) == -1) + { + log_error ("%s: 1st length byte missing\n", iobuf_where (inp)); + rc = gpg_error (GPG_ERR_INV_PACKET); + goto leave; + } + + + 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 = gpg_error (GPG_ERR_INV_PACKET); + goto leave; + } + hdr[hdrlen++] = c; + pktlen += c + 192; + } + else if (c == 255) + { + int i; + char value[4]; + + for (i = 0; i < 4; i ++) + { + if ((c = iobuf_get (inp)) == -1) + { + log_error ("%s: 4 byte length invalid\n", iobuf_where (inp)); + rc = gpg_error (GPG_ERR_INV_PACKET); + goto leave; + } + value[i] = hdr[hdrlen++] = c; + } + + pktlen = buf32_to_ulong (value); + } + else /* Partial body length. */ + { + switch (pkttype) + { + case PKT_PLAINTEXT: + case PKT_ENCRYPTED: + case PKT_ENCRYPTED_MDC: + case PKT_ENCRYPTED_AEAD: + case PKT_COMPRESSED: + iobuf_set_partial_body_length_mode (inp, c & 0xff); + pktlen = 0; /* To indicate partial length. */ + partial = 1; + break; + + default: + log_error ("%s: partial length invalid for" + " packet type %d\n", iobuf_where (inp), pkttype); + rc = gpg_error (GPG_ERR_INV_PACKET); + goto leave; + } + } + + } + else + /* This is an old format packet. */ + { + /* Extract the packet's type. This is encoded in bits 2-5. */ + pkttype = (ctb >> 2) & 0xf; + + /* The type of length encoding is encoded in bits 0-1 of the + tag. */ + 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 = gpg_error (GPG_ERR_INV_PACKET); + goto leave; + } + } + else + { + for (; lenbytes; lenbytes--) + { + pktlen <<= 8; + c = iobuf_get (inp); + if (c == -1) + { + log_error ("%s: length invalid\n", iobuf_where (inp)); + rc = gpg_error (GPG_ERR_INV_PACKET); + goto leave; + } + pktlen |= hdr[hdrlen++] = c; + } + } + } + + /* Sometimes the decompressing layer enters an error state in which + it simply outputs 0xff for every byte read. If we have a stream + of 0xff bytes, then it will be detected as a new format packet + with type 63 and a 4-byte encoded length that is 4G-1. Since + packets with type 63 are private and we use them as a control + packet, which won't be 4 GB, we reject such packets as + invalid. */ + if (pkttype == 63 && pktlen == 0xFFFFFFFF) + { + /* With 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) + { + /* This type of copying won't work if the packet uses a partial + body length. (In other words, this only works if HDR is + actually the length.) Currently, no callers require this + functionality so we just log this as an error. */ + if (partial) + { + log_error ("parse: Can't copy partial packet. Aborting.\n"); + rc = gpg_error (GPG_ERR_INV_PACKET); + goto leave; + } + + rc = iobuf_write (out, hdr, hdrlen); + if (!rc) + rc = copy_packet (inp, out, pkttype, pktlen, partial); + goto leave; + } + + if (with_uid && pkttype == PKT_USER_ID) + /* If ONLYKEYPKTS is set to 2, then we never skip user id packets, + even if DO_SKIP is set. */ + ; + else if (do_skip + /* type==0 is not allowed. This is an invalid packet. */ + || !pkttype + /* When ONLYKEYPKTS is set, we don't skip keys. */ + || (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) + { +#if 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 + } + + if (list_mode) + es_fprintf (listfp, "# off=%lu ctb=%02x tag=%d hlen=%d plen=%lu%s%s\n", + (unsigned long)pos, ctb, pkttype, hdrlen, pktlen, + partial? (new_ctb ? " partial" : " indeterminate") :"", + new_ctb? " new-ctb":""); + + /* Count it. */ + ctx->n_parsed_packets++; + + pkt->pkttype = pkttype; + rc = GPG_ERR_UNKNOWN_PACKET; /* default error */ + switch (pkttype) + { + case PKT_PUBLIC_KEY: + case PKT_PUBLIC_SUBKEY: + case PKT_SECRET_KEY: + case PKT_SECRET_SUBKEY: + pkt->pkt.public_key = xmalloc_clear (sizeof *pkt->pkt.public_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: + { + rc = parse_ring_trust (ctx, pktlen); + if (!rc) + goto again; /* Directly read the next packet. */ + } + 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_ENCRYPTED_AEAD: + rc = parse_encrypted_aead (inp, pkttype, pktlen, pkt, partial); + break; + case PKT_GPG_CONTROL: + rc = parse_gpg_control (inp, pkttype, pktlen, pkt, partial); + break; + case PKT_MARKER: + rc = parse_marker (inp, pkttype, pktlen); + break; + default: + /* Unknown packet. Skip it. */ + skip_packet (inp, pkttype, pktlen, partial); + break; + } + + /* Store a shallow copy of certain packets in the context. */ + free_packet (NULL, ctx); + if (!rc && (pkttype == PKT_PUBLIC_KEY + || pkttype == PKT_SECRET_KEY + || pkttype == PKT_USER_ID + || pkttype == PKT_ATTRIBUTE + || pkttype == PKT_SIGNATURE)) + { + ctx->last_pkt = *pkt; + } + + leave: + /* FIXME: We leak in case of an error (see the xmalloc's above). */ + if (!rc && iobuf_error (inp)) + rc = GPG_ERR_INV_KEYRING; + + /* FIXME: We use only the error code for now to avoid problems with + callers which have not been checked to always use gpg_err_code() + when comparing error codes. */ + return rc == -1? -1 : gpg_err_code (rc); +} + + +static void +dump_hex_line (int c, int *i) +{ + if (*i && !(*i % 8)) + { + if (*i && !(*i % 24)) + es_fprintf (listfp, "\n%4d:", *i); + else + es_putc (' ', listfp); + } + if (c == -1) + es_fprintf (listfp, " EOF"); + else + es_fprintf (listfp, " %02x", c); + ++*i; +} + + +/* Copy the contents of a packet from the pipeline IN to the pipeline + OUT. + + The header and length have already been read from INP and the + decoded values are given as PKGTYPE and PKTLEN. + + If the packet is a partial body length packet (RFC 4880, Section + 4.2.2.4), then iobuf_set_partial_block_modeiobuf_set_partial_block_mode + should already have been called on INP and PARTIAL should be set. + + If PARTIAL is set or PKTLEN is 0 and PKTTYPE is PKT_COMPRESSED, + copy until the first EOF is encountered on INP. + + Returns 0 on success and an error code if an error occurs. */ +static int +copy_packet (IOBUF inp, IOBUF out, int pkttype, + unsigned long pktlen, int partial) +{ + int rc; + int n; + char buf[100]; + + if (partial) + { + while ((n = iobuf_read (inp, buf, sizeof (buf))) != -1) + if ((rc = iobuf_write (out, buf, n))) + return rc; /* 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, sizeof (buf))) != -1) + if ((rc = iobuf_write (out, buf, n))) + return rc; /* write error */ + } + else + { + for (; pktlen; pktlen -= n) + { + n = pktlen > sizeof (buf) ? sizeof (buf) : pktlen; + n = iobuf_read (inp, buf, n); + if (n == -1) + return gpg_error (GPG_ERR_EOF); + if ((rc = iobuf_write (out, buf, n))) + return rc; /* write error */ + } + } + return 0; +} + + +/* Skip an unknown packet. PKTTYPE is the packet's type, PKTLEN is + the length of the packet's content and PARTIAL is whether partial + body length encoding in used (in this case PKTLEN is ignored). */ +static void +skip_packet (IOBUF inp, int pkttype, unsigned long pktlen, int partial) +{ + if (list_mode) + { + es_fprintf (listfp, ":unknown packet: type %2d, length %lu\n", + pkttype, pktlen); + if (pkttype) + { + int c, i = 0; + es_fputs ("dump:", listfp); + if (partial) + { + while ((c = iobuf_get (inp)) != -1) + dump_hex_line (c, &i); + } + else + { + for (; pktlen; pktlen--) + { + dump_hex_line ((c = iobuf_get (inp)), &i); + if (c == -1) + break; + } + } + es_putc ('\n', listfp); + return; + } + } + iobuf_skip_rest (inp, pktlen, partial); +} + + +/* Read PKTLEN bytes from INP and return them in a newly allocated + * buffer. In case of an error (including reading fewer than PKTLEN + * bytes from INP before EOF is returned), NULL is returned and an + * error message is logged. */ +static void * +read_rest (IOBUF inp, size_t pktlen) +{ + int c; + byte *buf, *p; + + buf = xtrymalloc (pktlen); + if (!buf) + { + gpg_error_t err = gpg_error_from_syserror (); + log_error ("error reading rest of packet: %s\n", gpg_strerror (err)); + return NULL; + } + for (p = buf; pktlen; pktlen--) + { + c = iobuf_get (inp); + if (c == -1) + { + log_error ("premature eof while reading rest of packet\n"); + xfree (buf); + return NULL; + } + *p++ = c; + } + + return buf; +} + + +/* Read a special size+body from INP. On success store an opaque MPI + with it at R_DATA. On error return an error code and store NULL at + R_DATA. Even in the error case store the number of read bytes at + R_NREAD. The caller shall pass the remaining size of the packet in + PKTLEN. */ +static gpg_error_t +read_size_body (iobuf_t inp, int pktlen, size_t *r_nread, + gcry_mpi_t *r_data) +{ + char buffer[256]; + char *tmpbuf; + int i, c, nbytes; + + *r_nread = 0; + *r_data = NULL; + + if (!pktlen) + return gpg_error (GPG_ERR_INV_PACKET); + c = iobuf_readbyte (inp); + if (c < 0) + return gpg_error (GPG_ERR_INV_PACKET); + pktlen--; + ++*r_nread; + nbytes = c; + if (nbytes < 2 || nbytes > 254) + return gpg_error (GPG_ERR_INV_PACKET); + if (nbytes > pktlen) + return gpg_error (GPG_ERR_INV_PACKET); + + buffer[0] = nbytes; + + for (i = 0; i < nbytes; i++) + { + c = iobuf_get (inp); + if (c < 0) + return gpg_error (GPG_ERR_INV_PACKET); + ++*r_nread; + buffer[1+i] = c; + } + + tmpbuf = xtrymalloc (1 + nbytes); + if (!tmpbuf) + return gpg_error_from_syserror (); + memcpy (tmpbuf, buffer, 1 + nbytes); + *r_data = gcry_mpi_set_opaque (NULL, tmpbuf, 8 * (1 + nbytes)); + if (!*r_data) + { + xfree (tmpbuf); + return gpg_error_from_syserror (); + } + return 0; +} + + +/* Parse a marker packet. */ +static int +parse_marker (IOBUF inp, int pkttype, unsigned long pktlen) +{ + (void) pkttype; + + if (pktlen != 3) + goto fail; + + if (iobuf_get (inp) != 'P') + { + pktlen--; + goto fail; + } + + if (iobuf_get (inp) != 'G') + { + pktlen--; + goto fail; + } + + if (iobuf_get (inp) != 'P') + { + pktlen--; + goto fail; + } + + if (list_mode) + es_fputs (":marker packet: PGP\n", listfp); + + return 0; + + fail: + log_error ("invalid marker packet\n"); + if (list_mode) + es_fputs (":marker packet: [invalid]\n", listfp); + iobuf_skip_rest (inp, pktlen, 0); + return GPG_ERR_INV_PACKET; +} + + +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, aead_algo, hash_algo, seskeylen, minlen; + + if (pktlen < 4) + goto too_short; + version = iobuf_get_noeof (inp); + pktlen--; + if (version == 4) + ; + else if (version == 5) + ; + else + { + log_error ("packet(%d) with unknown version %d\n", pkttype, version); + if (list_mode) + es_fprintf (listfp, ":symkey enc packet: [unknown version]\n"); + rc = gpg_error (GPG_ERR_INV_PACKET); + goto leave; + } + if (pktlen > 200) + { /* (we encode the seskeylen in a byte) */ + log_error ("packet(%d) too large\n", pkttype); + if (list_mode) + es_fprintf (listfp, ":symkey enc packet: [too large]\n"); + rc = gpg_error (GPG_ERR_INV_PACKET); + goto leave; + } + cipher_algo = iobuf_get_noeof (inp); + pktlen--; + if (version == 5) + { + aead_algo = iobuf_get_noeof (inp); + pktlen--; + } + else + aead_algo = 0; + if (pktlen < 2) + goto too_short; + 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 mode %d\n", s2kmode); + if (list_mode) + es_fprintf (listfp, ":symkey enc packet: [unknown S2K mode]\n"); + goto leave; + } + if (minlen > pktlen) + { + log_error ("packet with S2K %d too short\n", s2kmode); + if (list_mode) + es_fprintf (listfp, ":symkey enc packet: [too short]\n"); + rc = gpg_error (GPG_ERR_INV_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->aead_algo = aead_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_noeof (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")); + } + log_assert (!pktlen); + + if (list_mode) + { + es_fprintf (listfp, + ":symkey enc packet: version %d, cipher %d, aead %d," + "s2k %d, hash %d", + version, cipher_algo, aead_algo, s2kmode, hash_algo); + if (seskeylen) + { + /* To compute the size of the session key we need to know + * the size of the AEAD nonce which we may not know. Thus + * we show only the size of the entire encrypted session + * key. */ + if (aead_algo) + es_fprintf (listfp, ", encrypted seskey %d bytes", seskeylen); + else + es_fprintf (listfp, ", seskey %d bits", (seskeylen - 1) * 8); + } + es_fprintf (listfp, "\n"); + if (s2kmode == 1 || s2kmode == 3) + { + es_fprintf (listfp, "\tsalt "); + es_write_hexstring (listfp, k->s2k.salt, 8, 0, NULL); + if (s2kmode == 3) + es_fprintf (listfp, ", count %lu (%lu)", + S2K_DECODE_COUNT ((ulong) k->s2k.count), + (ulong) k->s2k.count); + es_fprintf (listfp, "\n"); + } + } + + leave: + iobuf_skip_rest (inp, pktlen, 0); + return rc; + + too_short: + log_error ("packet(%d) too short\n", pkttype); + if (list_mode) + es_fprintf (listfp, ":symkey enc packet: [too short]\n"); + rc = gpg_error (GPG_ERR_INV_PACKET); + goto leave; +} + + +static int +parse_pubkeyenc (IOBUF inp, int pkttype, unsigned long pktlen, + PACKET * packet) +{ + 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); + if (list_mode) + es_fputs (":pubkey enc packet: [too short]\n", listfp); + rc = gpg_error (GPG_ERR_INV_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); + if (list_mode) + es_fputs (":pubkey enc packet: [unknown version]\n", listfp); + rc = gpg_error (GPG_ERR_INV_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) + es_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) + es_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++) + { + if (k->pubkey_algo == PUBKEY_ALGO_ECDH && i == 1) + { + size_t n; + rc = read_size_body (inp, pktlen, &n, k->data+i); + pktlen -= n; + } + else + { + int n = pktlen; + k->data[i] = mpi_read (inp, &n, 0); + pktlen -= n; + if (!k->data[i]) + rc = gpg_error (GPG_ERR_INV_PACKET); + } + if (rc) + goto leave; + if (list_mode) + { + es_fprintf (listfp, "\tdata: "); + mpi_print (listfp, k->data[i], mpi_print_mode); + es_putc ('\n', listfp); + } + } + } + + leave: + iobuf_skip_rest (inp, pktlen, 0); + return rc; +} + + +/* Dump a subpacket to LISTFP. BUFFER contains the subpacket in + question and points to the type field in the subpacket header (not + the start of the header). TYPE is the subpacket's type with the + critical bit cleared. CRITICAL is the value of the CRITICAL bit. + BUFLEN is the length of the buffer and LENGTH is the length of the + subpacket according to the subpacket's header. */ +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) + { + es_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--; + + es_fprintf (listfp, "\t%s%ssubpkt %d len %u (", /*) */ + critical ? "critical " : "", + hashed ? "hashed " : "", type, (unsigned) length); + if (length > buflen) + { + es_fprintf (listfp, "too short: buffer is only %u)\n", (unsigned) buflen); + return; + } + switch (type) + { + case SIGSUBPKT_SIG_CREATED: + if (length >= 4) + es_fprintf (listfp, "sig created %s", + strtimestamp (buf32_to_u32 (buffer))); + break; + case SIGSUBPKT_SIG_EXPIRE: + if (length >= 4) + { + if (buf32_to_u32 (buffer)) + es_fprintf (listfp, "sig expires after %s", + strtimevalue (buf32_to_u32 (buffer))); + else + es_fprintf (listfp, "sig does not expire"); + } + break; + case SIGSUBPKT_EXPORTABLE: + if (length) + es_fprintf (listfp, "%sexportable", *buffer ? "" : "not "); + break; + case SIGSUBPKT_TRUST: + if (length != 2) + p = "[invalid trust subpacket]"; + else + es_fprintf (listfp, "trust signature of depth %d, value %d", buffer[0], + buffer[1]); + break; + case SIGSUBPKT_REGEXP: + if (!length) + p = "[invalid regexp subpacket]"; + else + { + es_fprintf (listfp, "regular expression: \""); + es_write_sanitized (listfp, buffer, length, "\"", NULL); + p = "\""; + } + break; + case SIGSUBPKT_REVOCABLE: + if (length) + es_fprintf (listfp, "%srevocable", *buffer ? "" : "not "); + break; + case SIGSUBPKT_KEY_EXPIRE: + if (length >= 4) + { + if (buf32_to_u32 (buffer)) + es_fprintf (listfp, "key expires after %s", + strtimevalue (buf32_to_u32 (buffer))); + else + es_fprintf (listfp, "key does not expire"); + } + break; + case SIGSUBPKT_PREF_SYM: + es_fputs ("pref-sym-algos:", listfp); + for (i = 0; i < length; i++) + es_fprintf (listfp, " %d", buffer[i]); + break; + case SIGSUBPKT_PREF_AEAD: + es_fputs ("pref-aead-algos:", listfp); + for (i = 0; i < length; i++) + es_fprintf (listfp, " %d", buffer[i]); + break; + case SIGSUBPKT_REV_KEY: + es_fputs ("revocation key: ", listfp); + if (length < 22) + p = "[too short]"; + else + { + es_fprintf (listfp, "c=%02x a=%d f=", buffer[0], buffer[1]); + for (i = 2; i < length; i++) + es_fprintf (listfp, "%02X", buffer[i]); + } + break; + case SIGSUBPKT_ISSUER: + if (length >= 8) + es_fprintf (listfp, "issuer key ID %08lX%08lX", + (ulong) buf32_to_u32 (buffer), + (ulong) buf32_to_u32 (buffer + 4)); + break; + case SIGSUBPKT_ISSUER_FPR: + if (length >= 21) + { + char *tmp; + es_fprintf (listfp, "issuer fpr v%d ", buffer[0]); + tmp = bin2hex (buffer+1, length-1, NULL); + if (tmp) + { + es_fputs (tmp, listfp); + xfree (tmp); + } + } + break; + case SIGSUBPKT_NOTATION: + { + es_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 + { + es_write_sanitized (listfp, s, n1, ")", NULL); + es_putc ('=', listfp); + + if (*buffer & 0x80) + es_write_sanitized (listfp, s + n1, n2, ")", NULL); + else + p = "[not human readable]"; + } + } + } + break; + case SIGSUBPKT_PREF_HASH: + es_fputs ("pref-hash-algos:", listfp); + for (i = 0; i < length; i++) + es_fprintf (listfp, " %d", buffer[i]); + break; + case SIGSUBPKT_PREF_COMPR: + es_fputs ("pref-zip-algos:", listfp); + for (i = 0; i < length; i++) + es_fprintf (listfp, " %d", buffer[i]); + break; + case SIGSUBPKT_KS_FLAGS: + es_fputs ("keyserver preferences:", listfp); + for (i = 0; i < length; i++) + es_fprintf (listfp, " %02X", buffer[i]); + break; + case SIGSUBPKT_PREF_KS: + es_fputs ("preferred keyserver: ", listfp); + es_write_sanitized (listfp, buffer, length, ")", NULL); + break; + case SIGSUBPKT_PRIMARY_UID: + p = "primary user ID"; + break; + case SIGSUBPKT_POLICY: + es_fputs ("policy: ", listfp); + es_write_sanitized (listfp, buffer, length, ")", NULL); + break; + case SIGSUBPKT_KEY_FLAGS: + es_fputs ("key flags:", listfp); + for (i = 0; i < length; i++) + es_fprintf (listfp, " %02X", buffer[i]); + break; + case SIGSUBPKT_SIGNERS_UID: + p = "signer's user ID"; + break; + case SIGSUBPKT_REVOC_REASON: + if (length) + { + es_fprintf (listfp, "revocation reason 0x%02x (", *buffer); + es_write_sanitized (listfp, buffer + 1, length - 1, ")", NULL); + p = ")"; + } + break; + case SIGSUBPKT_ARR: + es_fputs ("Big Brother's key (ignored): ", listfp); + if (length < 22) + p = "[too short]"; + else + { + es_fprintf (listfp, "c=%02x a=%d f=", buffer[0], buffer[1]); + if (length > 2) + es_write_hexstring (listfp, buffer+2, length-2, 0, NULL); + } + break; + case SIGSUBPKT_FEATURES: + es_fputs ("features:", listfp); + for (i = 0; i < length; i++) + es_fprintf (listfp, " %02x", buffer[i]); + break; + case SIGSUBPKT_SIGNATURE: + es_fputs ("signature: ", listfp); + if (length < 17) + p = "[too short]"; + else + es_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; + + case SIGSUBPKT_KEY_BLOCK: + es_fputs ("key-block: ", listfp); + if (length && buffer[0]) + p = "[unknown reserved octet]"; + else if (length < 50) /* 50 is an arbitrary min. length. */ + p = "[invalid subpacket]"; + else + { + /* estream_t fp; */ + /* fp = es_fopen ("a.key-block", "wb"); */ + /* log_assert (fp); */ + /* es_fwrite ( buffer+1, length-1, 1, fp); */ + /* es_fclose (fp); */ + es_fprintf (listfp, "[%u octets]", (unsigned int)length-1); + } + break; + + default: + if (type >= 100 && type <= 110) + p = "experimental / private subpacket"; + else + p = "?"; + break; + } + + es_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_AEAD: + 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_ISSUER_FPR: /* issuer key ID */ + if (n < 21) + 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; + case SIGSUBPKT_KEY_BLOCK: + if (n && buffer[0]) + return -1; /* Unknown version - ignore. */ + if (n < 50) + break; /* Definitely too short to carry a key block. */ + return 0; + default: + return 0; + } + return -2; +} + + +/* Return true if we understand the critical notation. */ +static int +can_handle_critical_notation (const byte *name, size_t len) +{ + strlist_t sl; + + register_known_notation (NULL); /* Make sure it is initialized. */ + + for (sl = known_notations_list; sl; sl = sl->next) + if (sl->flags == len && !memcmp (sl->d, name, len)) + return 1; /* Known */ + + if (opt.verbose && !glo_ctrl.silence_parse_warnings) + { + log_info(_("Unknown critical signature notation: ") ); + print_utf8_buffer (log_get_stream(), name, len); + log_printf ("\n"); + } + + return 0; /* Unknown. */ +} + + +static int +can_handle_critical (const byte * buffer, size_t n, int type) +{ + switch (type) + { + case SIGSUBPKT_NOTATION: + if (n >= 8) + { + size_t notation_len = ((buffer[4] << 8) | buffer[5]); + if (n - 8 >= notation_len) + return can_handle_critical_notation (buffer + 8, notation_len); + } + 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_ISSUER_FPR: /* issuer fingerprint */ + 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: + case SIGSUBPKT_REVOC_REASON: /* At least we know about it. */ + return 1; + + case SIGSUBPKT_KEY_BLOCK: + if (n && !buffer[0]) + return 1; + else + return 0; + + 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) + { + static char dummy[] = "x"; + /* Return a value different from NULL to indicate that + * there is no critical bit we do not understand. */ + return reqtype == SIGSUBPKT_TEST_CRITICAL ? dummy : 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 = buf32_to_size_t (buffer); + buffer += 4; + buflen -= 4; + } + else if (n >= 192) /* 4 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; + if (!buflen) + goto no_type_byte; + 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 && !glo_ctrl.silence_parse_warnings) + 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) + /* Returning NULL means we found a subpacket with the critical bit + set that we don't grok. We've iterated over all the subpackets + and haven't found such a packet so we need to return a non-NULL + value. */ + return buffer; + + /* Critical bit we don't understand. */ + if (start) + *start = -1; + return NULL; /* End of packets; not found. */ + + too_short: + if (opt.verbose && !glo_ctrl.silence_parse_warnings) + log_info ("buffer shorter than subpacket\n"); + if (start) + *start = -1; + return NULL; + + no_type_byte: + if (opt.verbose && !glo_ctrl.silence_parse_warnings) + log_info ("type octet missing in 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) +{ + const byte *p; + + p = parse_sig_subpkt (sig->hashed, reqtype, NULL); + if (!p) + p = parse_sig_subpkt (sig->unhashed, reqtype, NULL); + return p; +} + + +/* Find all revocation keys. Look in hashed area only. */ +void +parse_revkeys (PKT_signature * sig) +{ + const byte *revkey; + int seq = 0; + size_t len; + + if (sig->sig_class != 0x1F) + return; + + while ((revkey = enum_sig_subpkt (sig->hashed, SIGSUBPKT_REV_KEY, + &len, &seq, NULL))) + { + if (/* The only valid length is 22 bytes. See RFC 4880 + 5.2.3.15. */ + len == 22 + /* 0x80 bit must be set on the class. */ + && (revkey[0] & 0x80)) + { + sig->revkey = xrealloc (sig->revkey, + sizeof (struct revocation_key) * + (sig->numrevkeys + 1)); + + /* Copy the individual fields. */ + sig->revkey[sig->numrevkeys].class = revkey[0]; + sig->revkey[sig->numrevkeys].algid = revkey[1]; + memcpy (sig->revkey[sig->numrevkeys].fpr, &revkey[2], 20); + + 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); + if (list_mode) + es_fputs (":signature packet: [too short]\n", listfp); + 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); + if (list_mode) + es_fputs (":signature packet: [unknown version]\n", listfp); + rc = gpg_error (GPG_ERR_INV_PACKET); + goto leave; + } + + if (!is_v4) + { + if (pktlen == 0) + goto underflow; + md5_len = iobuf_get_noeof (inp); + pktlen--; + } + if (pktlen == 0) + goto underflow; + sig->sig_class = iobuf_get_noeof (inp); + pktlen--; + if (!is_v4) + { + if (pktlen < 12) + goto underflow; + sig->timestamp = read_32 (inp); + pktlen -= 4; + sig->keyid[0] = read_32 (inp); + pktlen -= 4; + sig->keyid[1] = read_32 (inp); + pktlen -= 4; + } + if (pktlen < 2) + goto underflow; + 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. */ + { + if (pktlen < 2) + goto underflow; + n = read_16 (inp); + pktlen -= 2; /* Length of hashed data. */ + if (pktlen < n) + goto underflow; + if (n > 10000) + { + log_error ("signature packet: hashed data too long\n"); + if (list_mode) + es_fputs (":signature packet: [hashed data too long]\n", listfp); + rc = GPG_ERR_INV_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"); + if (list_mode) + es_fputs (":signature packet: [premature eof]\n", listfp); + rc = -1; + goto leave; + } + pktlen -= n; + } + if (pktlen < 2) + goto underflow; + n = read_16 (inp); + pktlen -= 2; /* Length of unhashed data. */ + if (pktlen < n) + goto underflow; + if (n > 10000) + { + log_error ("signature packet: unhashed data too long\n"); + if (list_mode) + es_fputs (":signature packet: [unhashed data too long]\n", listfp); + rc = GPG_ERR_INV_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"); + if (list_mode) + es_fputs (":signature packet: [premature eof]\n", listfp); + rc = -1; + goto leave; + } + pktlen -= n; + } + } + + if (pktlen < 2) + goto underflow; + 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 = buf32_to_u32 (p); + else if (!(sig->pubkey_algo >= 100 && sig->pubkey_algo <= 110) + && opt.verbose && !glo_ctrl.silence_parse_warnings) + log_info ("signature packet without timestamp\n"); + + p = parse_sig_subpkt2 (sig, SIGSUBPKT_ISSUER); + if (p) + { + sig->keyid[0] = buf32_to_u32 (p); + sig->keyid[1] = buf32_to_u32 (p + 4); + } + else if (!(sig->pubkey_algo >= 100 && sig->pubkey_algo <= 110) + && opt.verbose && !glo_ctrl.silence_parse_warnings) + log_info ("signature packet without keyid\n"); + + p = parse_sig_subpkt (sig->hashed, SIGSUBPKT_SIG_EXPIRE, NULL); + if (p && buf32_to_u32 (p)) + sig->expiredate = sig->timestamp + buf32_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_SIGNERS_UID, &len); + if (p && len) + { + char *mbox; + + sig->signers_uid = try_make_printable_string (p, len, 0); + if (!sig->signers_uid) + { + rc = gpg_error_from_syserror (); + goto leave; + } + mbox = mailbox_from_userid (sig->signers_uid); + if (mbox) + { + xfree (sig->signers_uid); + sig->signers_uid = mbox; + } + } + + p = parse_sig_subpkt (sig->hashed, SIGSUBPKT_NOTATION, NULL); + if (p) + sig->flags.notation = 1; + + p = parse_sig_subpkt (sig->hashed, SIGSUBPKT_KEY_BLOCK, NULL); + if (p) + sig->flags.key_block = 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); + if (p && *p == 0) + sig->flags.exportable = 0; + + /* Find all revocation keys. */ + if (sig->sig_class == 0x1F) + parse_revkeys (sig); + } + + if (list_mode) + { + es_fprintf (listfp, ":signature packet: algo %d, keyid %08lX%08lX\n" + "\tversion %d, created %lu, md5len %d, sigclass 0x%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) + es_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(). */ + if (pktlen > (5 * MAX_EXTERN_MPI_BITS / 8)) + { + /* We include a limit to avoid too trivial DoS attacks by + having gpg allocate too much memory. */ + log_error ("signature packet: too much data\n"); + rc = GPG_ERR_INV_PACKET; + } + else + { + void *tmpp; + + tmpp = read_rest (inp, pktlen); + sig->data[0] = gcry_mpi_set_opaque (NULL, tmpp, tmpp? pktlen * 8 : 0); + pktlen = 0; + } + } + else + { + for (i = 0; i < ndata; i++) + { + n = pktlen; + sig->data[i] = mpi_read (inp, &n, 0); + pktlen -= n; + if (list_mode) + { + es_fprintf (listfp, "\tdata: "); + mpi_print (listfp, sig->data[i], mpi_print_mode); + es_putc ('\n', listfp); + } + if (!sig->data[i]) + rc = GPG_ERR_INV_PACKET; + } + } + + leave: + iobuf_skip_rest (inp, pktlen, 0); + return rc; + + underflow: + log_error ("packet(%d) too short\n", pkttype); + if (list_mode) + es_fputs (":signature packet: [too short]\n", listfp); + + iobuf_skip_rest (inp, pktlen, 0); + + return GPG_ERR_INV_PACKET; +} + + +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); + if (list_mode) + es_fputs (":onepass_sig packet: [too short]\n", listfp); + rc = gpg_error (GPG_ERR_INV_PACKET); + goto leave; + } + version = iobuf_get_noeof (inp); + pktlen--; + if (version != 3) + { + log_error ("onepass_sig with unknown version %d\n", version); + if (list_mode) + es_fputs (":onepass_sig packet: [unknown version]\n", listfp); + rc = gpg_error (GPG_ERR_INV_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) + es_fprintf (listfp, + ":onepass_sig packet: keyid %08lX%08lX\n" + "\tversion %d, sigclass 0x%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 int +parse_key (IOBUF inp, int pkttype, unsigned long pktlen, + byte * hdr, int hdrlen, PACKET * pkt) +{ + gpg_error_t err = 0; + int i, version, algorithm; + unsigned long timestamp, expiredate, max_expiredate; + int npkey, nskey; + u32 keyid[2]; + PKT_public_key *pk; + + (void) hdr; + + pk = pkt->pkt.public_key; /* PK has been cleared. */ + + version = iobuf_get_noeof (inp); + pktlen--; + if (pkttype == PKT_PUBLIC_SUBKEY && version == '#') + { + /* Early versions of G10 used the old PGP comments packets; + * luckily all those comments are started by a hash. */ + if (list_mode) + { + es_fprintf (listfp, ":rfc1991 comment packet: \""); + for (; pktlen; pktlen--) + { + int c; + c = iobuf_get (inp); + if (c == -1) + break; /* Ooops: shorter than indicated. */ + if (c >= ' ' && c <= 'z') + es_putc (c, listfp); + else + es_fprintf (listfp, "\\x%02x", c); + } + es_fprintf (listfp, "\"\n"); + } + iobuf_skip_rest (inp, pktlen, 0); + return 0; + } + else if (version == 4) + { + /* The only supported version. Use an older gpg + version (i.e. gpg 1.4) to parse v3 packets. */ + } + else if (version == 2 || version == 3) + { + /* Not anymore supported since 2.1. Use an older gpg version + * (i.e. gpg 1.4) to parse v3 packets. */ + if (opt.verbose > 1 && !glo_ctrl.silence_parse_warnings) + log_info ("packet(%d) with obsolete version %d\n", pkttype, version); + if (list_mode) + es_fprintf (listfp, ":key packet: [obsolete version %d]\n", version); + pk->version = version; + err = gpg_error (GPG_ERR_LEGACY_KEY); + goto leave; + } + else + { + log_error ("packet(%d) with unknown version %d\n", pkttype, version); + if (list_mode) + es_fputs (":key packet: [unknown version]\n", listfp); + err = gpg_error (GPG_ERR_UNKNOWN_VERSION); + goto leave; + } + + if (pktlen < 11) + { + log_error ("packet(%d) too short\n", pkttype); + if (list_mode) + es_fputs (":key packet: [too short]\n", listfp); + err = gpg_error (GPG_ERR_INV_PACKET); + goto leave; + } + else if (pktlen > MAX_KEY_PACKET_LENGTH) + { + log_error ("packet(%d) too large\n", pkttype); + if (list_mode) + es_fputs (":key packet: [too larget]\n", listfp); + err = gpg_error (GPG_ERR_INV_PACKET); + goto leave; + } + + timestamp = read_32 (inp); + pktlen -= 4; + expiredate = 0; /* have to get it from the selfsignature */ + max_expiredate = 0; + algorithm = iobuf_get_noeof (inp); + pktlen--; + if (list_mode) + es_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); + + pk->timestamp = timestamp; + pk->expiredate = expiredate; + pk->max_expiredate = max_expiredate; + pk->hdrbytes = hdrlen; + pk->version = version; + pk->flags.primary = (pkttype == PKT_PUBLIC_KEY || pkttype == PKT_SECRET_KEY); + pk->pubkey_algo = algorithm; + + nskey = pubkey_get_nskey (algorithm); + npkey = pubkey_get_npkey (algorithm); + if (!npkey) + { + if (list_mode) + es_fprintf (listfp, "\tunknown algorithm %d\n", algorithm); + unknown_pubkey_warning (algorithm); + } + + if (!npkey) + { + /* Unknown algorithm - put data into an opaque MPI. */ + void *tmpp = read_rest (inp, pktlen); + /* Current gcry_mpi_cmp does not handle a (NULL,n>0) nicely and + * thus we avoid to create such an MPI. */ + pk->pkey[0] = gcry_mpi_set_opaque (NULL, tmpp, tmpp? pktlen * 8 : 0); + pktlen = 0; + goto leave; + } + else + { + for (i = 0; i < npkey; i++) + { + if ( (algorithm == PUBKEY_ALGO_ECDSA && (i == 0)) + || (algorithm == PUBKEY_ALGO_EDDSA && (i == 0)) + || (algorithm == PUBKEY_ALGO_ECDH && (i == 0 || i == 2))) + { + /* Read the OID (i==1) or the KDF params (i==2). */ + size_t n; + err = read_size_body (inp, pktlen, &n, pk->pkey+i); + pktlen -= n; + } + else + { + unsigned int n = pktlen; + pk->pkey[i] = mpi_read (inp, &n, 0); + pktlen -= n; + if (!pk->pkey[i]) + err = gpg_error (GPG_ERR_INV_PACKET); + } + if (err) + goto leave; + if (list_mode) + { + es_fprintf (listfp, "\tpkey[%d]: ", i); + mpi_print (listfp, pk->pkey[i], mpi_print_mode); + if ((algorithm == PUBKEY_ALGO_ECDSA + || algorithm == PUBKEY_ALGO_EDDSA + || algorithm == PUBKEY_ALGO_ECDH) && i==0) + { + char *curve = openpgp_oid_to_str (pk->pkey[0]); + const char *name = openpgp_oid_to_curve (curve, 0); + es_fprintf (listfp, " %s (%s)", name?name:"", curve); + xfree (curve); + } + es_putc ('\n', listfp); + } + } + } + if (list_mode) + keyid_from_pk (pk, keyid); + + if (pkttype == PKT_SECRET_KEY || pkttype == PKT_SECRET_SUBKEY) + { + struct seckey_info *ski; + byte temp[16]; + size_t snlen = 0; + + if (pktlen < 1) + { + err = gpg_error (GPG_ERR_INV_PACKET); + goto leave; + } + + pk->seckey_info = ski = xtrycalloc (1, sizeof *ski); + if (!pk->seckey_info) + { + err = gpg_error_from_syserror (); + goto leave; + } + + ski->algo = iobuf_get_noeof (inp); + pktlen--; + if (ski->algo) + { + ski->is_protected = 1; + ski->s2k.count = 0; + if (ski->algo == 254 || ski->algo == 255) + { + if (pktlen < 3) + { + err = gpg_error (GPG_ERR_INV_PACKET); + goto leave; + } + ski->sha1chk = (ski->algo == 254); + ski->algo = iobuf_get_noeof (inp); + pktlen--; + /* Note that a ski->algo > 110 is illegal, but I'm not + erroring on it here as otherwise there would be no + way to delete such a key. */ + ski->s2k.mode = iobuf_get_noeof (inp); + pktlen--; + ski->s2k.hash_algo = iobuf_get_noeof (inp); + pktlen--; + /* Check for the special GNU extension. */ + if (ski->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) + es_fprintf (listfp, "\tunknown S2K %d\n", + ski->s2k.mode); + err = gpg_error (GPG_ERR_INV_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 to MODE + * with a base of 1000. */ + ski->s2k.mode = 1000 + temp[3]; + } + + /* Read the salt. */ + switch (ski->s2k.mode) + { + case 1: + case 3: + for (i = 0; i < 8 && pktlen; i++, pktlen--) + temp[i] = iobuf_get_noeof (inp); + if (i < 8) + { + err = gpg_error (GPG_ERR_INV_PACKET); + goto leave; + } + memcpy (ski->s2k.salt, temp, 8); + break; + } + + /* Check the mode. */ + switch (ski->s2k.mode) + { + case 0: + if (list_mode) + es_fprintf (listfp, "\tsimple S2K"); + break; + case 1: + if (list_mode) + es_fprintf (listfp, "\tsalted S2K"); + break; + case 3: + if (list_mode) + es_fprintf (listfp, "\titer+salt S2K"); + break; + case 1001: + if (list_mode) + es_fprintf (listfp, "\tgnu-dummy S2K"); + break; + case 1002: + if (list_mode) + es_fprintf (listfp, "\tgnu-divert-to-card S2K"); + break; + default: + if (list_mode) + es_fprintf (listfp, "\tunknown %sS2K %d\n", + ski->s2k.mode < 1000 ? "" : "GNU ", + ski->s2k.mode); + err = gpg_error (GPG_ERR_INV_PACKET); + goto leave; + } + + /* Print some info. */ + if (list_mode) + { + es_fprintf (listfp, ", algo: %d,%s hash: %d", + ski->algo, + ski->sha1chk ? " SHA1 protection," + : " simple checksum,", ski->s2k.hash_algo); + if (ski->s2k.mode == 1 || ski->s2k.mode == 3) + { + es_fprintf (listfp, ", salt: "); + es_write_hexstring (listfp, ski->s2k.salt, 8, 0, NULL); + } + es_putc ('\n', listfp); + } + + /* Read remaining protection parameters. */ + if (ski->s2k.mode == 3) + { + if (pktlen < 1) + { + err = gpg_error (GPG_ERR_INV_PACKET); + goto leave; + } + ski->s2k.count = iobuf_get_noeof (inp); + pktlen--; + if (list_mode) + es_fprintf (listfp, "\tprotect count: %lu (%lu)\n", + (ulong)S2K_DECODE_COUNT ((ulong)ski->s2k.count), + (ulong) ski->s2k.count); + } + else if (ski->s2k.mode == 1002) + { + /* Read the serial number. */ + if (pktlen < 1) + { + err = gpg_error (GPG_ERR_INV_PACKET); + goto leave; + } + snlen = iobuf_get (inp); + pktlen--; + if (pktlen < snlen || snlen == (size_t)(-1)) + { + err = gpg_error (GPG_ERR_INV_PACKET); + goto leave; + } + } + } + else /* Old version; no S2K, so we set mode to 0, hash MD5. */ + { + /* Note that a ski->algo > 110 is illegal, but I'm not + erroring on it here as otherwise there would be no + way to delete such a key. */ + ski->s2k.mode = 0; + ski->s2k.hash_algo = DIGEST_ALGO_MD5; + if (list_mode) + es_fprintf (listfp, "\tprotect algo: %d (hash algo: %d)\n", + ski->algo, ski->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 + * ski->ivlen = cipher_get_blocksize (ski->algo); + * won't work. The only solution I see is to hardwire it. + * NOTE: if you change the ivlen above 16, don't forget to + * enlarge temp. */ + ski->ivlen = openpgp_cipher_blocklen (ski->algo); + log_assert (ski->ivlen <= sizeof (temp)); + + if (ski->s2k.mode == 1001) + ski->ivlen = 0; + else if (ski->s2k.mode == 1002) + ski->ivlen = snlen < 16 ? snlen : 16; + + if (pktlen < ski->ivlen) + { + err = gpg_error (GPG_ERR_INV_PACKET); + goto leave; + } + for (i = 0; i < ski->ivlen; i++, pktlen--) + temp[i] = iobuf_get_noeof (inp); + if (list_mode) + { + es_fprintf (listfp, + ski->s2k.mode == 1002 ? "\tserial-number: " + : "\tprotect IV: "); + for (i = 0; i < ski->ivlen; i++) + es_fprintf (listfp, " %02x", temp[i]); + es_putc ('\n', listfp); + } + memcpy (ski->iv, temp, ski->ivlen); + } + + /* 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 (ski->s2k.mode == 1001 || ski->s2k.mode == 1002) + { + /* Better set some dummy stuff here. */ + pk->pkey[npkey] = gcry_mpi_set_opaque (NULL, + xstrdup ("dummydata"), + 10 * 8); + pktlen = 0; + } + else if (ski->is_protected) + { + void *tmpp; + + if (pktlen < 2) /* At least two bytes for the length. */ + { + err = gpg_error (GPG_ERR_INV_PACKET); + goto leave; + } + + /* Ugly: The length is encrypted too, so we read all stuff + * up to the end of the packet into the first SKEY + * element. */ + + tmpp = read_rest (inp, pktlen); + pk->pkey[npkey] = gcry_mpi_set_opaque (NULL, + tmpp, tmpp? pktlen * 8 : 0); + /* Mark that MPI as protected - we need this information for + importing a key. The OPAQUE flag can't be used because + we also store public EdDSA values in opaque MPIs. */ + if (pk->pkey[npkey]) + gcry_mpi_set_flag (pk->pkey[npkey], GCRYMPI_FLAG_USER1); + pktlen = 0; + if (list_mode) + es_fprintf (listfp, "\tskey[%d]: [v4 protected]\n", npkey); + } + else + { + u16 csum_tweak = 0; + + /* Not encrypted. */ + for (i = npkey; i < nskey; i++) + { + unsigned int n; + + if (pktlen < 2) /* At least two bytes for the length. */ + { + err = gpg_error (GPG_ERR_INV_PACKET); + goto leave; + } + n = pktlen; + if (algorithm == PUBKEY_ALGO_EDDSA) + pk->pkey[i] = mpi_read_detect_0_removal (inp, &n, 0, + &csum_tweak); + else + pk->pkey[i] = mpi_read (inp, &n, 0); + pktlen -= n; + if (list_mode) + { + es_fprintf (listfp, "\tskey[%d]: ", i); + mpi_print (listfp, pk->pkey[i], mpi_print_mode); + es_putc ('\n', listfp); + } + + if (!pk->pkey[i]) + err = gpg_error (GPG_ERR_INV_PACKET); + } + if (err) + goto leave; + + if (pktlen < 2) + { + err = gpg_error (GPG_ERR_INV_PACKET); + goto leave; + } + ski->csum = read_16 (inp); + ski->csum += csum_tweak; + pktlen -= 2; + if (list_mode) + es_fprintf (listfp, "\tchecksum: %04hx\n", ski->csum); + } + } + + /* Note that KEYID below has been initialized above in list_mode. */ + if (list_mode) + es_fprintf (listfp, "\tkeyid: %08lX%08lX\n", + (ulong) keyid[0], (ulong) keyid[1]); + + leave: + iobuf_skip_rest (inp, pktlen, 0); + return err; +} + + +/* 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 = buf32_to_size_t (buffer); + 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; + + if (!n) + { + /* Too short to encode the subpacket type. */ + if (opt.verbose) + log_info ("attribute subpacket too short\n"); + break; + } + + 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 && !glo_ctrl.silence_parse_warnings) + 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 > MAX_UID_PACKET_LENGTH) + { + log_error ("packet(%d) too large\n", pkttype); + if (list_mode) + es_fprintf (listfp, ":user ID packet: [too large]\n"); + iobuf_skip_rest (inp, pktlen, 0); + return GPG_ERR_INV_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; + es_fprintf (listfp, ":user ID packet: \""); + /* fixme: Hey why don't we replace this with es_write_sanitized?? */ + for (p = packet->pkt.user_id->name; n; p++, n--) + { + if (*p >= ' ' && *p <= 'z') + es_putc (*p, listfp); + else + es_fprintf (listfp, "\\x%02x", *p); + } + es_fprintf (listfp, "\"\n"); + } + return 0; +} + + +void +make_attribute_uidname (PKT_user_id * uid, size_t max_namelen) +{ + log_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; + + (void) pkttype; + + /* We better cap the size of an attribute packet to make DoS not too + easy. 16MB should be more then enough for one attribute packet + (ie. a photo). */ + if (pktlen > MAX_ATTR_PACKET_LENGTH) + { + log_error ("packet(%d) too large\n", pkttype); + if (list_mode) + es_fprintf (listfp, ":attribute packet: [too large]\n"); + iobuf_skip_rest (inp, pktlen, 0); + return GPG_ERR_INV_PACKET; + } + +#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? pktlen:1); + 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) + { + es_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 > MAX_COMMENT_PACKET_LENGTH) + { + log_error ("packet(%d) too large\n", pkttype); + if (list_mode) + es_fprintf (listfp, ":%scomment packet: [too large]\n", + pkttype == PKT_OLD_COMMENT ? "OpenPGP draft " : ""); + iobuf_skip_rest (inp, pktlen, 0); + return GPG_ERR_INV_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; + es_fprintf (listfp, ":%scomment packet: \"", pkttype == PKT_OLD_COMMENT ? + "OpenPGP draft " : ""); + for (p = packet->pkt.comment->data; n; p++, n--) + { + if (*p >= ' ' && *p <= 'z') + es_putc (*p, listfp); + else + es_fprintf (listfp, "\\x%02x", *p); + } + es_fprintf (listfp, "\"\n"); + } + return 0; +} + + +/* Parse a ring trust packet RFC4880 (5.10). + * + * This parser is special in that the packet is not stored as a packet + * but its content is merged into the previous packet. */ +static gpg_error_t +parse_ring_trust (parse_packet_ctx_t ctx, unsigned long pktlen) +{ + gpg_error_t err; + iobuf_t inp = ctx->inp; + PKT_ring_trust rt = {0}; + int c; + int not_gpg = 0; + + if (!pktlen) + { + if (list_mode) + es_fprintf (listfp, ":trust packet: empty\n"); + err = 0; + goto leave; + } + + c = iobuf_get_noeof (inp); + pktlen--; + rt.trustval = c; + if (pktlen) + { + if (!c) + { + c = iobuf_get_noeof (inp); + /* We require that bit 7 of the sigcache is 0 (easier + * eof handling). */ + if (!(c & 0x80)) + rt.sigcache = c; + } + else + iobuf_get_noeof (inp); /* Dummy read. */ + pktlen--; + } + + /* Next is the optional subtype. */ + if (pktlen > 3) + { + char tmp[4]; + tmp[0] = iobuf_get_noeof (inp); + tmp[1] = iobuf_get_noeof (inp); + tmp[2] = iobuf_get_noeof (inp); + tmp[3] = iobuf_get_noeof (inp); + pktlen -= 4; + if (!memcmp (tmp, "gpg", 3)) + rt.subtype = tmp[3]; + else + not_gpg = 1; + } + /* If it is a key or uid subtype read the remaining data. */ + if ((rt.subtype == RING_TRUST_KEY || rt.subtype == RING_TRUST_UID) + && pktlen >= 6 ) + { + int i; + unsigned int namelen; + + rt.keyorg = iobuf_get_noeof (inp); + pktlen--; + rt.keyupdate = read_32 (inp); + pktlen -= 4; + namelen = iobuf_get_noeof (inp); + pktlen--; + if (namelen && pktlen) + { + rt.url = xtrymalloc (namelen + 1); + if (!rt.url) + { + err = gpg_error_from_syserror (); + goto leave; + } + for (i = 0; pktlen && i < namelen; pktlen--, i++) + rt.url[i] = iobuf_get_noeof (inp); + rt.url[i] = 0; + } + } + + if (list_mode) + { + if (rt.subtype == RING_TRUST_SIG) + es_fprintf (listfp, ":trust packet: sig flag=%02x sigcache=%02x\n", + rt.trustval, rt.sigcache); + else if (rt.subtype == RING_TRUST_UID || rt.subtype == RING_TRUST_KEY) + { + unsigned char *p; + + es_fprintf (listfp, ":trust packet: %s upd=%lu src=%d%s", + (rt.subtype == RING_TRUST_UID? "uid" : "key"), + (unsigned long)rt.keyupdate, + rt.keyorg, + (rt.url? " url=":"")); + if (rt.url) + { + for (p = rt.url; *p; p++) + { + if (*p >= ' ' && *p <= 'z') + es_putc (*p, listfp); + else + es_fprintf (listfp, "\\x%02x", *p); + } + } + es_putc ('\n', listfp); + } + else if (not_gpg) + es_fprintf (listfp, ":trust packet: not created by gpg\n"); + else + es_fprintf (listfp, ":trust packet: subtype=%02x\n", + rt.subtype); + } + + /* Now transfer the data to the respective packet. Do not do this + * if SKIP_META is set. */ + if (!ctx->last_pkt.pkt.generic || ctx->skip_meta) + ; + else if (rt.subtype == RING_TRUST_SIG + && ctx->last_pkt.pkttype == PKT_SIGNATURE) + { + PKT_signature *sig = ctx->last_pkt.pkt.signature; + + if ((rt.sigcache & 1)) + { + sig->flags.checked = 1; + sig->flags.valid = !!(rt.sigcache & 2); + } + } + else if (rt.subtype == RING_TRUST_UID + && (ctx->last_pkt.pkttype == PKT_USER_ID + || ctx->last_pkt.pkttype == PKT_ATTRIBUTE)) + { + PKT_user_id *uid = ctx->last_pkt.pkt.user_id; + + uid->keyorg = rt.keyorg; + uid->keyupdate = rt.keyupdate; + uid->updateurl = rt.url; + rt.url = NULL; + } + else if (rt.subtype == RING_TRUST_KEY + && (ctx->last_pkt.pkttype == PKT_PUBLIC_KEY + || ctx->last_pkt.pkttype == PKT_SECRET_KEY)) + { + PKT_public_key *pk = ctx->last_pkt.pkt.public_key; + + pk->keyorg = rt.keyorg; + pk->keyupdate = rt.keyupdate; + pk->updateurl = rt.url; + rt.url = NULL; + } + + err = 0; + + leave: + xfree (rt.url); + free_packet (NULL, ctx); /* This sets ctx->last_pkt to NULL. */ + iobuf_skip_rest (inp, pktlen, 0); + return err; +} + + +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); + if (list_mode) + es_fputs (":literal data packet: [too short]\n", listfp); + rc = gpg_error (GPG_ERR_INV_PACKET); + goto leave; + } + mode = iobuf_get_noeof (inp); + if (pktlen) + pktlen--; + namelen = iobuf_get_noeof (inp); + if (pktlen) + pktlen--; + /* Note that namelen will never exceed 255 bytes. */ + 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; + } + /* Fill up NAME so that a check with valgrind won't complain about + * reading from uninitalized memory. This case may be triggred by + * corrupted packets. */ + for (; i < namelen; i++) + pt->name[i] = 0; + + pt->timestamp = read_32 (inp); + if (pktlen) + pktlen -= 4; + pt->len = pktlen; + pt->buf = inp; + + if (list_mode) + { + es_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') + es_putc (*p, listfp); + else + es_fprintf (listfp, "\\x%02x", *p); + } + es_fprintf (listfp, "\",\n\traw data: "); + if (partial) + es_fprintf (listfp, "unknown length\n"); + else + es_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). */ + (void) pkttype; + (void) pktlen; + + 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) + es_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 is set below. */ + ed->extralen = 0; /* Unknown here; only used in build_packet. */ + ed->buf = NULL; + ed->new_ctb = new_ctb; + ed->is_partial = partial; + ed->aead_algo = 0; + ed->cipher_algo = 0; /* Only used with AEAD. */ + ed->chunkbyte = 0; /* Only used with AEAD. */ + 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); + if (list_mode) + es_fputs (":encrypted data packet: [unknown version]\n", listfp); + /*skip_rest(inp, pktlen); should we really do this? */ + rc = gpg_error (GPG_ERR_INV_PACKET); + goto leave; + } + ed->mdc_method = DIGEST_ALGO_SHA1; + } + else + ed->mdc_method = 0; + + /* A basic sanity check. We need at least an 8 byte IV plus the 2 + detection bytes. Note that we don't known the algorithm and thus + we may only check against the minimum blocksize. */ + if (orig_pktlen && pktlen < 10) + { + /* Actually this is blocksize+2. */ + log_error ("packet(%d) too short\n", pkttype); + if (list_mode) + es_fputs (":encrypted data packet: [too short]\n", listfp); + rc = GPG_ERR_INV_PACKET; + iobuf_skip_rest (inp, pktlen, partial); + goto leave; + } + + /* Store the remaining length of the encrypted data (i.e. without + the MDC version number but with the IV etc.). This value is + required during decryption. */ + ed->len = pktlen; + + if (list_mode) + { + if (orig_pktlen) + es_fprintf (listfp, ":encrypted data packet:\n\tlength: %lu\n", + orig_pktlen); + else + es_fprintf (listfp, ":encrypted data packet:\n\tlength: unknown\n"); + if (ed->mdc_method) + es_fprintf (listfp, "\tmdc_method: %d\n", ed->mdc_method); + } + + ed->buf = inp; + + leave: + return rc; +} + + +/* Note, that this code is not anymore used in real life because the + MDC checking is now done right after the decryption in + decrypt_data. */ +static int +parse_mdc (IOBUF inp, int pkttype, unsigned long pktlen, + PACKET * pkt, int new_ctb) +{ + int rc = 0; + PKT_mdc *mdc; + byte *p; + + (void) pkttype; + + mdc = pkt->pkt.mdc = xmalloc (sizeof *pkt->pkt.mdc); + if (list_mode) + es_fprintf (listfp, ":mdc packet: length=%lu\n", pktlen); + if (!new_ctb || pktlen != 20) + { + log_error ("mdc_packet with invalid encoding\n"); + rc = gpg_error (GPG_ERR_INV_PACKET); + goto leave; + } + p = mdc->hash; + for (; pktlen; pktlen--, p++) + *p = iobuf_get_noeof (inp); + + leave: + return rc; +} + + +static gpg_error_t +parse_encrypted_aead (iobuf_t inp, int pkttype, unsigned long pktlen, + PACKET *pkt, int partial) +{ + int rc = 0; + PKT_encrypted *ed; + unsigned long orig_pktlen = pktlen; + int version; + + ed = pkt->pkt.encrypted = xtrymalloc (sizeof *pkt->pkt.encrypted); + if (!ed) + return gpg_error_from_syserror (); + ed->len = 0; + ed->extralen = 0; /* (only used in build_packet.) */ + ed->buf = NULL; + ed->new_ctb = 1; /* (packet number requires a new CTB anyway.) */ + ed->is_partial = partial; + ed->mdc_method = 0; + /* A basic sanity check. We need one version byte, one algo byte, + * one aead algo byte, one chunkbyte, at least 15 byte IV. */ + if (orig_pktlen && pktlen < 19) + { + log_error ("packet(%d) too short\n", pkttype); + if (list_mode) + es_fputs (":aead encrypted packet: [too short]\n", listfp); + rc = gpg_error (GPG_ERR_INV_PACKET); + iobuf_skip_rest (inp, pktlen, partial); + goto leave; + } + + version = iobuf_get_noeof (inp); + if (orig_pktlen) + pktlen--; + if (version != 1) + { + log_error ("aead encrypted packet with unknown version %d\n", + version); + if (list_mode) + es_fputs (":aead encrypted packet: [unknown version]\n", listfp); + /*skip_rest(inp, pktlen); should we really do this? */ + rc = gpg_error (GPG_ERR_INV_PACKET); + goto leave; + } + + ed->cipher_algo = iobuf_get_noeof (inp); + if (orig_pktlen) + pktlen--; + ed->aead_algo = iobuf_get_noeof (inp); + if (orig_pktlen) + pktlen--; + ed->chunkbyte = iobuf_get_noeof (inp); + if (orig_pktlen) + pktlen--; + + /* Store the remaining length of the encrypted data. We read the + * rest during decryption. */ + ed->len = pktlen; + + if (list_mode) + { + es_fprintf (listfp, ":aead encrypted packet: cipher=%u aead=%u cb=%u\n", + ed->cipher_algo, ed->aead_algo, ed->chunkbyte); + if (orig_pktlen) + es_fprintf (listfp, "\tlength: %lu\n", orig_pktlen); + else + es_fprintf (listfp, "\tlength: unknown\n"); + } + + ed->buf = inp; + + leave: + return rc; +} + + +/* + * This packet is internally generated by us (in 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 coming 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; + + (void) pkttype; + + if (list_mode) + es_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) + es_fputs ("- gpg control packet", listfp); + + 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; + es_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 ((c = iobuf_get (inp)), &i); + if (c == -1) + break; + } + } + es_putc ('\n', listfp); + } + iobuf_skip_rest (inp, pktlen, 0); + return gpg_error (GPG_ERR_INV_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..f94b93c --- /dev/null +++ b/g10/passphrase.c @@ -0,0 +1,581 @@ +/* passphrase.c - Get a passphrase + * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, + * 2005, 2006, 2007, 2009, 2011 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#ifdef HAVE_LOCALE_H +#include +#endif +#ifdef HAVE_LANGINFO_CODESET +#include +#endif + +#include "gpg.h" +#include "../common/util.h" +#include "options.h" +#include "../common/ttyio.h" +#include "keydb.h" +#include "main.h" +#include "../common/i18n.h" +#include "../common/status.h" +#include "call-agent.h" +#include "../common/shareddefs.h" + +static char *fd_passwd = NULL; +static char *next_pw = NULL; +static char *last_pw = NULL; + + + +/* Pack an s2k iteration count into the form specified in 2440. If + we're in between valid values, round up. With value 0 return the + old default. */ +unsigned char +encode_s2k_iterations (int iterations) +{ + gpg_error_t err; + unsigned char c=0; + unsigned char result; + unsigned int count; + + if (!iterations) + { + unsigned long mycnt; + + /* Ask the gpg-agent for a useful iteration count. */ + err = agent_get_s2k_count (&mycnt); + if (err || mycnt < 65536) + { + /* Don't print an error if an older agent is used. */ + if (err && gpg_err_code (err) != GPG_ERR_ASS_PARAMETER) + log_error (_("problem with the agent: %s\n"), gpg_strerror (err)); + /* Default to 65536 which we used up to 2.0.13. */ + return 96; + } + else if (mycnt >= 65011712) + return 255; /* Largest possible value. */ + else + return encode_s2k_iterations ((int)mycnt); + } + + if (iterations <= 1024) + return 0; /* Command line arg compatibility. */ + + 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 +have_static_passphrase() +{ + return (!!fd_passwd + && (opt.batch || opt.pinentry_mode == PINENTRY_MODE_LOOPBACK)); +} + +/* Return a static passphrase. The returned value is only valid as + long as no other passphrase related function is called. NULL may + be returned if no passphrase has been set; better use + have_static_passphrase first. */ +const char * +get_static_passphrase (void) +{ + return fd_passwd; +} + + +/**************** + * 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; +} + +/* 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 (! gnupg_fd_valid (fd)) + log_fatal ("passphrase-fd is invalid: %s\n", strerror (errno)); + + if ( !opt.batch && opt.pinentry_mode != PINENTRY_MODE_LOOPBACK) + { /* Not used but we have to do a dummy read, so that it won't end + up at the begin of the message if the quite usual trick to + prepend the passphtrase to the message is used. */ + char buf[1]; + + while (!(read (fd, buf, 1) != 1 || *buf == '\n' )) + ; + *buf = 0; + return; + } + + for (pw = NULL, i = len = 100; ; i++ ) + { + if (i >= len-1 ) + { + char *pw2 = pw; + len += 100; + pw = xmalloc_secure( len ); + if( pw2 ) + { + memcpy(pw, pw2, i ); + xfree (pw2); + } + else + i=0; + } + if (read( fd, pw+i, 1) != 1 || pw[i] == '\n' ) + break; + } + pw[i] = 0; + if (!opt.batch && opt.pinentry_mode != PINENTRY_MODE_LOOPBACK) + tty_printf("\b\b\b \n" ); + + xfree ( fd_passwd ); + fd_passwd = pw; +} + + +/* + * Ask the GPG Agent for the passphrase. + * If NOCACHE is set the symmetric passpharse caching will not be used. + * + * If REPEAT is positive, a new passphrase is requested and the agent + * shall require REPEAT times repetitions of the entered passphrase. + * This is used for symmetric encryption. + * + * 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. + * + * For FLAGS see passphrase_to_dek; + */ +static char * +passphrase_get (int newsymkey, int nocache, const char *cacheid, int repeat, + const char *tryagain_text, unsigned int flags, int *canceled) +{ + int rc; + char *pw = NULL; + char *orig_codeset; + const char *my_cacheid; + const char *desc; + + if (canceled) + *canceled = 0; + + orig_codeset = i18n_switchto_utf8 (); + + if (!nocache && cacheid) + my_cacheid = cacheid; + else + my_cacheid = NULL; + + if (tryagain_text) + tryagain_text = _(tryagain_text); + + if ((flags & GETPASSWORD_FLAG_SYMDECRYPT)) + desc = _("Please enter the passphrase for decryption."); + else + desc = _("Enter passphrase\n"); + + /* Here we have: + * REPEAT is set in create mode and if opt.passphrase_repeat is set. + * (Thus it is not a clean indication that we want a new passphrase). + * NOCACHE is set in create mode or if --no-symkey-cache is used. + * CACHEID is only set if caching shall be used. + * NEWSYMKEY has been added latter to make it clear that a new key + * is requested. The whole chain of API is a bit too complex since + * we we stripped things out over time; however, there is no time + * for a full state analysis and thus this new parameter. + */ + rc = agent_get_passphrase (my_cacheid, tryagain_text, NULL, + desc, + newsymkey, repeat, nocache, &pw); + + i18n_switchback (orig_codeset); + + + if (!rc) + ; + else if (gpg_err_code (rc) == GPG_ERR_CANCELED + || gpg_err_code (rc) == GPG_ERR_FULLY_CANCELED) + { + log_info (_("cancelled by user\n") ); + if (canceled) + *canceled = 1; + } + else + { + log_error (_("problem with the agent: %s\n"), gpg_strerror (rc)); + /* Due to limitations in the API of the upper layers they + consider an error as no passphrase entered. This works in + most cases but not during key creation where this should + definitely not happen and let it continue without requiring a + passphrase. Given that now all the upper layers handle a + cancel correctly, we simply set the cancel flag now for all + errors from the agent. */ + if (canceled) + *canceled = 1; + + write_status_errcode ("get_passphrase", rc); + } + + if (rc) + { + xfree (pw); + pw = NULL; + } + return pw; +} + + +/* + * Clear the cached passphrase with CACHEID. + */ +void +passphrase_clear_cache (const char *cacheid) +{ + int rc; + + rc = agent_clear_passphrase (cacheid); + if (rc) + log_error (_("problem with the agent: %s\n"), gpg_strerror (rc)); +} + + +/* Return a new DEK object using the string-to-key specifier S2K. + * Returns NULL if the user canceled the passphrase entry and if + * CANCELED is not NULL, sets it to true. + * + * If CREATE is true a new passphrase sll be created. If NOCACHE is + * true the symmetric key caching will not be used. + * + * FLAG bits are: + * GETPASSWORD_FLAG_SYMDECRYPT := for symmetric decryption + */ +DEK * +passphrase_to_dek (int cipher_algo, STRING2KEY *s2k, + int create, int nocache, + const char *tryagain_text, unsigned int flags, + int *canceled) +{ + char *pw = NULL; + DEK *dek; + STRING2KEY help_s2k; + int dummy_canceled; + char s2k_cacheidbuf[1+16+1]; + char *s2k_cacheid = NULL; + + if (!canceled) + canceled = &dummy_canceled; + *canceled = 0; + + if (opt.no_symkey_cache) + nocache = 1; /* Force no symmetric key caching. */ + + if ( !s2k ) + { + log_assert (create && !nocache); + /* This is used for the old rfc1991 mode + * Note: This must match the code in encode.c with opt.rfc1991 set */ + memset (&help_s2k, 0, sizeof (help_s2k)); + s2k = &help_s2k; + s2k->hash_algo = S2K_DIGEST_ALGO; + } + + /* Create a new salt or what else to be filled into the s2k for a + new key. */ + if (create && (s2k->mode == 1 || s2k->mode == 3)) + { + gcry_randomize (s2k->salt, 8, GCRY_STRONG_RANDOM); + if ( s2k->mode == 3 ) + { + /* We delay the encoding until it is really needed. This is + if we are going to dynamically calibrate it, we need to + call out to gpg-agent and that should not be done during + option processing in main(). */ + if (!opt.s2k_count) + opt.s2k_count = encode_s2k_iterations (0); + s2k->count = opt.s2k_count; + } + } + + /* 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]; + + snprintf (buf, sizeof buf, "%d %d %d", + cipher_algo, s2k->mode, s2k->hash_algo ); + write_status_text ( STATUS_NEED_PASSPHRASE_SYM, buf ); + } + + if ( next_pw ) + { + /* Simply return the passphrase we already have in NEXT_PW. */ + pw = next_pw; + next_pw = NULL; + } + else if ( have_static_passphrase () ) + { + /* Return the passphrase we have stored in FD_PASSWD. */ + pw = xmalloc_secure ( strlen(fd_passwd)+1 ); + strcpy ( pw, fd_passwd ); + } + else + { + if (!nocache && (s2k->mode == 1 || s2k->mode == 3)) + { + memset (s2k_cacheidbuf, 0, sizeof s2k_cacheidbuf); + *s2k_cacheidbuf = 'S'; + bin2hex (s2k->salt, 8, s2k_cacheidbuf + 1); + s2k_cacheid = s2k_cacheidbuf; + } + + if (opt.pinentry_mode == PINENTRY_MODE_LOOPBACK) + { + char buf[32]; + + snprintf (buf, sizeof (buf), "%u", 100); + write_status_text (STATUS_INQUIRE_MAXLEN, buf); + } + + /* Divert to the gpg-agent. */ + pw = passphrase_get (create, create && nocache, s2k_cacheid, + create? opt.passphrase_repeat : 0, + tryagain_text, flags, canceled); + if (*canceled) + { + xfree (pw); + write_status( STATUS_CANCELED_BY_USER ); + return NULL; + } + } + + 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 || !*pw) && create) + dek->keylen = 0; + else + { + gpg_error_t err; + + dek->keylen = openpgp_cipher_get_algo_keylen (dek->algo); + if (!(dek->keylen > 0 && dek->keylen <= DIM(dek->key))) + BUG (); + err = gcry_kdf_derive (pw, strlen (pw), + s2k->mode == 3? GCRY_KDF_ITERSALTED_S2K : + s2k->mode == 1? GCRY_KDF_SALTED_S2K : + /* */ GCRY_KDF_SIMPLE_S2K, + s2k->hash_algo, s2k->salt, 8, + S2K_DECODE_COUNT(s2k->count), + dek->keylen, dek->key); + if (err) + { + log_error ("gcry_kdf_derive failed: %s", gpg_strerror (err)); + xfree (pw); + xfree (dek); + write_status( STATUS_MISSING_PASSPHRASE ); + return NULL; + } + } + if (s2k_cacheid) + memcpy (dek->s2k_cacheid, s2k_cacheid, sizeof dek->s2k_cacheid); + xfree(last_pw); + last_pw = pw; + return dek; +} + + +/* Emit the USERID_HINT and the NEED_PASSPHRASE status messages. + MAINKEYID may be NULL. */ +void +emit_status_need_passphrase (ctrl_t ctrl, + u32 *keyid, u32 *mainkeyid, int pubkey_algo) +{ + char buf[50]; + char *us; + + us = get_long_user_id_string (ctrl, keyid); + write_status_text (STATUS_USERID_HINT, us); + xfree (us); + + snprintf (buf, sizeof buf, "%08lX%08lX %08lX%08lX %d 0", + (ulong)keyid[0], + (ulong)keyid[1], + (ulong)(mainkeyid? mainkeyid[0]:keyid[0]), + (ulong)(mainkeyid? mainkeyid[1]:keyid[1]), + pubkey_algo); + + write_status_text (STATUS_NEED_PASSPHRASE, buf); +} + + +/* Return an allocated utf-8 string describing the key PK. If ESCAPED + is true spaces and control characters are percent or plus escaped. + MODE describes the use of the key description; use one of the + FORMAT_KEYDESC_ macros. */ +char * +gpg_format_keydesc (ctrl_t ctrl, PKT_public_key *pk, int mode, int escaped) +{ + char *uid; + size_t uidlen; + const char *algo_name; + const char *timestr; + char *orig_codeset; + char *maink; + char *desc; + const char *prompt; + const char *trailer = ""; + int is_subkey; + + is_subkey = (pk->main_keyid[0] && pk->main_keyid[1] + && pk->keyid[0] != pk->main_keyid[0] + && pk->keyid[1] != pk->main_keyid[1]); + algo_name = openpgp_pk_algo_name (pk->pubkey_algo); + timestr = strtimestamp (pk->timestamp); + uid = get_user_id (ctrl, is_subkey? pk->main_keyid:pk->keyid, &uidlen, NULL); + + orig_codeset = i18n_switchto_utf8 (); + + if (is_subkey) + maink = xtryasprintf (_(" (main key ID %s)"), keystr (pk->main_keyid)); + else + maink = NULL; + + switch (mode) + { + case FORMAT_KEYDESC_NORMAL: + prompt = _("Please enter the passphrase to unlock the" + " OpenPGP secret key:"); + break; + case FORMAT_KEYDESC_IMPORT: + prompt = _("Please enter the passphrase to import the" + " OpenPGP secret key:"); + break; + case FORMAT_KEYDESC_EXPORT: + if (is_subkey) + prompt = _("Please enter the passphrase to export the" + " OpenPGP secret subkey:"); + else + prompt = _("Please enter the passphrase to export the" + " OpenPGP secret key:"); + break; + case FORMAT_KEYDESC_DELKEY: + if (is_subkey) + prompt = _("Do you really want to permanently delete the" + " OpenPGP secret subkey key:"); + else + prompt = _("Do you really want to permanently delete the" + " OpenPGP secret key:"); + trailer = "?"; + break; + default: + prompt = "?"; + break; + } + + desc = xtryasprintf (_("%s\n" + "\"%.*s\"\n" + "%u-bit %s key, ID %s,\n" + "created %s%s.\n%s"), + prompt, + (int)uidlen, uid, + nbits_from_pk (pk), algo_name, + keystr (pk->keyid), timestr, + maink?maink:"", trailer); + xfree (maink); + xfree (uid); + + i18n_switchback (orig_codeset); + + if (escaped) + { + char *tmp = percent_plus_escape (desc); + xfree (desc); + desc = tmp; + } + + return desc; +} diff --git a/g10/photoid.c b/g10/photoid.c new file mode 100644 index 0000000..dbef7d7 --- /dev/null +++ b/g10/photoid.c @@ -0,0 +1,401 @@ +/* photoid.c - photo ID handling code + * Copyright (C) 2001, 2002, 2005, 2006, 2008, 2011 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include +#include +#include +#include +#ifdef _WIN32 +# ifdef HAVE_WINSOCK2_H +# include +# endif +# include +# ifndef VER_PLATFORM_WIN32_WINDOWS +# define VER_PLATFORM_WIN32_WINDOWS 1 +# endif +#endif + +#include "gpg.h" +#include "../common/util.h" +#include "packet.h" +#include "../common/status.h" +#include "exec.h" +#include "keydb.h" +#include "../common/i18n.h" +#include "../common/iobuf.h" +#include "options.h" +#include "main.h" +#include "photoid.h" +#include "../common/ttyio.h" +#include "trustdb.h" + +/* Generate a new photo id packet, or return NULL if canceled. + FIXME: Should we add a duplicates check similar to generate_user_id? */ +PKT_user_id * +generate_photo_id (ctrl_t ctrl, 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; + gpg_err_set_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) + { + 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 (ctrl, uid->attribs, uid->numattribs, pk, uid); + 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 "!ShellExecute 400 %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)) + return "xloadimage -fork -quiet -title 'KeyID 0x%k' stdin"; + else if (!path_access ("display",X_OK)) + return "display -title 'KeyID 0x%k' %i"; + else if (getuid () && !path_access ("xdg-open", X_OK)) + { + /* xdg-open spawns the actual program and exits so we need to + * keep the temp file */ + return "xdg-open %I"; + } + else + return "/bin/true"; +#endif +} +#endif + + +void +show_photos (ctrl_t ctrl, const struct user_attribute *attrs, int count, + PKT_public_key *pk, PKT_user_id *uid) +{ +#ifdef DISABLE_PHOTO_VIEWER + (void)attrs; + (void)count; + (void)pk; + (void)uid; +#else /*!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.validity_info = get_validity_info (ctrl, NULL, pk, uid); + args.validity_string = get_validity_string (ctrl, pk, uid); + namehash_from_uid (uid); + args.namehash = uid->namehash; + + if (pk) + keyid_from_pk (pk, kid); + + es_fflush (es_stdout); + + for(i=0;itempfile_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 /*!DISABLE_PHOTO_VIEWER*/ +} diff --git a/g10/photoid.h b/g10/photoid.h new file mode 100644 index 0000000..fc7ec6f --- /dev/null +++ b/g10/photoid.h @@ -0,0 +1,34 @@ +/* photoid.h + * Copyright (C) 2001, 2002, 2005, 2008 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +/* Photo ID functions */ + +#ifndef _PHOTOID_H_ +#define _PHOTOID_H_ + +#include "packet.h" + +PKT_user_id *generate_photo_id (ctrl_t ctrl, + 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 (ctrl_t ctrl, const struct user_attribute *attrs, int count, + PKT_public_key *pk, PKT_user_id *uid); + +#endif /* !_PHOTOID_H_ */ diff --git a/g10/pkclist.c b/g10/pkclist.c new file mode 100644 index 0000000..fb8b176 --- /dev/null +++ b/g10/pkclist.c @@ -0,0 +1,1721 @@ +/* pkclist.c - create a list of public keys + * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, + * 2008, 2009, 2010 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include +#include +#include +#include +#include + +#include "gpg.h" +#include "options.h" +#include "packet.h" +#include "../common/status.h" +#include "keydb.h" +#include "../common/util.h" +#include "main.h" +#include "trustdb.h" +#include "../common/ttyio.h" +#include "../common/status.h" +#include "photoid.h" +#include "../common/i18n.h" +#include "tofu.h" + +#define CONTROL_D ('D' - 'A' + 1) + +static void +send_status_inv_recp (int reason, const char *name) +{ + char buf[40]; + + snprintf (buf, sizeof buf, "%d ", reason); + write_status_text_and_buffer (STATUS_INV_RECP, buf, + name, strlen (name), + -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) + log_printf ("%s\n", text); + else + log_printf ("code=%02x\n", *p ); + 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: ") ); + es_write_sanitized (log_get_stream(), p, nn, NULL, NULL); + log_printf ("\n"); + 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 (ctrl_t ctrl, PKT_public_key *pk, int mode) +{ + /* Hmmm, this is not so easy because we have to duplicate the code + * used in the trustdb 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_pubkey_byfprint (ctrl, NULL, &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 (ctrl, 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 + */ +#ifndef NO_TRUST_MODELS +static int +do_edit_ownertrust (ctrl_t ctrl, 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 = tdb_get_min_ownertrust (ctrl, pk, 0); + + 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. + + 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")); + print_key_line (ctrl, NULL, pk, 0); + + p = get_user_id_native (ctrl, keyid); + tty_printf (_(" \"%s\"\n"),p); + xfree (p); + + keyblock = get_pubkeyblock (ctrl, 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->flags.revoked) + continue; + if (un->pkt->pkt.user_id->flags.expired) + continue; + /* Only skip textual primaries */ + if (un->pkt->pkt.user_id->flags.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 (ctrl, + un->pkt->pkt.user_id->attribs, + un->pkt->pkt.user_id->numattribs, pk, + un->pkt->pkt.user_id); + + 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 (ctrl, NULL, pk, 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; +} +#endif /*!NO_TRUST_MODELS*/ + + +/* + * Display a menu to change the ownertrust of the key PK (which should + * be a primary key). + * For mode values see do_edit_ownertrust () + */ +#ifndef NO_TRUST_MODELS +int +edit_ownertrust (ctrl_t ctrl, PKT_public_key *pk, int mode ) +{ + unsigned int trust = 0; + int no_help = 0; + + for(;;) + { + switch ( do_edit_ownertrust (ctrl, 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 (ctrl, pk) & TRUST_FLAG_DISABLED; + update_ownertrust (ctrl, pk, trust ); + return 1; + default: + return 0; + } + } +} +#endif /*!NO_TRUST_MODELS*/ + + +/**************** + * 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 through */ + 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 */ + + case TRUST_NEVER: + /* This can be returned by TOFU, which can return negative + assertions. */ + log_info(_("%s: This key is bad! It has been marked as untrusted!\n"), + keystr_from_pk(pk)); + return 0; /* no */ + } + + return 1; /*NOTREACHED*/ +} + + +/**************** + * wrapper around do_we_trust, so we can ask whether to use the + * key anyway. + */ +static int +do_we_trust_pre (ctrl_t ctrl, PKT_public_key *pk, unsigned int trustlevel ) +{ + int rc; + + rc = do_we_trust( pk, trustlevel ); + + if( !opt.batch && !rc ) + { + print_pubkey_info (ctrl, NULL,pk); + print_fingerprint (ctrl, NULL, pk, 2); + tty_printf("\n"); + + if ((trustlevel & TRUST_MASK) == TRUST_NEVER) + tty_printf( + _("This key is bad! It has been marked as untrusted! If you\n" + "*really* know what you are doing, you may answer the next\n" + "question with yes.\n")); + else + 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 (is_status_enabled ()) + { + u32 kid[2]; + char *hint_str; + + keyid_from_pk (pk, kid); + hint_str = get_long_user_id_string (ctrl, kid); + write_status_text ( STATUS_USERID_HINT, hint_str ); + xfree (hint_str); + } + + 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; +} + + +/* Write a TRUST_foo status line inclduing the validation model. */ +static void +write_trust_status (int statuscode, int trustlevel) +{ +#ifdef NO_TRUST_MODELS + write_status (statuscode); +#else /* NO_TRUST_MODELS */ + int tm; + + /* For the combined tofu+pgp method, we return the trust model which + * was responsible for the trustlevel. */ + if (opt.trust_model == TM_TOFU_PGP) + tm = (trustlevel & TRUST_FLAG_TOFU_BASED)? TM_TOFU : TM_PGP; + else + tm = opt.trust_model; + write_status_strings (statuscode, "0 ", trust_model_string (tm), NULL); +#endif /* NO_TRUST_MODELS */ +} + + +/**************** + * Check whether we can trust this signature. + * Returns an error code if we should not trust this signature. + */ +int +check_signatures_trust (ctrl_t ctrl, PKT_signature *sig) +{ + PKT_public_key *pk = xmalloc_clear( sizeof *pk ); + unsigned int trustlevel = TRUST_UNKNOWN; + int rc=0; + + rc = get_pubkey_for_sig (ctrl, pk, sig, NULL); + if (rc) + { /* this should not happen */ + log_error("Ooops; the key vanished - can't check the trust\n"); + rc = GPG_ERR_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 (ctrl, NULL, pk, 1); + goto leave; + } + + if(pk->flags.maybe_revoked && !pk->flags.revoked) + log_info(_("WARNING: this key might be revoked (revocation key" + " not present)\n")); + + trustlevel = get_validity (ctrl, NULL, pk, NULL, sig, 1); + + if ( (trustlevel & TRUST_FLAG_REVOKED) ) + { + write_status( STATUS_KEYREVOKED ); + if(pk->flags.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 (ctrl, 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 (ctrl, 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 (ctrl, 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 (ctrl, NULL, pk, 1); + break; + + default: + log_error ("invalid trustlevel %u returned from validation layer\n", + trustlevel); + /* fall through */ + case TRUST_UNKNOWN: + case TRUST_UNDEFINED: + write_trust_status (STATUS_TRUST_UNDEFINED, trustlevel); + 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 (ctrl, NULL, pk, 1); + break; + + case TRUST_NEVER: + /* This level can be returned by TOFU, which supports negative + * assertions. */ + write_trust_status (STATUS_TRUST_NEVER, trustlevel); + 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 (ctrl, NULL, pk, 1); + rc = gpg_error (GPG_ERR_BAD_SIGNATURE); + break; + + case TRUST_MARGINAL: + write_trust_status (STATUS_TRUST_MARGINAL, trustlevel); + 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 (ctrl, NULL, pk, 1); + break; + + case TRUST_FULLY: + write_trust_status (STATUS_TRUST_FULLY, trustlevel); + if (opt.with_fingerprint) + print_fingerprint (ctrl, NULL, pk, 1); + break; + + case TRUST_ULTIMATE: + write_trust_status (STATUS_TRUST_ULTIMATE, trustlevel); + if (opt.with_fingerprint) + print_fingerprint (ctrl, NULL, pk, 1); + break; + } + + leave: + free_public_key( pk ); + return rc; +} + + +void +release_pk_list (pk_list_t 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 recipient if there is any + * Fixme: We don't distinguish between malloc failure and no-default-recipient. + */ +static char * +default_recipient (ctrl_t ctrl) +{ + PKT_public_key *pk; + char *result; + + if (opt.def_recipient) + return xtrystrdup (opt.def_recipient); + + if (!opt.def_recipient_self) + return NULL; + pk = xtrycalloc (1, sizeof *pk ); + if (!pk) + return NULL; + if (get_seckey_default (ctrl, pk)) + { + free_public_key (pk); + return NULL; + } + result = hexfingerprint (pk, NULL, 0); + free_public_key (pk); + return result; +} + + +static int +expand_id(const char *id,strlist_t *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_t 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_t +expand_group (strlist_t input) +{ + strlist_t output = NULL; + strlist_t sl, rover; + + for (rover = input; rover; rover = rover->next) + if (!(rover->flags & PK_LIST_FROM_FILE) + && !expand_id(rover->d,&output,rover->flags)) + { + /* Didn't find any groups, so use the existing string */ + sl=add_to_strlist(&output,rover->d); + sl->flags=rover->flags; + } + + return output; +} + + +/* Helper for build_pk_list to find and check one key. This helper is + * also used directly in server mode by the RECIPIENTS command. On + * success the new key is added to PK_LIST_ADDR. NAME is the user id + * of the key. USE the requested usage and a set MARK_HIDDEN will + * mark the key in the updated list as a hidden recipient. If + * FROM_FILE is true, NAME is not a user ID but the name of a file + * holding a key. */ +gpg_error_t +find_and_check_key (ctrl_t ctrl, const char *name, unsigned int use, + int mark_hidden, int from_file, pk_list_t *pk_list_addr) +{ + int rc; + PKT_public_key *pk; + KBNODE keyblock = NULL; + + if (!name || !*name) + return gpg_error (GPG_ERR_INV_USER_ID); + + pk = xtrycalloc (1, sizeof *pk); + if (!pk) + return gpg_error_from_syserror (); + pk->req_usage = use; + + if (from_file) + rc = get_pubkey_fromfile (ctrl, pk, name); + else + rc = get_best_pubkey_byname (ctrl, GET_PUBKEY_NORMAL, + NULL, pk, name, &keyblock, 0); + if (rc) + { + int code; + + /* Key not found or other error. */ + log_error (_("%s: skipped: %s\n"), name, gpg_strerror (rc) ); + switch (gpg_err_code (rc)) + { + case GPG_ERR_NO_SECKEY: + case GPG_ERR_NO_PUBKEY: code = 1; break; + case GPG_ERR_INV_USER_ID: code = 14; break; + default: code = 0; break; + } + send_status_inv_recp (code, name); + free_public_key (pk); + return rc; + } + + rc = openpgp_pk_test_algo2 (pk->pubkey_algo, use); + if (rc) + { + /* Key found but not usable for us (e.g. sign-only key). */ + release_kbnode (keyblock); + send_status_inv_recp (3, name); /* Wrong key usage */ + log_error (_("%s: skipped: %s\n"), name, gpg_strerror (rc) ); + free_public_key (pk); + return rc; + } + + /* Key found and usable. Check validity. */ + if (!from_file) + { + int trustlevel; + + trustlevel = get_validity (ctrl, keyblock, pk, pk->user_id, NULL, 1); + release_kbnode (keyblock); + if ( (trustlevel & TRUST_FLAG_DISABLED) ) + { + /* Key has been disabled. */ + send_status_inv_recp (13, name); + log_info (_("%s: skipped: public key is disabled\n"), name); + free_public_key (pk); + return GPG_ERR_UNUSABLE_PUBKEY; + } + + if ( !do_we_trust_pre (ctrl, pk, trustlevel) ) + { + /* We don't trust this key. */ + send_status_inv_recp (10, name); + free_public_key (pk); + return GPG_ERR_UNUSABLE_PUBKEY; + } + } + + /* Skip the actual key if the key is already present in the + list. */ + if (!key_present_in_pk_list (*pk_list_addr, pk)) + { + if (!opt.quiet) + log_info (_("%s: skipped: public key already present\n"), name); + free_public_key (pk); + } + else + { + pk_list_t r; + + r = xtrymalloc (sizeof *r); + if (!r) + { + rc = gpg_error_from_syserror (); + free_public_key (pk); + return rc; + } + r->pk = pk; + r->next = *pk_list_addr; + r->flags = mark_hidden? 1:0; + *pk_list_addr = r; + } + + return 0; +} + + + +/* 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: + * + * - PK_LIST_ENCRYPT_TO :: This is an encrypt-to recipient. + * - PK_LIST_HIDDEN :: This is a hidden recipient. + * - PK_LIST_FROM_FILE :: The argument is a file with a key. + * + * 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 (ctrl_t ctrl, strlist_t rcpts, PK_LIST *ret_pk_list) +{ + PK_LIST pk_list = NULL; + PKT_public_key *pk=NULL; + int rc=0; + int any_recipients=0; + strlist_t rov,remusr; + char *def_rec = NULL; + char pkstrbuf[PUBKEY_STRING_SIZE]; + + /* Try to expand groups if any have been defined. */ + if (opt.grouplist) + remusr = expand_group (rcpts); + else + remusr = rcpts; + + /* XXX: Change this function to use get_pubkeys instead of + get_pubkey_byname to detect ambiguous key specifications and warn + about duplicate keyblocks. For ambiguous key specifications on + the command line or provided interactively, prompt the user to + select the best key. If a key specification is ambiguous and we + are in batch mode, die. */ + + if (opt.encrypt_to_default_key) + { + static int warned; + + const char *default_key = parse_def_secret_key (ctrl); + if (default_key) + { + PK_LIST r = xmalloc_clear (sizeof *r); + + r->pk = xmalloc_clear (sizeof *r->pk); + r->pk->req_usage = PUBKEY_USAGE_ENC; + + rc = get_pubkey_byname (ctrl, GET_PUBKEY_NO_AKL, + NULL, r->pk, default_key, NULL, NULL, 0); + if (rc) + { + xfree (r->pk); + xfree (r); + + log_error (_("can't encrypt to '%s'\n"), default_key); + if (!opt.quiet) + log_info (_("(check argument of option '%s')\n"), + "--default-key"); + } + else + { + r->next = pk_list; + r->flags = 0; + pk_list = r; + } + } + else if (opt.def_secret_key) + { + if (! warned) + log_info (_("option '%s' given, but no valid default keys given\n"), + "--encrypt-to-default-key"); + warned = 1; + } + else + { + if (! warned) + log_info (_("option '%s' given, but option '%s' not given\n"), + "--encrypt-to-default-key", "--default-key"); + warned = 1; + } + } + + /* 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 & PK_LIST_ENCRYPT_TO) ) + { + /* 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 & PK_LIST_HIDDEN) && (PGP6 || PGP7 || PGP8)) + { + log_info(_("option '%s' may not be used in %s mode\n"), + "--hidden-recipient", + gnupg_compliance_option_string (opt.compliance)); + + compliance_failure(); + } + } + else if (!opt.no_encrypt_to) + { + /* --encrypt-to has not been disabled. Check this + encrypt-to key. */ + pk = xmalloc_clear( sizeof *pk ); + pk->req_usage = PUBKEY_USAGE_ENC; + + /* We explicitly allow encrypt-to to an disabled key; thus + we pass 1 for the second last argument and 1 as the last + argument to disable AKL. */ + if ((rc = get_pubkey_byname (ctrl, GET_PUBKEY_NO_AKL, + NULL, pk, rov->d, NULL, NULL, 1))) + { + free_public_key ( pk ); pk = NULL; + log_error (_("%s: skipped: %s\n"), rov->d, gpg_strerror (rc) ); + send_status_inv_recp (0, rov->d); + goto fail; + } + else if ( !(rc=openpgp_pk_test_algo2 (pk->pubkey_algo, + PUBKEY_USAGE_ENC)) ) + { + /* 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; + if (!opt.quiet) + 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&PK_LIST_HIDDEN)?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&PK_LIST_ENCRYPT_TO) && (PGP6 || PGP7 || PGP8)) + { + log_info(_("option '%s' may not be used in %s mode\n"), + "--hidden-encrypt-to", + gnupg_compliance_option_string (opt.compliance)); + + compliance_failure(); + } + } + } + else + { + /* The public key is not usable for encryption. */ + free_public_key( pk ); pk = NULL; + log_error(_("%s: skipped: %s\n"), rov->d, gpg_strerror (rc) ); + send_status_inv_recp (3, rov->d); /* Wrong key usage */ + 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_t backlog = NULL; + + if (pk_list) + any_recipients = 1; + def_rec = default_recipient(ctrl); + 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 = strlist_pop (&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 ("%s/%s %s \"", + pubkey_string (iter->pk, + pkstrbuf, sizeof pkstrbuf), + 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 (ctrl, keyid, &n, NULL); + 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 occurred. The code above will + then list all expanded keys. */ + if (expand_id(answer,&backlog,0)) + continue; + + /* Get and check key for the current name. */ + free_public_key (pk); + pk = xmalloc_clear( sizeof *pk ); + pk->req_usage = PUBKEY_USAGE_ENC; + rc = get_pubkey_byname (ctrl, GET_PUBKEY_NORMAL, + NULL, pk, answer, NULL, NULL, 0); + if (rc) + tty_printf(_("No such user ID.\n")); + else if ( !(rc=openpgp_pk_test_algo2 (pk->pubkey_algo, + PUBKEY_USAGE_ENC)) ) + { + 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 (ctrl, NULL, pk, pk->user_id, NULL, 1); + if ( (trustlevel & TRUST_FLAG_DISABLED) ) + { + tty_printf (_("Public key is disabled.\n") ); + } + else if ( do_we_trust_pre (ctrl, 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(ctrl)) ) + { + /* We are in batch mode and have only a default recipient. */ + pk = xmalloc_clear( sizeof *pk ); + pk->req_usage = PUBKEY_USAGE_ENC; + + /* The default recipient is allowed to be disabled; thus pass 1 + as second last argument. We also don't want an AKL. */ + rc = get_pubkey_byname (ctrl, GET_PUBKEY_NO_AKL, + NULL, pk, def_rec, NULL, NULL, 1); + if (rc) + log_error(_("unknown default recipient \"%s\"\n"), def_rec ); + else if ( !(rc=openpgp_pk_test_algo2(pk->pubkey_algo, + PUBKEY_USAGE_ENC)) ) + { + /* 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 & PK_LIST_ENCRYPT_TO) ) + continue; /* encrypt-to keys are already handled. */ + + rc = find_and_check_key (ctrl, remusr->d, PUBKEY_USAGE_ENC, + !!(remusr->flags&PK_LIST_HIDDEN), + !!(remusr->flags&PK_LIST_FROM_FILE), + &pk_list); + if (rc) + goto fail; + any_recipients = 1; + } + } + + if ( !rc && !any_recipients ) + { + log_error(_("no valid addressees\n")); + write_status_text (STATUS_NO_RECP, "0"); + rc = GPG_ERR_NO_USER_ID; + } + +#ifdef USE_TOFU + if (! rc && (opt.trust_model == TM_TOFU_PGP || opt.trust_model == TM_TOFU)) + { + PK_LIST iter; + for (iter = pk_list; iter; iter = iter->next) + { + int rc2; + + /* Note: we already resolved any conflict when looking up + the key. Don't annoy the user again if she selected + accept once. */ + rc2 = tofu_register_encryption (ctrl, iter->pk, NULL, 0); + if (rc2) + log_info ("WARNING: Failed to register encryption to %s" + " with TOFU engine\n", + keystr (pk_main_keyid (iter->pk))); + else if (DBG_TRUST) + log_debug ("Registered encryption to %s with TOFU DB.\n", + keystr (pk_main_keyid (iter->pk))); + } + } +#endif /*USE_TOFU*/ + + 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 struct 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 && !openpgp_cipher_test_algo ( algo ); + } + else if( preftype == PREFTYPE_HASH ) + { + if (hint && hint->digest_length) + { + unsigned int n = gcry_md_get_algo_dlen (algo); + + if (hint->exact) + { + /* For example ECDSA requires an exact hash value so + * that we do not truncate. For DSA we allow truncation + * and thus exact is not set. */ + if (hint->digest_length != n) + return 0; + } + else 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 > n) + return 0; + } + else if (hint->digest_length != n) + 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 && !openpgp_md_test_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 struct pref_hint *hint) +{ + PK_LIST pkr; + u32 bits[8]; + const prefitem_t *prefs; + int result=-1,i; + u16 scores[256]; + + if( !pk_list ) + return -1; + + memset(bits,0xFF,sizeof(bits)); + memset(scores,0,sizeof(scores)); + + for( pkr = pk_list; pkr; pkr = pkr->next ) + { + u32 mask[8]; + int rank=1,implicit=-1; + + memset(mask,0,sizeof(mask)); + + switch(preftype) + { + case PREFTYPE_SYM: + /* IDEA is implicitly there for v3 keys with v3 selfsigs if + --pgp2 mode is on. This was a 2440 thing that was + dropped from 4880 but is still relevant to GPG's 1991 + support. All this doesn't mean IDEA is actually + available, of course. + + Because "de-vs" compliance will soon not anymore allow + 3DES it does not make sense to assign 3DES as implicit + algorithm. Instead it is better to use AES-128 as + implicit algorithm here. */ + if (opt.compliance == CO_DE_VS) + implicit = CIPHER_ALGO_AES; + else + implicit=CIPHER_ALGO_3DES; + + break; + + case PREFTYPE_HASH: + /* While I am including this code for completeness, note + that currently --pgp2 mode locks the hash at MD5, so this + code 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 + + Because "de-vs" compliance does not allow SHA-1 it does + not make sense to assign SHA-1 as implicit algorithm. + Instead it is better to use SHA-256 as implicit algorithm + (which will be the case for rfc4880bis anyway). */ + + if (opt.compliance == CO_DE_VS) + implicit = DIGEST_ALGO_SHA256; + else + implicit = DIGEST_ALGO_SHA1; + + break; + + case PREFTYPE_ZIP: + /* Uncompressed is always an option. */ + implicit=COMPRESS_ALGO_NONE; + } + + if (pkr->pk->user_id) /* selected by user ID */ + prefs = pkr->pk->user_id->prefs; + else + prefs = pkr->pk->prefs; + + if( prefs ) + { + for (i=0; prefs[i].type; i++ ) + { + if( prefs[i].type == preftype ) + { + /* Make sure all scores don't add up past 0xFFFF + (and roll around) */ + if(rank+scores[prefs[i].value]<=0xFFFF) + scores[prefs[i].value]+=rank; + else + scores[prefs[i].value]=0xFFFF; + + mask[prefs[i].value/32] |= 1<<(prefs[i].value%32); + + rank++; + + /* We saw the implicit algorithm, so we don't need + tack it on the end ourselves. */ + if(implicit==prefs[i].value) + implicit=-1; + } + } + } + + if(rank==1 && preftype==PREFTYPE_ZIP) + { + /* If the compression preferences are not present, they are + assumed to be ZIP, Uncompressed (RFC4880:13.3.1) */ + scores[1]=1; /* ZIP is first choice */ + scores[0]=2; /* Uncompressed is second choice */ + mask[0]|=3; + } + + /* If the key didn't have the implicit algorithm listed + explicitly, add it here at the tail of the list. */ + if(implicit>-1) + { + scores[implicit]+=rank; + mask[implicit/32] |= 1<<(implicit%32); + } + + for(i=0;i<8;i++) + bits[i]&=mask[i]; + } + + /* We've now scored all of the algorithms, and the usable ones have + bits set. Let's pick the winner. */ + + /* The caller passed us a request. Can we use it? */ + if(request>-1 && (bits[request/32] & (1<<(request%32))) && + algo_available(preftype,request,hint)) + result=request; + + if(result==-1) + { + /* If we have personal prefs set, use them. */ + prefs=NULL; + 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(i=0; prefs[i].type; i++ ) + { + if(bits[prefs[i].value/32] & (1<<(prefs[i].value%32)) + && algo_available( preftype, prefs[i].value, hint)) + { + result = prefs[i].value; + break; + } + } + } + + if(result==-1) + { + unsigned int best=-1; + + /* At this point, we have not selected an algorithm due to a + special request or via personal prefs. Pick the highest + ranked algorithm (i.e. the one with the lowest score). */ + + if(preftype==PREFTYPE_HASH && scores[DIGEST_ALGO_MD5]) + { + /* "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." (RFC4880:14). If any + other hash algorithm is available, pretend that MD5 isn't. + Note that if the user intentionally chose MD5 by putting it + in their personal prefs, then we do what the user said (as we + never reach this code). */ + + for(i=DIGEST_ALGO_MD5+1;i<256;i++) + if(scores[i]) + { + scores[DIGEST_ALGO_MD5]=0; + break; + } + } + + for(i=0;i<256;i++) + { + /* Note the '<' here. This means in case of a tie, we will + favor the lower algorithm number. We have a choice + between the lower number (probably an older algorithm + with more time in use), or the higher number (probably a + newer algorithm with less time in use). Older is + probably safer here, even though the newer algorithms + tend to be "stronger". */ + if(scores[i] && scores[i]next) + { + int mdc; + + if (pkr->pk->user_id) /* selected by user ID */ + mdc = pkr->pk->user_id->flags.mdc; + else + mdc = pkr->pk->flags.mdc; + if (!mdc) + return 0; /* At least one recipient does not support it. */ + } + return 1; /* Can be used. */ +} + + +/* Print a warning for all keys in PK_LIST missing the MDC feature. */ +void +warn_missing_mdc_from_pklist (PK_LIST pk_list) +{ + PK_LIST pkr; + + 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->flags.mdc; + if (!mdc) + log_info (_("Note: key %s has no %s feature\n"), + keystr_from_pk (pkr->pk), "MDC"); + } +} + +void +warn_missing_aes_from_pklist (PK_LIST pk_list) +{ + PK_LIST pkr; + + for (pkr = pk_list; pkr; pkr = pkr->next) + { + const prefitem_t *prefs; + int i; + int gotit = 0; + + prefs = pkr->pk->user_id? pkr->pk->user_id->prefs : pkr->pk->prefs; + if (prefs) + { + for (i=0; !gotit && prefs[i].type; i++ ) + if (prefs[i].type == PREFTYPE_SYM + && prefs[i].value == CIPHER_ALGO_AES) + gotit++; + } + if (!gotit) + log_info (_("Note: key %s has no preference for %s\n"), + keystr_from_pk (pkr->pk), "AES"); + } +} diff --git a/g10/pkglue.c b/g10/pkglue.c new file mode 100644 index 0000000..e053657 --- /dev/null +++ b/g10/pkglue.c @@ -0,0 +1,453 @@ +/* pkglue.c - public key operations glue code + * Copyright (C) 2000, 2003, 2010 Free Software Foundation, Inc. + * Copyright (C) 2014 Werner Koch + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include +#include +#include +#include +#include + +#include "gpg.h" +#include "../common/util.h" +#include "pkglue.h" +#include "main.h" +#include "options.h" + +/* FIXME: Better change the function name because mpi_ is used by + gcrypt macros. */ +gcry_mpi_t +get_mpi_from_sexp (gcry_sexp_t sexp, const char *item, int mpifmt) +{ + gcry_sexp_t list; + gcry_mpi_t data; + + list = gcry_sexp_find_token (sexp, item, 0); + log_assert (list); + data = gcry_sexp_nth_mpi (list, 1, mpifmt); + log_assert (data); + gcry_sexp_release (list); + return data; +} + + +static byte * +get_data_from_sexp (gcry_sexp_t sexp, const char *item, size_t *r_size) +{ + gcry_sexp_t list; + size_t valuelen; + const char *value; + byte *v; + + if (DBG_CRYPTO) + log_printsexp ("get_data_from_sexp:", sexp); + + list = gcry_sexp_find_token (sexp, item, 0); + log_assert (list); + value = gcry_sexp_nth_data (list, 1, &valuelen); + log_assert (value); + v = xtrymalloc (valuelen); + memcpy (v, value, valuelen); + gcry_sexp_release (list); + *r_size = valuelen; + return v; +} + + +/**************** + * Emulate our old PK interface here - sometime in the future we might + * change the internal design to directly fit to libgcrypt. + */ +int +pk_verify (pubkey_algo_t pkalgo, gcry_mpi_t hash, + gcry_mpi_t *data, gcry_mpi_t *pkey) +{ + gcry_sexp_t s_sig, s_hash, s_pkey; + int rc; + unsigned int neededfixedlen = 0; + + /* Make a sexp from pkey. */ + if (pkalgo == PUBKEY_ALGO_DSA) + { + rc = gcry_sexp_build (&s_pkey, NULL, + "(public-key(dsa(p%m)(q%m)(g%m)(y%m)))", + pkey[0], pkey[1], pkey[2], pkey[3]); + } + else if (pkalgo == PUBKEY_ALGO_ELGAMAL_E || pkalgo == PUBKEY_ALGO_ELGAMAL) + { + rc = gcry_sexp_build (&s_pkey, NULL, + "(public-key(elg(p%m)(g%m)(y%m)))", + pkey[0], pkey[1], pkey[2]); + } + else if (pkalgo == PUBKEY_ALGO_RSA || pkalgo == PUBKEY_ALGO_RSA_S) + { + rc = gcry_sexp_build (&s_pkey, NULL, + "(public-key(rsa(n%m)(e%m)))", pkey[0], pkey[1]); + } + else if (pkalgo == PUBKEY_ALGO_ECDSA) + { + char *curve = openpgp_oid_to_str (pkey[0]); + if (!curve) + rc = gpg_error_from_syserror (); + else + { + rc = gcry_sexp_build (&s_pkey, NULL, + "(public-key(ecdsa(curve %s)(q%m)))", + curve, pkey[1]); + xfree (curve); + } + } + else if (pkalgo == PUBKEY_ALGO_EDDSA) + { + char *curve = openpgp_oid_to_str (pkey[0]); + if (!curve) + rc = gpg_error_from_syserror (); + else + { + rc = gcry_sexp_build (&s_pkey, NULL, + "(public-key(ecc(curve %s)" + "(flags eddsa)(q%m)))", + curve, pkey[1]); + xfree (curve); + } + + if (openpgp_oid_is_ed25519 (pkey[0])) + neededfixedlen = 256 / 8; + } + else + return GPG_ERR_PUBKEY_ALGO; + + if (rc) + BUG (); /* gcry_sexp_build should never fail. */ + + /* Put hash into a S-Exp s_hash. */ + if (pkalgo == PUBKEY_ALGO_EDDSA) + { + if (gcry_sexp_build (&s_hash, NULL, + "(data(flags eddsa)(hash-algo sha512)(value %m))", + hash)) + BUG (); /* gcry_sexp_build should never fail. */ + } + else + { + if (gcry_sexp_build (&s_hash, NULL, "%m", hash)) + BUG (); /* gcry_sexp_build should never fail. */ + } + + /* Put data into a S-Exp s_sig. */ + s_sig = NULL; + if (pkalgo == PUBKEY_ALGO_DSA) + { + if (!data[0] || !data[1]) + rc = gpg_error (GPG_ERR_BAD_MPI); + else + rc = gcry_sexp_build (&s_sig, NULL, + "(sig-val(dsa(r%m)(s%m)))", data[0], data[1]); + } + else if (pkalgo == PUBKEY_ALGO_ECDSA) + { + if (!data[0] || !data[1]) + rc = gpg_error (GPG_ERR_BAD_MPI); + else + rc = gcry_sexp_build (&s_sig, NULL, + "(sig-val(ecdsa(r%m)(s%m)))", data[0], data[1]); + } + else if (pkalgo == PUBKEY_ALGO_EDDSA) + { + gcry_mpi_t r = data[0]; + gcry_mpi_t s = data[1]; + size_t rlen, slen, n; /* (bytes) */ + char buf[64]; + + log_assert (neededfixedlen <= sizeof buf); + + if (!r || !s) + rc = gpg_error (GPG_ERR_BAD_MPI); + else if ((rlen = (gcry_mpi_get_nbits (r)+7)/8) > neededfixedlen || !rlen) + rc = gpg_error (GPG_ERR_BAD_MPI); + else if ((slen = (gcry_mpi_get_nbits (s)+7)/8) > neededfixedlen || !slen) + rc = gpg_error (GPG_ERR_BAD_MPI); + else + { + /* We need to fixup the length in case of leading zeroes. + * OpenPGP does not allow leading zeroes and the parser for + * the signature packet has no information on the use curve, + * thus we need to do it here. We won't do it for opaque + * MPIs under the assumption that they are known to be fine; + * we won't see them here anyway but the check is anyway + * required. Fixme: A nifty feature for gcry_sexp_build + * would be a format to left pad the value (e.g. "%*M"). */ + rc = 0; + + if (rlen < neededfixedlen + && !gcry_mpi_get_flag (r, GCRYMPI_FLAG_OPAQUE) + && !(rc=gcry_mpi_print (GCRYMPI_FMT_USG, buf, sizeof buf, &n, r))) + { + log_assert (n < neededfixedlen); + memmove (buf + (neededfixedlen - n), buf, n); + memset (buf, 0, neededfixedlen - n); + r = gcry_mpi_set_opaque_copy (NULL, buf, neededfixedlen * 8); + } + if (slen < neededfixedlen + && !gcry_mpi_get_flag (s, GCRYMPI_FLAG_OPAQUE) + && !(rc=gcry_mpi_print (GCRYMPI_FMT_USG, buf, sizeof buf, &n, s))) + { + log_assert (n < neededfixedlen); + memmove (buf + (neededfixedlen - n), buf, n); + memset (buf, 0, neededfixedlen - n); + s = gcry_mpi_set_opaque_copy (NULL, buf, neededfixedlen * 8); + } + + if (!rc) + rc = gcry_sexp_build (&s_sig, NULL, + "(sig-val(eddsa(r%M)(s%M)))", r, s); + + if (r != data[0]) + gcry_mpi_release (r); + if (s != data[1]) + gcry_mpi_release (s); + } + } + else if (pkalgo == PUBKEY_ALGO_ELGAMAL || pkalgo == PUBKEY_ALGO_ELGAMAL_E) + { + if (!data[0] || !data[1]) + rc = gpg_error (GPG_ERR_BAD_MPI); + else + rc = gcry_sexp_build (&s_sig, NULL, + "(sig-val(elg(r%m)(s%m)))", data[0], data[1]); + } + else if (pkalgo == PUBKEY_ALGO_RSA || pkalgo == PUBKEY_ALGO_RSA_S) + { + if (!data[0]) + rc = gpg_error (GPG_ERR_BAD_MPI); + else + rc = gcry_sexp_build (&s_sig, NULL, "(sig-val(rsa(s%m)))", data[0]); + } + else + BUG (); + + if (!rc) + rc = gcry_pk_verify (s_sig, s_hash, s_pkey); + + gcry_sexp_release (s_sig); + gcry_sexp_release (s_hash); + gcry_sexp_release (s_pkey); + return rc; +} + + + + +/**************** + * Emulate our old PK interface here - sometime in the future we might + * change the internal design to directly fit to libgcrypt. + * PK is only required to compute the fingerprint for ECDH. + */ +int +pk_encrypt (pubkey_algo_t algo, gcry_mpi_t *resarr, gcry_mpi_t data, + PKT_public_key *pk, gcry_mpi_t *pkey) +{ + gcry_sexp_t s_ciph = NULL; + gcry_sexp_t s_data = NULL; + gcry_sexp_t s_pkey = NULL; + int rc; + + /* Make a sexp from pkey. */ + if (algo == PUBKEY_ALGO_ELGAMAL || algo == PUBKEY_ALGO_ELGAMAL_E) + { + rc = gcry_sexp_build (&s_pkey, NULL, + "(public-key(elg(p%m)(g%m)(y%m)))", + pkey[0], pkey[1], pkey[2]); + /* Put DATA into a simplified S-expression. */ + if (!rc) + rc = gcry_sexp_build (&s_data, NULL, "%m", data); + } + else if (algo == PUBKEY_ALGO_RSA || algo == PUBKEY_ALGO_RSA_E) + { + rc = gcry_sexp_build (&s_pkey, NULL, + "(public-key(rsa(n%m)(e%m)))", + pkey[0], pkey[1]); + /* Put DATA into a simplified S-expression. */ + if (!rc) + rc = gcry_sexp_build (&s_data, NULL, "%m", data); + } + else if (algo == PUBKEY_ALGO_ECDH) + { + gcry_mpi_t k; + + rc = pk_ecdh_generate_ephemeral_key (pkey, &k); + if (!rc) + { + char *curve; + + curve = openpgp_oid_to_str (pkey[0]); + if (!curve) + rc = gpg_error_from_syserror (); + else + { + int with_djb_tweak_flag = openpgp_oid_is_cv25519 (pkey[0]); + + /* Now use the ephemeral secret to compute the shared point. */ + rc = gcry_sexp_build (&s_pkey, NULL, + with_djb_tweak_flag ? + "(public-key(ecdh(curve%s)(flags djb-tweak)(q%m)))" + : "(public-key(ecdh(curve%s)(q%m)))", + curve, pkey[1]); + xfree (curve); + /* Put K into a simplified S-expression. */ + if (!rc) + rc = gcry_sexp_build (&s_data, NULL, "%m", k); + } + gcry_mpi_release (k); + } + } + else + rc = gpg_error (GPG_ERR_PUBKEY_ALGO); + + /* Pass it to libgcrypt. */ + if (!rc) + rc = gcry_pk_encrypt (&s_ciph, s_data, s_pkey); + + gcry_sexp_release (s_data); + gcry_sexp_release (s_pkey); + + if (rc) + ; + else if (algo == PUBKEY_ALGO_ECDH) + { + gcry_mpi_t public, result; + byte fp[MAX_FINGERPRINT_LEN]; + size_t fpn; + byte *shared; + size_t nshared; + + /* Get the shared point and the ephemeral public key. */ + shared = get_data_from_sexp (s_ciph, "s", &nshared); + if (!shared) + { + rc = gpg_error_from_syserror (); + goto leave; + } + public = get_mpi_from_sexp (s_ciph, "e", GCRYMPI_FMT_USG); + gcry_sexp_release (s_ciph); + s_ciph = NULL; + if (DBG_CRYPTO) + { + log_debug ("ECDH ephemeral key:"); + gcry_mpi_dump (public); + log_printf ("\n"); + } + + result = NULL; + fingerprint_from_pk (pk, fp, &fpn); + if (fpn != 20) + rc = gpg_error (GPG_ERR_INV_LENGTH); + else + rc = pk_ecdh_encrypt_with_shared_point (1 /*=encrypton*/, + shared, nshared, + fp, data, pkey, &result); + xfree (shared); + if (!rc) + { + resarr[0] = public; + resarr[1] = result; + } + else + { + gcry_mpi_release (public); + gcry_mpi_release (result); + } + } + else /* Elgamal or RSA case. */ + { /* Fixme: Add better error handling or make gnupg use + S-expressions directly. */ + resarr[0] = get_mpi_from_sexp (s_ciph, "a", GCRYMPI_FMT_USG); + if (!is_RSA (algo)) + resarr[1] = get_mpi_from_sexp (s_ciph, "b", GCRYMPI_FMT_USG); + } + + leave: + gcry_sexp_release (s_ciph); + return rc; +} + + +/* Check whether SKEY is a suitable secret key. */ +int +pk_check_secret_key (pubkey_algo_t pkalgo, gcry_mpi_t *skey) +{ + gcry_sexp_t s_skey; + int rc; + + if (pkalgo == PUBKEY_ALGO_DSA) + { + rc = gcry_sexp_build (&s_skey, NULL, + "(private-key(dsa(p%m)(q%m)(g%m)(y%m)(x%m)))", + skey[0], skey[1], skey[2], skey[3], skey[4]); + } + else if (pkalgo == PUBKEY_ALGO_ELGAMAL || pkalgo == PUBKEY_ALGO_ELGAMAL_E) + { + rc = gcry_sexp_build (&s_skey, NULL, + "(private-key(elg(p%m)(g%m)(y%m)(x%m)))", + skey[0], skey[1], skey[2], skey[3]); + } + else if (is_RSA (pkalgo)) + { + rc = gcry_sexp_build (&s_skey, NULL, + "(private-key(rsa(n%m)(e%m)(d%m)(p%m)(q%m)(u%m)))", + skey[0], skey[1], skey[2], skey[3], skey[4], + skey[5]); + } + else if (pkalgo == PUBKEY_ALGO_ECDSA || pkalgo == PUBKEY_ALGO_ECDH) + { + char *curve = openpgp_oid_to_str (skey[0]); + if (!curve) + rc = gpg_error_from_syserror (); + else + { + rc = gcry_sexp_build (&s_skey, NULL, + "(private-key(ecc(curve%s)(q%m)(d%m)))", + curve, skey[1], skey[2]); + xfree (curve); + } + } + else if (pkalgo == PUBKEY_ALGO_EDDSA) + { + char *curve = openpgp_oid_to_str (skey[0]); + if (!curve) + rc = gpg_error_from_syserror (); + else + { + rc = gcry_sexp_build (&s_skey, NULL, + "(private-key(ecc(curve %s)" + "(flags eddsa)(q%m)(d%m)))", + curve, skey[1], skey[2]); + xfree (curve); + } + } + else + return GPG_ERR_PUBKEY_ALGO; + + if (!rc) + { + rc = gcry_pk_testkey (s_skey); + gcry_sexp_release (s_skey); + } + return rc; +} diff --git a/g10/pkglue.h b/g10/pkglue.h new file mode 100644 index 0000000..5780e23 --- /dev/null +++ b/g10/pkglue.h @@ -0,0 +1,51 @@ +/* pkglue.h - public key operations definitions + * Copyright (C) 2003, 2010 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#ifndef GNUPG_G10_PKGLUE_H +#define GNUPG_G10_PKGLUE_H + +#include "packet.h" /* For PKT_public_key. */ + +/*-- pkglue.c --*/ +gcry_mpi_t get_mpi_from_sexp (gcry_sexp_t sexp, const char *item, int mpifmt); + +int pk_verify (pubkey_algo_t algo, gcry_mpi_t hash, gcry_mpi_t *data, + gcry_mpi_t *pkey); +int pk_encrypt (pubkey_algo_t algo, gcry_mpi_t *resarr, gcry_mpi_t data, + PKT_public_key *pk, gcry_mpi_t *pkey); +int pk_check_secret_key (pubkey_algo_t algo, gcry_mpi_t *skey); + + +/*-- ecdh.c --*/ +gcry_mpi_t pk_ecdh_default_params (unsigned int qbits); +gpg_error_t pk_ecdh_generate_ephemeral_key (gcry_mpi_t *pkey, gcry_mpi_t *r_k); +gpg_error_t pk_ecdh_encrypt_with_shared_point +/* */ (int is_encrypt, const char *shared, size_t nshared, + const byte pk_fp[MAX_FINGERPRINT_LEN], + gcry_mpi_t data, gcry_mpi_t *pkey, + gcry_mpi_t *out); + +int pk_ecdh_encrypt (gcry_mpi_t *resarr, const byte pk_fp[MAX_FINGERPRINT_LEN], + gcry_mpi_t data, gcry_mpi_t * pkey); +int pk_ecdh_decrypt (gcry_mpi_t *result, const byte sk_fp[MAX_FINGERPRINT_LEN], + gcry_mpi_t data, const char *shared, size_t nshared, + gcry_mpi_t *skey); + + +#endif /*GNUPG_G10_PKGLUE_H*/ diff --git a/g10/plaintext.c b/g10/plaintext.c new file mode 100644 index 0000000..3bc8696 --- /dev/null +++ b/g10/plaintext.c @@ -0,0 +1,814 @@ +/* plaintext.c - process plaintext packets + * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, + * 2006, 2009, 2010 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include +#include +#include +#include +#include +#include +#ifdef HAVE_DOSISH_SYSTEM +# include /* for setmode() */ +#endif + +#include "gpg.h" +#include "../common/util.h" +#include "options.h" +#include "packet.h" +#include "../common/ttyio.h" +#include "filter.h" +#include "main.h" +#include "../common/status.h" +#include "../common/i18n.h" + + +/* Get the output filename. On success, the actual filename that is + used is set in *FNAMEP and a filepointer is returned in *FP. + + EMBEDDED_NAME AND EMBEDDED_NAMELEN are normally stored in a + plaintext packet. EMBEDDED_NAMELEN should not include any NUL + terminator (EMBEDDED_NAME does not need to be NUL terminated). + + DATA is the iobuf containing the input data. We just use it to get + the input file's filename. + + On success, the caller is responsible for calling xfree on *FNAMEP + and calling es_close on *FPP. */ +gpg_error_t +get_output_file (const byte *embedded_name, int embedded_namelen, + iobuf_t data, char **fnamep, estream_t *fpp) +{ + gpg_error_t err = 0; + char *fname = NULL; + estream_t fp = NULL; + int nooutput = 0; + + /* Create the filename as C string. */ + if (opt.outfp) + { + fname = xtrystrdup ("[FP]"); + if (!fname) + { + err = gpg_error_from_syserror (); + goto leave; + } + } + else if (opt.outfile + && !(opt.flags.use_embedded_filename && opt.flags.dummy_outfile)) + { + fname = xtrystrdup (opt.outfile); + if (!fname) + { + err = gpg_error_from_syserror (); + goto leave; + } + } + else if (embedded_namelen == 8 && !memcmp (embedded_name, "_CONSOLE", 8)) + { + log_info (_("data not saved; use option \"--output\" to save it\n")); + nooutput = 1; + } + else if (!opt.flags.use_embedded_filename) + { + if (data) + fname = make_outfile_name (iobuf_get_real_fname (data)); + if (!fname) + fname = ask_outfile_name (embedded_name, embedded_namelen); + if (!fname) + { + err = gpg_error (GPG_ERR_GENERAL); /* Can't create file. */ + goto leave; + } + } + else + fname = utf8_to_native (embedded_name, embedded_namelen, 0); + + if (nooutput) + ; + else if (opt.outfp) + { + fp = opt.outfp; + es_set_binary (fp); + } + else if (iobuf_is_pipe_filename (fname) || !*fname) + { + /* Special file name, no filename, or "-" given; write to the + * file descriptor or to stdout. */ + int fd; + char xname[64]; + + fd = check_special_filename (fname, 1, 0); + if (fd == -1) + { + /* Not a special filename, thus we want stdout. */ + fp = es_stdout; + es_set_binary (fp); + } + else if (!(fp = es_fdopen_nc (fd, "wb"))) + { + err = gpg_error_from_syserror (); + snprintf (xname, sizeof xname, "[fd %d]", fd); + log_error (_("can't open '%s': %s\n"), xname, gpg_strerror (err)); + goto leave; + } + } + else + { + while (!overwrite_filep (fname)) + { + char *tmp = ask_outfile_name (NULL, 0); + if (!tmp || !*tmp) + { + xfree (tmp); + /* FIXME: Below used to be GPG_ERR_CREATE_FILE */ + err = gpg_error (GPG_ERR_GENERAL); + goto leave; + } + xfree (fname); + fname = tmp; + } + } + +#ifndef __riscos__ + if (opt.outfp && is_secured_file (es_fileno (opt.outfp))) + { + err = gpg_error (GPG_ERR_EPERM); + log_error (_("error creating '%s': %s\n"), fname, gpg_strerror (err)); + goto leave; + } + else if (fp || nooutput) + ; + else if (is_secured_filename (fname)) + { + gpg_err_set_errno (EPERM); + err = gpg_error_from_syserror (); + log_error (_("error creating '%s': %s\n"), fname, gpg_strerror (err)); + goto leave; + } + else if (!(fp = es_fopen (fname, "wb"))) + { + err = gpg_error_from_syserror (); + log_error (_("error creating '%s': %s\n"), fname, gpg_strerror (err)); + 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 + { + /* Note: riscos stuff is not expected to work anymore. If we + want to port it again to riscos we should do most of the suff + in estream. FIXME: Consider to remove all riscos special + cases. */ + fp = gnupg_fopen (fname, "wb"); + if (!fp) + { + log_error (_("error creating '%s': %s\n"), fname, gpg_strerror (err)); + err = GPG_ERR_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 (embedded_name, + embedded_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__ */ + + leave: + if (err) + { + if (fp && fp != es_stdout && fp != opt.outfp) + es_fclose (fp); + xfree (fname); + return err; + } + + *fnamep = fname; + *fpp = fp; + return 0; +} + +/* Handle a plaintext packet. If MFX is not NULL, update the MDs + * Note: We should have used 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; + estream_t fp = NULL; + static off_t count = 0; + int err = 0; + int c; + int convert; +#ifdef __riscos__ + int filetype = 0xfff; +#endif + + if (pt->mode == 't' || pt->mode == 'u' || pt->mode == 'm') + convert = pt->mode; + else + convert = 0; + + /* 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]; + + /* Better make sure that stdout has been flushed in case the + output will be written to it. This is to make sure that no + not-yet-flushed stuff will be written after the plaintext + status message. */ + es_fflush (es_stdout); + + snprintf (status, sizeof 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) + { + snprintf (status, sizeof status, "%lu", (ulong) pt->len); + write_status_text (STATUS_PLAINTEXT_LENGTH, status); + } + } + + if (! nooutput) + { + err = get_output_file (pt->name, pt->namelen, pt->buf, &fname, &fp); + if (err) + goto leave; + } + + if (!pt->is_partial) + { + /* We have an actual length (which might be zero). */ + + if (clearsig) + { + log_error ("clearsig encountered while not expected\n"); + err = gpg_error (GPG_ERR_UNEXPECTED); + goto leave; + } + + if (convert) /* Text mode. */ + { + for (; pt->len; pt->len--) + { + if ((c = iobuf_get (pt->buf)) == -1) + { + err = gpg_error_from_syserror (); + log_error ("problem reading source (%u bytes remaining)\n", + (unsigned) pt->len); + goto leave; + } + if (mfx->md) + gcry_md_putc (mfx->md, c); +#ifndef HAVE_DOSISH_SYSTEM + /* Convert to native line ending. */ + /* fixme: this hack might be too simple */ + if (c == '\r' && convert != 'm') + continue; +#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"); + err = gpg_error (GPG_ERR_TOO_LARGE); + goto leave; + } + else if (es_putc (c, fp) == EOF) + { + if (es_ferror (fp)) + err = gpg_error_from_syserror (); + else + err = gpg_error (GPG_ERR_EOF); + log_error ("error writing to '%s': %s\n", + fname, gpg_strerror (err)); + 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) + { + err = gpg_error_from_syserror (); + log_error ("problem reading source (%u bytes remaining)\n", + (unsigned) pt->len); + xfree (buffer); + goto leave; + } + if (mfx->md) + gcry_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"); + err = gpg_error (GPG_ERR_TOO_LARGE); + xfree (buffer); + goto leave; + } + else if (es_fwrite (buffer, 1, len, fp) != len) + { + err = gpg_error_from_syserror (); + log_error ("error writing to '%s': %s\n", + fname, gpg_strerror (err)); + 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) + gcry_md_putc (mfx->md, c); +#ifndef HAVE_DOSISH_SYSTEM + if (c == '\r' && convert != 'm') + 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"); + err = gpg_error (GPG_ERR_TOO_LARGE); + goto leave; + } + else if (es_putc (c, fp) == EOF) + { + if (es_ferror (fp)) + err = gpg_error_from_syserror (); + else + err = gpg_error (GPG_ERR_EOF); + log_error ("error writing to '%s': %s\n", + fname, gpg_strerror (err)); + goto leave; + } + } + } + } + else + { /* binary mode */ + byte *buffer; + int eof_seen = 0; + + buffer = xtrymalloc (32768); + if (!buffer) + { + err = gpg_error_from_syserror (); + goto leave; + } + + while (!eof_seen) + { + /* 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_seen = 1; + if (mfx->md) + gcry_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"); + err = gpg_error (GPG_ERR_TOO_LARGE); + xfree (buffer); + goto leave; + } + else if (es_fwrite (buffer, 1, len, fp) != len) + { + err = gpg_error_from_syserror (); + log_error ("error writing to '%s': %s\n", + fname, gpg_strerror (err)); + 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"); + err = gpg_error (GPG_ERR_TOO_LARGE); + goto leave; + } + else if (es_putc (c, fp) == EOF) + { + err = gpg_error_from_syserror (); + log_error ("error writing to '%s': %s\n", + fname, gpg_strerror (err)); + goto leave; + } + } + if (!mfx->md) + continue; + if (state == 2) + { + gcry_md_putc (mfx->md, '\r'); + gcry_md_putc (mfx->md, '\n'); + state = 0; + } + if (!state) + { + if (c == '\r') + state = 1; + else if (c == '\n') + state = 2; + else + gcry_md_putc (mfx->md, c); + } + else if (state == 1) + { + if (c == '\n') + state = 2; + else + { + gcry_md_putc (mfx->md, '\r'); + if (c == '\r') + state = 1; + else + { + state = 0; + gcry_md_putc (mfx->md, c); + } + } + } + } + pt->buf = NULL; + } + + if (fp && fp != es_stdout && fp != opt.outfp && es_fclose (fp)) + { + err = gpg_error_from_syserror (); + log_error ("error closing '%s': %s\n", fname, gpg_strerror (err)); + fp = NULL; + goto leave; + } + fp = NULL; + + leave: + /* Make sure that stdout gets flushed after the plaintext has been + handled. This is for extra security as we do a flush anyway + before checking the signature. */ + if (es_fflush (es_stdout)) + { + /* We need to check the return code to detect errors like disk + full for short plaintexts. See bug#1207. Checking return + values is a good idea in any case. */ + if (!err) + err = gpg_error_from_syserror (); + log_error ("error flushing '%s': %s\n", "[stdout]", + gpg_strerror (err)); + } + + if (fp && fp != es_stdout && fp != opt.outfp) + es_fclose (fp); + xfree (fname); + return err; +} + + +static void +do_hash (gcry_md_hd_t md, gcry_md_hd_t 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') + gcry_md_putc (md2, c); + else if (c == '\n') + { + gcry_md_putc (md2, '\r'); + gcry_md_putc (md2, c); + } + else if (c != '\n' && lc == '\r') + { + gcry_md_putc (md2, '\n'); + gcry_md_putc (md2, c); + } + else + gcry_md_putc (md2, c); + + if (md) + gcry_md_putc (md, c); + lc = c; + } + } + else + { + while ((c = iobuf_get (fp)) != -1) + { + if (md) + gcry_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 (gcry_md_hd_t md, gcry_md_hd_t md2, + const char *inname, int textmode) +{ + progress_filter_context_t *pfx; + char *answer = NULL; + IOBUF fp; + int rc = 0; + + pfx = new_progress_context (); + 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 = gpg_error (GPG_ERR_GENERAL); /*G10ERR_READ_FILE */ + goto leave; + } + fp = iobuf_open (answer); + if (fp && is_secured_file (iobuf_get_fd (fp))) + { + iobuf_close (fp); + fp = NULL; + gpg_err_set_errno (EPERM); + } + if (!fp && errno == ENOENT) + { + tty_printf ("No such file, try again or hit enter to quit.\n"); + any++; + } + else if (!fp) + { + rc = gpg_error_from_syserror (); + log_error (_("can't open '%s': %s\n"), answer, + strerror (errno)); + goto leave; + } + } + while (!fp); + } + + if (!fp) + { + if (opt.verbose) + log_info (_("reading stdin ...\n")); + fp = iobuf_open (NULL); + log_assert (fp); + } + do_hash (md, md2, fp, textmode); + iobuf_close (fp); + +leave: + xfree (answer); + release_progress_context (pfx); + return rc; +} + + + +/* Hash the given files and append the hash to hash contexts MD and + * MD2. If FILES is NULL, stdin is hashed. */ +int +hash_datafiles (gcry_md_hd_t md, gcry_md_hd_t md2, strlist_t files, + const char *sigfilename, int textmode) +{ + progress_filter_context_t *pfx; + IOBUF fp; + strlist_t sl; + + pfx = new_progress_context (); + + if (!files) + { + /* Check whether we can open the signed material. We avoid + trying to open a file if run in batch mode. This assumed + data file for a sig file feature is just a convenience thing + for the command line and the user needs to read possible + warning messages. */ + if (!opt.batch) + { + fp = open_sigfile (sigfilename, pfx); + if (fp) + { + do_hash (md, md2, fp, textmode); + iobuf_close (fp); + release_progress_context (pfx); + return 0; + } + } + log_error (_("no signed data\n")); + release_progress_context (pfx); + return gpg_error (GPG_ERR_NO_DATA); + } + + + 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; + gpg_err_set_errno (EPERM); + } + if (!fp) + { + int rc = gpg_error_from_syserror (); + log_error (_("can't open signed data '%s'\n"), + print_fname_stdin (sl->d)); + release_progress_context (pfx); + return rc; + } + handle_progress (pfx, fp, sl->d); + do_hash (md, md2, fp, textmode); + iobuf_close (fp); + } + + release_progress_context (pfx); + return 0; +} + + +/* Hash the data from file descriptor DATA_FD and append the hash to hash + contexts MD and MD2. */ +int +hash_datafile_by_fd (gcry_md_hd_t md, gcry_md_hd_t md2, int data_fd, + int textmode) +{ + progress_filter_context_t *pfx = new_progress_context (); + iobuf_t fp; + + if (is_secured_file (data_fd)) + { + fp = NULL; + gpg_err_set_errno (EPERM); + } + else + fp = iobuf_fdopen_nc (data_fd, "rb"); + + if (!fp) + { + int rc = gpg_error_from_syserror (); + log_error (_("can't open signed data fd=%d: %s\n"), + data_fd, strerror (errno)); + release_progress_context (pfx); + return rc; + } + + handle_progress (pfx, fp, NULL); + + do_hash (md, md2, fp, textmode); + + iobuf_close (fp); + + release_progress_context (pfx); + 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 && !iobuf_is_pipe_filename (filename)) + || (opt.set_filename && !iobuf_is_pipe_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..7e777d4 --- /dev/null +++ b/g10/progress.c @@ -0,0 +1,202 @@ +/* progress.c - emit progress status lines + * Copyright (C) 2003, 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 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include +#include + +#include "gpg.h" +#include "../common/iobuf.h" +#include "filter.h" +#include "../common/status.h" +#include "../common/util.h" +#include "options.h" + +/* Create a new context for use with the progress filter. We need to + allocate such contexts on the heap because there is no guarantee + that at the end of a function the filter has already been popped + off. In general this will happen but with malformed packets it is + possible that a filter has not yet reached the end-of-stream when + the function has done all processing. Checking in each function + that end-of-stream has been reached would be to cumbersome. + + What we also do is to shortcut the progress handler by having this + function return NULL if progress information has not been + requested. +*/ +progress_filter_context_t * +new_progress_context (void) +{ + progress_filter_context_t *pfx; + + if (!opt.enable_progress_filter) + return NULL; + + if (!is_status_enabled ()) + return NULL; + + pfx = xcalloc (1, sizeof *pfx); + pfx->refcount = 1; + + return pfx; +} + +/* Release a progress filter context. Passing NULL is explicitly + allowed and a no-op. */ +void +release_progress_context (progress_filter_context_t *pfx) +{ + if (!pfx) + return; + log_assert (pfx->refcount); + if ( --pfx->refcount ) + return; + xfree (pfx->what); + xfree (pfx); +} + + +static void +write_status_progress (const char *what, + unsigned long current, unsigned long total_arg) +{ + char buffer[60]; + char units[] = "BKMGTPEZY?"; + int unitidx = 0; + uint64_t total = total_arg; + + /* Although we use an unsigned long for the values, 32 bit + * applications using GPGME will use an "int" and thus are limited + * in the total size which can be represented. On Windows, where + * sizeof(int)==sizeof(long), this is even worse and will lead to an + * integer overflow for all files larger than 2 GiB. Although, the + * allowed value range of TOTAL and CURRENT is nowhere specified, we + * better protect applications from the need to handle negative + * values. The common usage pattern of the progress information is + * to display how many percent of the operation has been done and + * thus scaling CURRENT and TOTAL down before they get to large, + * should not have a noticeable effect except for rounding + * imprecision. */ + + if (!total && opt.input_size_hint) + total = opt.input_size_hint; + + if (total) + { + if (current > total) + current = total; + + while (total > 1024*1024) + { + total /= 1024; + current /= 1024; + unitidx++; + } + } + else + { + while (current > 1024*1024) + { + current /= 1024; + unitidx++; + } + } + + if (unitidx > 9) + unitidx = 9; + + snprintf (buffer, sizeof buffer, "%.20s ? %lu %lu %c%s", + what? what : "?", current, (unsigned long)total, + units[unitidx], + unitidx? "iB" : ""); + write_status_text (STATUS_PROGRESS, buffer); +} + + +/**************** + * The filter is used to report progress to the user. + */ +static 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) + { + pfx->last = 0; + pfx->offset = 0; + pfx->last_time = make_timestamp (); + + write_status_progress (pfx->what, pfx->offset, pfx->total); + } + 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) + { + write_status_progress (pfx->what, pfx->offset, pfx->total); + pfx->last = pfx->offset; + pfx->last_time = timestamp; + } + } + else if (control == IOBUFCTRL_FREE) + { + release_progress_context (pfx); + } + else if (control == IOBUFCTRL_DESC) + mem2str (buf, "progress_filter", *ret_len); + return rc; +} + +void +handle_progress (progress_filter_context_t *pfx, IOBUF inp, const char *name) +{ + off_t filesize = 0; + + if (!pfx) + return; + + log_assert (opt.enable_progress_filter); + log_assert (is_status_enabled ()); + + 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; + pfx->refcount++; + 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..91dfb77 --- /dev/null +++ b/g10/pubkey-enc.c @@ -0,0 +1,487 @@ +/* pubkey-enc.c - Process a public key encoded packet. + * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2006, 2009, + * 2010 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include +#include +#include +#include + +#include "gpg.h" +#include "../common/util.h" +#include "packet.h" +#include "keydb.h" +#include "trustdb.h" +#include "../common/status.h" +#include "options.h" +#include "main.h" +#include "../common/i18n.h" +#include "pkglue.h" +#include "call-agent.h" +#include "../common/host2net.h" +#include "../common/compliance.h" + + +static gpg_error_t get_it (ctrl_t ctrl, PKT_pubkey_enc *k, + DEK *dek, PKT_public_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_t keyblock, preftype_t type, int algo) +{ + kbnode_t 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->flags.revoked && !uid->flags.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 by the caller. + */ +gpg_error_t +get_session_key (ctrl_t ctrl, PKT_pubkey_enc * k, DEK * dek) +{ + PKT_public_key *sk = NULL; + int rc; + + if (DBG_CLOCK) + log_clock ("get_session_key enter"); + + rc = openpgp_pk_test_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 (ctrl, sk, k->keyid))) + { + /* Check compliance. */ + if (! gnupg_pk_is_allowed (opt.compliance, PK_USE_DECRYPTION, + sk->pubkey_algo, 0, + sk->pkey, nbits_from_pk (sk), NULL)) + { + log_info (_("key %s is not suitable for decryption" + " in %s mode\n"), + keystr_from_pk (sk), + gnupg_compliance_option_string (opt.compliance)); + rc = gpg_error (GPG_ERR_PUBKEY_ALGO); + } + else + rc = get_it (ctrl, k, dek, sk, k->keyid); + } + } + else if (opt.skip_hidden_recipients) + rc = gpg_error (GPG_ERR_NO_SECKEY); + else /* Anonymous receiver: Try all available secret keys. */ + { + void *enum_context = NULL; + u32 keyid[2]; + + for (;;) + { + sk = xmalloc_clear (sizeof *sk); + rc = enum_secret_keys (ctrl, &enum_context, sk); + if (rc) + { + sk = NULL; /* enum_secret_keys turns SK into a shallow copy! */ + rc = GPG_ERR_NO_SECKEY; + break; + } + if (sk->pubkey_algo != k->pubkey_algo) + continue; + if (!(sk->pubkey_usage & PUBKEY_USAGE_ENC)) + continue; + keyid_from_pk (sk, keyid); + if (!opt.quiet) + log_info (_("anonymous recipient; trying secret key %s ...\n"), + keystr (keyid)); + + /* Check compliance. */ + if (! gnupg_pk_is_allowed (opt.compliance, PK_USE_DECRYPTION, + sk->pubkey_algo, 0, + sk->pkey, nbits_from_pk (sk), NULL)) + { + log_info (_("key %s is not suitable for decryption" + " in %s mode\n"), + keystr_from_pk (sk), + gnupg_compliance_option_string (opt.compliance)); + continue; + } + + rc = get_it (ctrl, k, dek, sk, keyid); + if (!rc) + { + if (!opt.quiet) + log_info (_("okay, we are the anonymous recipient.\n")); + sk = NULL; + break; + } + else if (gpg_err_code (rc) == GPG_ERR_FULLY_CANCELED) + { + sk = NULL; + break; /* Don't try any more secret keys. */ + } + } + enum_secret_keys (ctrl, &enum_context, NULL); /* free context */ + } + + leave: + free_public_key (sk); + if (DBG_CLOCK) + log_clock ("get_session_key leave"); + return rc; +} + + +static gpg_error_t +get_it (ctrl_t ctrl, + PKT_pubkey_enc *enc, DEK *dek, PKT_public_key *sk, u32 *keyid) +{ + gpg_error_t err; + byte *frame = NULL; + unsigned int n; + size_t nframe; + u16 csum, csum2; + int padding; + gcry_sexp_t s_data; + char *desc; + char *keygrip; + byte fp[MAX_FINGERPRINT_LEN]; + size_t fpn; + + if (DBG_CLOCK) + log_clock ("decryption start"); + + /* Get the keygrip. */ + err = hexkeygrip_from_pk (sk, &keygrip); + if (err) + goto leave; + + /* Convert the data to an S-expression. */ + if (sk->pubkey_algo == PUBKEY_ALGO_ELGAMAL + || sk->pubkey_algo == PUBKEY_ALGO_ELGAMAL_E) + { + if (!enc->data[0] || !enc->data[1]) + err = gpg_error (GPG_ERR_BAD_MPI); + else + err = gcry_sexp_build (&s_data, NULL, "(enc-val(elg(a%m)(b%m)))", + enc->data[0], enc->data[1]); + } + else if (sk->pubkey_algo == PUBKEY_ALGO_RSA + || sk->pubkey_algo == PUBKEY_ALGO_RSA_E) + { + if (!enc->data[0]) + err = gpg_error (GPG_ERR_BAD_MPI); + else + err = gcry_sexp_build (&s_data, NULL, "(enc-val(rsa(a%m)))", + enc->data[0]); + } + else if (sk->pubkey_algo == PUBKEY_ALGO_ECDH) + { + if (!enc->data[0] || !enc->data[1]) + err = gpg_error (GPG_ERR_BAD_MPI); + else + err = gcry_sexp_build (&s_data, NULL, "(enc-val(ecdh(s%m)(e%m)))", + enc->data[1], enc->data[0]); + } + else + err = gpg_error (GPG_ERR_BUG); + + if (err) + goto leave; + + if (sk->pubkey_algo == PUBKEY_ALGO_ECDH) + { + fingerprint_from_pk (sk, fp, &fpn); + log_assert (fpn == 20); + } + + /* Decrypt. */ + desc = gpg_format_keydesc (ctrl, sk, FORMAT_KEYDESC_NORMAL, 1); + err = agent_pkdecrypt (NULL, keygrip, + desc, sk->keyid, sk->main_keyid, sk->pubkey_algo, + s_data, &frame, &nframe, &padding); + xfree (desc); + gcry_sexp_release (s_data); + if (err) + goto leave; + + /* Now get the DEK (data encryption key) from the frame + * + * Old versions encode the DEK 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_CRYPTO) + log_printhex (frame, nframe, "DEK frame:"); + n = 0; + + if (sk->pubkey_algo == PUBKEY_ALGO_ECDH) + { + gcry_mpi_t decoded; + + err = pk_ecdh_decrypt (&decoded, fp, enc->data[1]/*encr data as an MPI*/, + frame, nframe, sk->pkey); + if(err) + goto leave; + + xfree (frame); + err = gcry_mpi_aprint (GCRYMPI_FMT_USG, &frame, &nframe, decoded); + mpi_release (decoded); + if (err) + goto leave; + + /* Now the frame are the bytes decrypted but padded session key. */ + if (!nframe || nframe <= 8 + || frame[nframe-1] > nframe) + { + err = gpg_error (GPG_ERR_WRONG_SECKEY); + goto leave; + } + nframe -= frame[nframe-1]; /* Remove padding. */ + log_assert (!n); /* (used just below) */ + } + else + { + if (padding) + { + if (n + 7 > nframe) + { + err = gpg_error (GPG_ERR_WRONG_SECKEY); + goto leave; + } + + /* FIXME: Actually the leading zero is required but due to + * the way we encode the output in libgcrypt as an MPI we + * are not able to encode that leading zero. However, when + * using a Smartcard we are doing it the right way and + * therefore we have to skip the zero. This should be fixed + * in gpg-agent of course. */ + if (!frame[n]) + n++; + + if (frame[n] == 1 && frame[nframe - 1] == 2) + { + log_info (_("old encoding of the DEK is not supported\n")); + err = gpg_error (GPG_ERR_CIPHER_ALGO); + goto leave; + } + if (frame[n] != 2) /* Something went wrong. */ + { + err = gpg_error (GPG_ERR_WRONG_SECKEY); + goto leave; + } + for (n++; n < nframe && frame[n]; n++) /* Skip the random bytes. */ + ; + n++; /* Skip the zero byte. */ + } + } + + if (n + 4 > nframe) + { + err = gpg_error (GPG_ERR_WRONG_SECKEY); + goto leave; + } + + dek->keylen = nframe - (n + 1) - 2; + dek->algo = frame[n++]; + err = openpgp_cipher_test_algo (dek->algo); + if (err) + { + if (!opt.quiet && gpg_err_code (err) == GPG_ERR_CIPHER_ALGO) + { + log_info (_("cipher algorithm %d%s is unknown or disabled\n"), + dek->algo, + dek->algo == CIPHER_ALGO_IDEA ? " (IDEA)" : ""); + } + dek->algo = 0; + goto leave; + } + if (dek->keylen != openpgp_cipher_get_algo_keylen (dek->algo)) + { + err = gpg_error (GPG_ERR_WRONG_SECKEY); + goto leave; + } + + /* Copy the key to DEK and compare the checksum. */ + csum = buf16_to_u16 (frame+nframe-2); + memcpy (dek->key, frame + n, dek->keylen); + for (csum2 = 0, n = 0; n < dek->keylen; n++) + csum2 += dek->key[n]; + if (csum != csum2) + { + err = gpg_error (GPG_ERR_WRONG_SECKEY); + goto leave; + } + if (DBG_CLOCK) + log_clock ("decryption ready"); + if (DBG_CRYPTO) + log_printhex (dek->key, dek->keylen, "DEK is:"); + + /* Check that the algo is in the preferences and whether it has + * expired. Also print a status line with the key's fingerprint. */ + { + PKT_public_key *pk = NULL; + PKT_public_key *mainpk = NULL; + KBNODE pkb = get_pubkeyblock (ctrl, keyid); + + if (!pkb) + { + err = -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"), openpgp_cipher_algo_name (dek->algo)); + + if (!err) + { + kbnode_t k; + int first = 1; + + for (k = pkb; k; k = k->next) + { + if (k->pkt->pkttype == PKT_PUBLIC_KEY + || k->pkt->pkttype == PKT_PUBLIC_SUBKEY) + { + u32 aki[2]; + + if (first) + { + first = 0; + mainpk = k->pkt->pkt.public_key; + } + + 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->flags.revoked) + { + log_info (_("Note: key has been revoked")); + log_printf ("\n"); + show_revocation_reason (ctrl, pk, 1); + } + + if (is_status_enabled () && pk && mainpk) + { + char pkhex[MAX_FINGERPRINT_LEN*2+1]; + char mainpkhex[MAX_FINGERPRINT_LEN*2+1]; + + hexfingerprint (pk, pkhex, sizeof pkhex); + hexfingerprint (mainpk, mainpkhex, sizeof mainpkhex); + + /* Note that we do not want to create a trustdb just for + * getting the ownertrust: If there is no trustdb there can't + * be ulitmately trusted key anyway and thus the ownertrust + * value is irrelevant. */ + write_status_printf (STATUS_DECRYPTION_KEY, "%s %s %c", + pkhex, mainpkhex, + get_ownertrust_info (ctrl, mainpk, 1)); + + } + + release_kbnode (pkb); + err = 0; + } + + leave: + xfree (frame); + xfree (keygrip); + return err; +} + + +/* + * Get the session key from the given string. + * String is supposed to be formatted as this: + * : + */ +gpg_error_t +get_override_session_key (DEK *dek, const char *string) +{ + const char *s; + int i; + + if (!string) + return GPG_ERR_BAD_KEY; + dek->algo = atoi (string); + if (dek->algo < 1) + return GPG_ERR_BAD_KEY; + if (!(s = strchr (string, ':'))) + return GPG_ERR_BAD_KEY; + s++; + for (i = 0; i < DIM (dek->key) && *s; i++, s += 2) + { + int c = hextobyte (s); + if (c == -1) + return GPG_ERR_BAD_KEY; + dek->key[i] = c; + } + if (*s) + return GPG_ERR_BAD_KEY; + dek->keylen = i; + return 0; +} diff --git a/g10/revoke.c b/g10/revoke.c new file mode 100644 index 0000000..035a2e9 --- /dev/null +++ b/g10/revoke.c @@ -0,0 +1,911 @@ +/* revoke.c - Create recovation certificates. + * 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 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include +#include +#include +#include +#include +#include + +#include "gpg.h" +#include "options.h" +#include "packet.h" +#include "../common/status.h" +#include "keydb.h" +#include "../common/util.h" +#include "main.h" +#include "../common/ttyio.h" +#include "../common/i18n.h" +#include "call-agent.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 GPG_ERR_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"), gpg_strerror (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", gpg_strerror (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"), gpg_strerror (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 GPG_ERR_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"), gpg_strerror (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"), gpg_strerror (rc) ); + return rc; + } + } + + return 0; +} + +/**************** + * Generate a revocation certificate for UNAME via a designated revoker + */ +int +gen_desig_revoke (ctrl_t ctrl, const char *uname, strlist_t locusr) +{ + int rc = 0; + armor_filter_context_t *afx; + PKT_public_key *pk = NULL; + PKT_public_key *pk2 = 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 GPG_ERR_GENERAL; + } + + afx = new_armor_context (); + + kdbhd = keydb_new (); + if (!kdbhd) + { + rc = gpg_error_from_syserror (); + goto leave; + } + rc = classify_user_id (uname, &desc, 1); + if (!rc) + rc = keydb_search (kdbhd, &desc, 1, NULL); + if (rc) { + log_error (_("key \"%s\" not found: %s\n"),uname, gpg_strerror (rc)); + goto leave; + } + + rc = keydb_get_keyblock (kdbhd, &keyblock ); + if( rc ) { + log_error (_("error reading keyblock: %s\n"), gpg_strerror (rc) ); + goto leave; + } + + /* To parse the revkeys */ + merge_keys_and_selfsig (ctrl, 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 (ctrl, locusr, &sk_list, PUBKEY_USAGE_CERT); + if(rc) + goto leave; + } + + /* Are we a designated revoker for this key? */ + + if(!pk->revkey && pk->numrevkeys) + BUG(); + + for(i=0;inumrevkeys;i++) + { + SK_LIST list; + + free_public_key (pk2); + pk2 = NULL; + + if(sk_list) + { + for(list=sk_list;list;list=list->next) + { + byte fpr[MAX_FINGERPRINT_LEN]; + size_t fprlen; + + fingerprint_from_pk (list->pk, 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) + pk2 = copy_public_key (NULL, list->pk); + else + continue; + } + else + { + pk2 = xmalloc_clear (sizeof *pk2); + rc = get_pubkey_byfprint (ctrl, pk2, NULL, + pk->revkey[i].fpr, MAX_FINGERPRINT_LEN); + } + + /* We have the revocation key. */ + if(!rc) + { + PKT_signature *revkey = NULL; + + any = 1; + + print_pubkey_info (ctrl, NULL, pk); + tty_printf ("\n"); + + tty_printf (_("To be revoked by:\n")); + print_seckey_info (ctrl, pk2); + + if(pk->revkey[i].class&0x40) + tty_printf(_("(This is a sensitive revocation key)\n")); + tty_printf("\n"); + + rc = agent_probe_secret_key (ctrl, pk2); + if (rc) + { + tty_printf (_("Secret key is not available.\n")); + continue; + } + + 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; + + if( !opt.armor ) + tty_printf(_("ASCII armored output forced.\n")); + + if( (rc = open_outfile (-1, NULL, 0, 1, &out )) ) + goto leave; + + afx->what = 1; + afx->hdrlines = "Comment: A designated revocation certificate" + " should follow\n"; + push_armor_filter (afx, out); + + /* create it */ + rc = make_keysig_packet (ctrl, &sig, pk, NULL, NULL, pk2, 0x20, 0, + 0, 0, + revocation_reason_build_cb, reason, + NULL); + if( rc ) { + log_error(_("make_keysig_packet failed: %s\n"), gpg_strerror (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;jpkt->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: + free_public_key (pk); + free_public_key (pk2); + 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 ); + release_armor_context (afx); + return rc; +} + + +/* Common core to create the revocation. FILENAME may be NULL to write + to stdout or the filename given by --output. REASON describes the + revocation reason. PSK is the public primary key - we expect that + a corresponding secret key is available. KEYBLOCK is the entire + KEYBLOCK which is used in PGP mode to write a minimal key and not + just the naked revocation signature; it may be NULL. If LEADINTEXT + is not NULL, it is written right before the (armored) output.*/ +static int +create_revocation (ctrl_t ctrl, + const char *filename, + struct revocation_reason_info *reason, + PKT_public_key *psk, + kbnode_t keyblock, + const char *leadintext, int suffix, + const char *cache_nonce) +{ + int rc; + iobuf_t out = NULL; + armor_filter_context_t *afx; + PKT_signature *sig = NULL; + PACKET pkt; + + afx = new_armor_context (); + + if ((rc = open_outfile (-1, filename, suffix, 1, &out))) + goto leave; + + if (leadintext ) + iobuf_writestr (out, leadintext); + + afx->what = 1; + afx->hdrlines = "Comment: This is a revocation certificate\n"; + push_armor_filter (afx, out); + + rc = make_keysig_packet (ctrl, &sig, psk, NULL, NULL, psk, 0x20, 0, + 0, 0, + revocation_reason_build_cb, reason, cache_nonce); + if (rc) + { + log_error (_("make_keysig_packet failed: %s\n"), gpg_strerror (rc)); + goto leave; + } + + if (keyblock && (PGP6 || PGP7 || PGP8)) + { + /* Use a minimal pk for PGPx mode, since PGP can't import bare + revocation certificates. */ + rc = export_minimal_pk (out, 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"), gpg_strerror (rc)); + goto leave; + } + } + + leave: + if (sig) + free_seckey_enc (sig); + if (rc) + iobuf_cancel (out); + else + iobuf_close (out); + release_armor_context (afx); + return rc; +} + + +/* This function is used to generate a standard revocation certificate + by gpg's interactive key generation function. The certificate is + stored at a dedicated place in a slightly modified form to avoid an + accidental import. PSK is the primary key; a corresponding secret + key must be available. CACHE_NONCE is optional but can be used to + help gpg-agent to avoid an extra passphrase prompt. */ +int +gen_standard_revoke (ctrl_t ctrl, PKT_public_key *psk, const char *cache_nonce) +{ + int rc; + estream_t memfp; + struct revocation_reason_info reason; + char *dir, *tmpstr, *fname; + void *leadin; + size_t len; + u32 keyid[2]; + int kl; + char *orig_codeset; + char *old_outfile; + + dir = get_openpgp_revocdir (gnupg_homedir ()); + tmpstr = hexfingerprint (psk, NULL, 0); + if (!tmpstr) + { + rc = gpg_error_from_syserror (); + xfree (dir); + return rc; + } + fname = strconcat (dir, DIRSEP_S, tmpstr, NULL); + if (!fname) + { + rc = gpg_error_from_syserror (); + xfree (tmpstr); + xfree (dir); + return rc; + } + xfree (tmpstr); + xfree (dir); + + keyid_from_pk (psk, keyid); + + memfp = es_fopenmem (0, "r+"); + if (!memfp) + log_fatal ("error creating memory stream\n"); + + orig_codeset = i18n_switchto_utf8 (); + + es_fprintf (memfp, "%s\n\n", + _("This is a revocation certificate for the OpenPGP key:")); + + print_key_line (ctrl, memfp, psk, 0); + + if (opt.keyid_format != KF_NONE) + print_fingerprint (ctrl, memfp, psk, 3); + + kl = opt.keyid_format == KF_NONE? 0 : keystrlen (); + + tmpstr = get_user_id (ctrl, keyid, &len, NULL); + es_fprintf (memfp, "uid%*s%.*s\n\n", + kl + 10, "", + (int)len, tmpstr); + xfree (tmpstr); + + es_fprintf (memfp, "%s\n\n%s\n\n%s\n\n:", + _("A revocation certificate is a kind of \"kill switch\" to publicly\n" + "declare that a key shall not anymore be used. It is not possible\n" + "to retract such a revocation certificate once it has been published."), + _("Use it to revoke this key in case of a compromise or loss of\n" + "the secret key. However, if the secret key is still accessible,\n" + "it is better to generate a new revocation certificate and give\n" + "a reason for the revocation. For details see the description of\n" + "of the gpg command \"--generate-revocation\" in the " + "GnuPG manual."), + _("To avoid an accidental use of this file, a colon has been inserted\n" + "before the 5 dashes below. Remove this colon with a text editor\n" + "before importing and publishing this revocation certificate.")); + + es_putc (0, memfp); + + i18n_switchback (orig_codeset); + + if (es_fclose_snatch (memfp, &leadin, NULL)) + log_fatal ("error snatching memory stream\n"); + + reason.code = 0x00; /* No particular reason. */ + reason.desc = NULL; + old_outfile = opt.outfile; + opt.outfile = NULL; + rc = create_revocation (ctrl, + fname, &reason, psk, NULL, leadin, 3, cache_nonce); + opt.outfile = old_outfile; + if (!rc && !opt.quiet) + log_info (_("revocation certificate stored as '%s.rev'\n"), fname); + + xfree (leadin); + xfree (fname); + + return rc; +} + + + +/**************** + * Generate a revocation certificate for UNAME + */ +int +gen_revoke (ctrl_t ctrl, const char *uname) +{ + int rc = 0; + PKT_public_key *psk; + u32 keyid[2]; + kbnode_t keyblock = NULL; + kbnode_t 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 GPG_ERR_GENERAL; + } + + /* Search the userid; we don't want the whole getkey stuff here. */ + kdbhd = keydb_new (); + if (!kdbhd) + { + rc = gpg_error_from_syserror (); + goto leave; + } + rc = classify_user_id (uname, &desc, 1); + if (!rc) + rc = keydb_search (kdbhd, &desc, 1, NULL); + if (rc) + { + if (gpg_err_code (rc) == GPG_ERR_NOT_FOUND) + log_error (_("secret key \"%s\" not found\n"), uname); + else + log_error (_("secret key \"%s\" not found: %s\n"), + uname, gpg_strerror (rc)); + goto leave; + } + + rc = keydb_get_keyblock (kdbhd, &keyblock ); + if (rc) + { + log_error (_("error reading keyblock: %s\n"), gpg_strerror (rc) ); + goto leave; + } + + rc = keydb_search (kdbhd, &desc, 1, NULL); + if (gpg_err_code (rc) == GPG_ERR_NOT_FOUND) + /* Not ambiguous. */ + { + } + else if (rc == 0) + /* Ambiguous. */ + { + char *info; + + /* TRANSLATORS: The %s prints a key specification which + for example has been given at the command line. Several lines + lines with secret key infos are printed after this message. */ + log_error (_("'%s' matches multiple secret keys:\n"), uname); + + info = format_seckey_info (ctrl, keyblock->pkt->pkt.public_key); + log_error (" %s\n", info); + xfree (info); + release_kbnode (keyblock); + + rc = keydb_get_keyblock (kdbhd, &keyblock); + while (! rc) + { + info = format_seckey_info (ctrl, keyblock->pkt->pkt.public_key); + log_info (" %s\n", info); + xfree (info); + release_kbnode (keyblock); + keyblock = NULL; + + rc = keydb_search (kdbhd, &desc, 1, NULL); + if (! rc) + rc = keydb_get_keyblock (kdbhd, &keyblock); + } + + rc = GPG_ERR_AMBIGUOUS_NAME; + + goto leave; + } + else + { + log_error (_("error searching the keyring: %s\n"), gpg_strerror (rc)); + goto leave; + } + + /* Get the keyid from the keyblock. */ + node = find_kbnode (keyblock, PKT_PUBLIC_KEY); + if (!node) + BUG (); + + psk = node->pkt->pkt.public_key; + rc = agent_probe_secret_key (NULL, psk); + if (rc) + { + log_error (_("secret key \"%s\" not found: %s\n"), + uname, gpg_strerror (rc)); + goto leave; + } + + keyid_from_pk (psk, keyid ); + print_seckey_info (ctrl, psk); + + 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; + } + + /* Get the reason for the revocation. */ + reason = ask_revocation_reason (1, 0, 1); + if (!reason) + { + /* User decided to cancel. */ + rc = 0; + goto leave; + } + + if (!opt.armor) + tty_printf (_("ASCII armored output forced.\n")); + + rc = create_revocation (ctrl, NULL, reason, psk, keyblock, NULL, 0, NULL); + if (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: + release_kbnode (keyblock); + keydb_release (kdbhd); + 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; +} + +struct revocation_reason_info * +get_default_uid_revocation_reason(void) +{ + struct revocation_reason_info *reason; + reason = xmalloc( sizeof *reason ); + reason->code = 0x20; /* uid is no longer valid */ + reason->desc = strdup(""); /* no text */ + return reason; +} + +struct revocation_reason_info * +get_default_sig_revocation_reason(void) +{ + struct revocation_reason_info *reason; + reason = xmalloc( sizeof *reason ); + reason->code = 0; /* No specific reason given. */ + reason->desc = strdup(""); /* no text */ + return reason; +} + +void +release_revocation_reason_info( struct revocation_reason_info *reason ) +{ + if( reason ) { + xfree( reason->desc ); + xfree( reason ); + } +} diff --git a/g10/rmd160.c b/g10/rmd160.c new file mode 100644 index 0000000..cf27796 --- /dev/null +++ b/g10/rmd160.c @@ -0,0 +1,425 @@ +/* rmd160.c - RIPE-MD160 + * Copyright (C) 1998, 1999, 2000, 2001, 2008 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +/* For historic reasons gpg uses RIPE-MD160 to identify names in + the trustdb. It would be better to change that to SHA-1, to take + advantage of a SHA-1 hardware operation provided by some CPUs. + This would break trustdb compatibility and thus we don't want to do + it now. + + We do not use the libgcrypt provided implementation of RMD160 + because that is not available in FIPS mode, thus for the sake of + gpg internal non-cryptographic, purposes, we use this separate + implementation. +*/ + +#include + +#include +#include +#include + +#include "../common/types.h" +#include "rmd160.h" + +/* + * Rotate the 32 bit integer X by N bytes. + */ +#if defined(__GNUC__) && defined(__i386__) +static inline u32 +rol (u32 x, int n) +{ + __asm__("roll %%cl,%0" + :"=r" (x) + :"0" (x),"c" (n)); + return x; +} +#else +#define rol(x,n) ( ((x) << (n)) | ((x) >> (32-(n))) ) +#endif + +/* Structure holding the context for the RIPE-MD160 computation. */ +typedef struct +{ + u32 h0, h1, h2, h3, h4; + u32 nblocks; + unsigned char buf[64]; + int count; +} rmd160_context_t; + + + +static void +rmd160_init (rmd160_context_t *hd) +{ + hd->h0 = 0x67452301; + hd->h1 = 0xEFCDAB89; + hd->h2 = 0x98BADCFE; + hd->h3 = 0x10325476; + hd->h4 = 0xC3D2E1F0; + hd->nblocks = 0; + hd->count = 0; +} + + + +/* + * Transform the message X which consists of 16 32-bit-words. + */ +static void +transform (rmd160_context_t *hd, const unsigned char *data) +{ + u32 a,b,c,d,e,aa,bb,cc,dd,ee,t; +#ifdef BIG_ENDIAN_HOST + u32 x[16]; + { + int i; + unsigned char *p2, *p1; + for (i=0, p1=data, p2=(unsigned char*)x; i < 16; i++, p2 += 4 ) + { + p2[3] = *p1++; + p2[2] = *p1++; + p2[1] = *p1++; + p2[0] = *p1++; + } + } +#else + u32 x[16]; + memcpy (x, data, 64); +#endif + + +#define K0 0x00000000 +#define K1 0x5A827999 +#define K2 0x6ED9EBA1 +#define K3 0x8F1BBCDC +#define K4 0xA953FD4E +#define KK0 0x50A28BE6 +#define KK1 0x5C4DD124 +#define KK2 0x6D703EF3 +#define KK3 0x7A6D76E9 +#define KK4 0x00000000 +#define F0(x,y,z) ( (x) ^ (y) ^ (z) ) +#define F1(x,y,z) ( ((x) & (y)) | (~(x) & (z)) ) +#define F2(x,y,z) ( ((x) | ~(y)) ^ (z) ) +#define F3(x,y,z) ( ((x) & (z)) | ((y) & ~(z)) ) +#define F4(x,y,z) ( (x) ^ ((y) | ~(z)) ) +#define R(a,b,c,d,e,f,k,r,s) do { t = a + f(b,c,d) + k + x[r]; \ + a = rol(t,s) + e; \ + c = rol(c,10); \ + } while(0) + + /* Left lane. */ + a = hd->h0; + b = hd->h1; + c = hd->h2; + d = hd->h3; + e = hd->h4; + R( a, b, c, d, e, F0, K0, 0, 11 ); + R( e, a, b, c, d, F0, K0, 1, 14 ); + R( d, e, a, b, c, F0, K0, 2, 15 ); + R( c, d, e, a, b, F0, K0, 3, 12 ); + R( b, c, d, e, a, F0, K0, 4, 5 ); + R( a, b, c, d, e, F0, K0, 5, 8 ); + R( e, a, b, c, d, F0, K0, 6, 7 ); + R( d, e, a, b, c, F0, K0, 7, 9 ); + R( c, d, e, a, b, F0, K0, 8, 11 ); + R( b, c, d, e, a, F0, K0, 9, 13 ); + R( a, b, c, d, e, F0, K0, 10, 14 ); + R( e, a, b, c, d, F0, K0, 11, 15 ); + R( d, e, a, b, c, F0, K0, 12, 6 ); + R( c, d, e, a, b, F0, K0, 13, 7 ); + R( b, c, d, e, a, F0, K0, 14, 9 ); + R( a, b, c, d, e, F0, K0, 15, 8 ); + R( e, a, b, c, d, F1, K1, 7, 7 ); + R( d, e, a, b, c, F1, K1, 4, 6 ); + R( c, d, e, a, b, F1, K1, 13, 8 ); + R( b, c, d, e, a, F1, K1, 1, 13 ); + R( a, b, c, d, e, F1, K1, 10, 11 ); + R( e, a, b, c, d, F1, K1, 6, 9 ); + R( d, e, a, b, c, F1, K1, 15, 7 ); + R( c, d, e, a, b, F1, K1, 3, 15 ); + R( b, c, d, e, a, F1, K1, 12, 7 ); + R( a, b, c, d, e, F1, K1, 0, 12 ); + R( e, a, b, c, d, F1, K1, 9, 15 ); + R( d, e, a, b, c, F1, K1, 5, 9 ); + R( c, d, e, a, b, F1, K1, 2, 11 ); + R( b, c, d, e, a, F1, K1, 14, 7 ); + R( a, b, c, d, e, F1, K1, 11, 13 ); + R( e, a, b, c, d, F1, K1, 8, 12 ); + R( d, e, a, b, c, F2, K2, 3, 11 ); + R( c, d, e, a, b, F2, K2, 10, 13 ); + R( b, c, d, e, a, F2, K2, 14, 6 ); + R( a, b, c, d, e, F2, K2, 4, 7 ); + R( e, a, b, c, d, F2, K2, 9, 14 ); + R( d, e, a, b, c, F2, K2, 15, 9 ); + R( c, d, e, a, b, F2, K2, 8, 13 ); + R( b, c, d, e, a, F2, K2, 1, 15 ); + R( a, b, c, d, e, F2, K2, 2, 14 ); + R( e, a, b, c, d, F2, K2, 7, 8 ); + R( d, e, a, b, c, F2, K2, 0, 13 ); + R( c, d, e, a, b, F2, K2, 6, 6 ); + R( b, c, d, e, a, F2, K2, 13, 5 ); + R( a, b, c, d, e, F2, K2, 11, 12 ); + R( e, a, b, c, d, F2, K2, 5, 7 ); + R( d, e, a, b, c, F2, K2, 12, 5 ); + R( c, d, e, a, b, F3, K3, 1, 11 ); + R( b, c, d, e, a, F3, K3, 9, 12 ); + R( a, b, c, d, e, F3, K3, 11, 14 ); + R( e, a, b, c, d, F3, K3, 10, 15 ); + R( d, e, a, b, c, F3, K3, 0, 14 ); + R( c, d, e, a, b, F3, K3, 8, 15 ); + R( b, c, d, e, a, F3, K3, 12, 9 ); + R( a, b, c, d, e, F3, K3, 4, 8 ); + R( e, a, b, c, d, F3, K3, 13, 9 ); + R( d, e, a, b, c, F3, K3, 3, 14 ); + R( c, d, e, a, b, F3, K3, 7, 5 ); + R( b, c, d, e, a, F3, K3, 15, 6 ); + R( a, b, c, d, e, F3, K3, 14, 8 ); + R( e, a, b, c, d, F3, K3, 5, 6 ); + R( d, e, a, b, c, F3, K3, 6, 5 ); + R( c, d, e, a, b, F3, K3, 2, 12 ); + R( b, c, d, e, a, F4, K4, 4, 9 ); + R( a, b, c, d, e, F4, K4, 0, 15 ); + R( e, a, b, c, d, F4, K4, 5, 5 ); + R( d, e, a, b, c, F4, K4, 9, 11 ); + R( c, d, e, a, b, F4, K4, 7, 6 ); + R( b, c, d, e, a, F4, K4, 12, 8 ); + R( a, b, c, d, e, F4, K4, 2, 13 ); + R( e, a, b, c, d, F4, K4, 10, 12 ); + R( d, e, a, b, c, F4, K4, 14, 5 ); + R( c, d, e, a, b, F4, K4, 1, 12 ); + R( b, c, d, e, a, F4, K4, 3, 13 ); + R( a, b, c, d, e, F4, K4, 8, 14 ); + R( e, a, b, c, d, F4, K4, 11, 11 ); + R( d, e, a, b, c, F4, K4, 6, 8 ); + R( c, d, e, a, b, F4, K4, 15, 5 ); + R( b, c, d, e, a, F4, K4, 13, 6 ); + + aa = a; bb = b; cc = c; dd = d; ee = e; + + /* Right lane. */ + a = hd->h0; + b = hd->h1; + c = hd->h2; + d = hd->h3; + e = hd->h4; + R( a, b, c, d, e, F4, KK0, 5, 8); + R( e, a, b, c, d, F4, KK0, 14, 9); + R( d, e, a, b, c, F4, KK0, 7, 9); + R( c, d, e, a, b, F4, KK0, 0, 11); + R( b, c, d, e, a, F4, KK0, 9, 13); + R( a, b, c, d, e, F4, KK0, 2, 15); + R( e, a, b, c, d, F4, KK0, 11, 15); + R( d, e, a, b, c, F4, KK0, 4, 5); + R( c, d, e, a, b, F4, KK0, 13, 7); + R( b, c, d, e, a, F4, KK0, 6, 7); + R( a, b, c, d, e, F4, KK0, 15, 8); + R( e, a, b, c, d, F4, KK0, 8, 11); + R( d, e, a, b, c, F4, KK0, 1, 14); + R( c, d, e, a, b, F4, KK0, 10, 14); + R( b, c, d, e, a, F4, KK0, 3, 12); + R( a, b, c, d, e, F4, KK0, 12, 6); + R( e, a, b, c, d, F3, KK1, 6, 9); + R( d, e, a, b, c, F3, KK1, 11, 13); + R( c, d, e, a, b, F3, KK1, 3, 15); + R( b, c, d, e, a, F3, KK1, 7, 7); + R( a, b, c, d, e, F3, KK1, 0, 12); + R( e, a, b, c, d, F3, KK1, 13, 8); + R( d, e, a, b, c, F3, KK1, 5, 9); + R( c, d, e, a, b, F3, KK1, 10, 11); + R( b, c, d, e, a, F3, KK1, 14, 7); + R( a, b, c, d, e, F3, KK1, 15, 7); + R( e, a, b, c, d, F3, KK1, 8, 12); + R( d, e, a, b, c, F3, KK1, 12, 7); + R( c, d, e, a, b, F3, KK1, 4, 6); + R( b, c, d, e, a, F3, KK1, 9, 15); + R( a, b, c, d, e, F3, KK1, 1, 13); + R( e, a, b, c, d, F3, KK1, 2, 11); + R( d, e, a, b, c, F2, KK2, 15, 9); + R( c, d, e, a, b, F2, KK2, 5, 7); + R( b, c, d, e, a, F2, KK2, 1, 15); + R( a, b, c, d, e, F2, KK2, 3, 11); + R( e, a, b, c, d, F2, KK2, 7, 8); + R( d, e, a, b, c, F2, KK2, 14, 6); + R( c, d, e, a, b, F2, KK2, 6, 6); + R( b, c, d, e, a, F2, KK2, 9, 14); + R( a, b, c, d, e, F2, KK2, 11, 12); + R( e, a, b, c, d, F2, KK2, 8, 13); + R( d, e, a, b, c, F2, KK2, 12, 5); + R( c, d, e, a, b, F2, KK2, 2, 14); + R( b, c, d, e, a, F2, KK2, 10, 13); + R( a, b, c, d, e, F2, KK2, 0, 13); + R( e, a, b, c, d, F2, KK2, 4, 7); + R( d, e, a, b, c, F2, KK2, 13, 5); + R( c, d, e, a, b, F1, KK3, 8, 15); + R( b, c, d, e, a, F1, KK3, 6, 5); + R( a, b, c, d, e, F1, KK3, 4, 8); + R( e, a, b, c, d, F1, KK3, 1, 11); + R( d, e, a, b, c, F1, KK3, 3, 14); + R( c, d, e, a, b, F1, KK3, 11, 14); + R( b, c, d, e, a, F1, KK3, 15, 6); + R( a, b, c, d, e, F1, KK3, 0, 14); + R( e, a, b, c, d, F1, KK3, 5, 6); + R( d, e, a, b, c, F1, KK3, 12, 9); + R( c, d, e, a, b, F1, KK3, 2, 12); + R( b, c, d, e, a, F1, KK3, 13, 9); + R( a, b, c, d, e, F1, KK3, 9, 12); + R( e, a, b, c, d, F1, KK3, 7, 5); + R( d, e, a, b, c, F1, KK3, 10, 15); + R( c, d, e, a, b, F1, KK3, 14, 8); + R( b, c, d, e, a, F0, KK4, 12, 8); + R( a, b, c, d, e, F0, KK4, 15, 5); + R( e, a, b, c, d, F0, KK4, 10, 12); + R( d, e, a, b, c, F0, KK4, 4, 9); + R( c, d, e, a, b, F0, KK4, 1, 12); + R( b, c, d, e, a, F0, KK4, 5, 5); + R( a, b, c, d, e, F0, KK4, 8, 14); + R( e, a, b, c, d, F0, KK4, 7, 6); + R( d, e, a, b, c, F0, KK4, 6, 8); + R( c, d, e, a, b, F0, KK4, 2, 13); + R( b, c, d, e, a, F0, KK4, 13, 6); + R( a, b, c, d, e, F0, KK4, 14, 5); + R( e, a, b, c, d, F0, KK4, 0, 15); + R( d, e, a, b, c, F0, KK4, 3, 13); + R( c, d, e, a, b, F0, KK4, 9, 11); + R( b, c, d, e, a, F0, KK4, 11, 11); + + + t = hd->h1 + d + cc; + hd->h1 = hd->h2 + e + dd; + hd->h2 = hd->h3 + a + ee; + hd->h3 = hd->h4 + b + aa; + hd->h4 = hd->h0 + c + bb; + hd->h0 = t; +} + + +/* Update the message digest with the content of (INBUF,INLEN). */ +static void +rmd160_write (rmd160_context_t *hd, const unsigned char *inbuf, size_t inlen) +{ + if( hd->count == 64 ) + { + /* Flush the buffer. */ + transform (hd, hd->buf); + hd->count = 0; + hd->nblocks++; + } + if (!inbuf) + return; + + if (hd->count) + { + for (; inlen && hd->count < 64; inlen--) + hd->buf[hd->count++] = *inbuf++; + rmd160_write (hd, NULL, 0); + if (!inlen) + return; + } + + while (inlen >= 64) + { + transform (hd, inbuf); + hd->count = 0; + hd->nblocks++; + inlen -= 64; + inbuf += 64; + } + for (; inlen && hd->count < 64; inlen--) + hd->buf[hd->count++] = *inbuf++; +} + + +/* Complete the message computation. */ +static void +rmd160_final( rmd160_context_t *hd ) +{ + u32 t, msb, lsb; + unsigned char *p; + + rmd160_write (hd, NULL, 0); /* Flush buffer. */ + + t = hd->nblocks; + /* Multiply by 64 to make a byte count. */ + lsb = t << 6; + msb = t >> 26; + /* Add the count. */ + t = lsb; + if ((lsb += hd->count) < t) + msb++; + /* Multiply by 8 to make a bit count. */ + t = lsb; + lsb <<= 3; + msb <<= 3; + msb |= t >> 29; + + if (hd->count < 56) + { + /* Enough room. */ + hd->buf[hd->count++] = 0x80; /* Pad character. */ + while (hd->count < 56) + hd->buf[hd->count++] = 0; + } + else + { + /* Need one extra block. */ + hd->buf[hd->count++] = 0x80; /* Pad character. */ + while (hd->count < 64) + hd->buf[hd->count++] = 0; + rmd160_write (hd, NULL, 0); /* Flush buffer. */ + memset (hd->buf, 0, 56); /* Fill next block with zeroes. */ + } + /* Append the 64 bit count. */ + hd->buf[56] = lsb; + hd->buf[57] = lsb >> 8; + hd->buf[58] = lsb >> 16; + hd->buf[59] = lsb >> 24; + hd->buf[60] = msb; + hd->buf[61] = msb >> 8; + hd->buf[62] = msb >> 16; + hd->buf[63] = msb >> 24; + transform (hd, hd->buf); + + p = hd->buf; +#define X(a) do { *p++ = hd->h##a; *p++ = hd->h##a >> 8; \ + *p++ = hd->h##a >> 16; *p++ = hd->h##a >> 24; } while(0) + X(0); + X(1); + X(2); + X(3); + X(4); +#undef X +} + + +/* + * Compines function to put the hash value of the supplied BUFFER into + * OUTBUF which must have a size of 20 bytes. + */ +void +rmd160_hash_buffer (void *outbuf, const void *buffer, size_t length) +{ + rmd160_context_t hd; + + rmd160_init (&hd); + rmd160_write (&hd, buffer, length); + rmd160_final (&hd); + memcpy (outbuf, hd.buf, 20); +} diff --git a/g10/rmd160.h b/g10/rmd160.h new file mode 100644 index 0000000..f186b72 --- /dev/null +++ b/g10/rmd160.h @@ -0,0 +1,24 @@ +/* rmd160.h + * Copyright (C) 2008 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ +#ifndef G10_RMD160_H +#define G10_RMD160_H + +void rmd160_hash_buffer (void *outbuf, const void *buffer, size_t length); + +#endif /*G10_RMD160_H*/ diff --git a/g10/server.c b/g10/server.c new file mode 100644 index 0000000..60b447c --- /dev/null +++ b/g10/server.c @@ -0,0 +1,799 @@ +/* server.c - server mode for gpg + * Copyright (C) 2006, 2008 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include + + +#include "gpg.h" +#include +#include "../common/util.h" +#include "../common/i18n.h" +#include "options.h" +#include "../common/server-help.h" +#include "../common/sysutils.h" +#include "../common/status.h" + + +#define set_error(e,t) assuan_set_error (ctx, gpg_error (e), (t)) + + +/* Data used to associate an Assuan context with local server data. */ +struct server_local_s +{ + /* Our current Assuan context. */ + assuan_context_t assuan_ctx; + /* File descriptor as set by the MESSAGE command. */ + gnupg_fd_t message_fd; + + /* List of prepared recipients. */ + pk_list_t recplist; + + /* Set if pinentry notifications should be passed back to the + client. */ + int allow_pinentry_notify; +}; + + + +/* Helper to close the message fd if it is open. */ +static void +close_message_fd (ctrl_t ctrl) +{ + if (ctrl->server_local->message_fd != GNUPG_INVALID_FD) + { + assuan_sock_close (ctrl->server_local->message_fd); + ctrl->server_local->message_fd = GNUPG_INVALID_FD; + } +} + + +/* Called by libassuan for Assuan options. See the Assuan manual for + details. */ +static gpg_error_t +option_handler (assuan_context_t ctx, const char *key, const char *value) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + + (void)value; + + /* Fixme: Implement the tty and locale args. */ + if (!strcmp (key, "display")) + { + } + else if (!strcmp (key, "ttyname")) + { + } + else if (!strcmp (key, "ttytype")) + { + } + else if (!strcmp (key, "lc-ctype")) + { + } + else if (!strcmp (key, "lc-messages")) + { + } + else if (!strcmp (key, "xauthority")) + { + } + else if (!strcmp (key, "pinentry_user_data")) + { + } + else if (!strcmp (key, "list-mode")) + { + /* This is for now a dummy option. */ + } + else if (!strcmp (key, "allow-pinentry-notify")) + { + ctrl->server_local->allow_pinentry_notify = 1; + } + else + return gpg_error (GPG_ERR_UNKNOWN_OPTION); + + return 0; +} + + +/* Called by libassuan for RESET commands. */ +static gpg_error_t +reset_notify (assuan_context_t ctx, char *line) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + + (void)line; + + release_pk_list (ctrl->server_local->recplist); + ctrl->server_local->recplist = NULL; + + close_message_fd (ctrl); + assuan_close_input_fd (ctx); + assuan_close_output_fd (ctx); + return 0; +} + + +/* Called by libassuan for INPUT commands. */ +static gpg_error_t +input_notify (assuan_context_t ctx, char *line) +{ +/* ctrl_t ctrl = assuan_get_pointer (ctx); */ + + (void)ctx; + + if (strstr (line, "--armor")) + ; /* FIXME */ + else if (strstr (line, "--base64")) + ; /* FIXME */ + else if (strstr (line, "--binary")) + ; + else + { + /* FIXME (autodetect encoding) */ + } + return 0; +} + + +/* Called by libassuan for OUTPUT commands. */ +static gpg_error_t +output_notify (assuan_context_t ctx, char *line) +{ +/* ctrl_t ctrl = assuan_get_pointer (ctx); */ + + (void)ctx; + + if (strstr (line, "--armor")) + ; /* FIXME */ + else if (strstr (line, "--base64")) + { + /* FIXME */ + } + return 0; +} + + + + +/* RECIPIENT [--hidden] + RECIPIENT [--hidden] --file + + Set the recipient for the encryption. should be the + internal representation of the key; the server may accept any other + way of specification. If this is a valid and trusted recipient the + server does respond with OK, otherwise the return is an ERR with + the reason why the recipient can't be used, the encryption will + then not be done for this recipient. If the policy is not to + encrypt at all if not all recipients are valid, the client has to + take care of this. All RECIPIENT commands are cumulative until a + RESET or an successful ENCRYPT command. */ +static gpg_error_t +cmd_recipient (assuan_context_t ctx, char *line) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + gpg_error_t err; + int hidden, file; + + hidden = has_option (line,"--hidden"); + file = has_option (line,"--file"); + line = skip_options (line); + + /* FIXME: Expand groups + if (opt.grouplist) + remusr = expand_group (rcpts); + else + remusr = rcpts; + */ + + err = find_and_check_key (ctrl, line, PUBKEY_USAGE_ENC, hidden, file, + &ctrl->server_local->recplist); + + if (err) + log_error ("command '%s' failed: %s\n", "RECIPIENT", gpg_strerror (err)); + return err; +} + + + +/* SIGNER + + Set the signer's keys for the signature creation. should + be the internal representation of the key; the server may accept + any other way of specification. If this is a valid and usable + signing key the server does respond with OK, otherwise it returns + an ERR with the reason why the key can't be used, the signing will + then not be done for this key. If the policy is not to sign at all + if not all signer keys are valid, the client has to take care of + this. All SIGNER commands are cumulative until a RESET but they + are *not* reset by an SIGN command because it can be expected that + set of signers are used for more than one sign operation. + + Note that this command returns an INV_RECP status which is a bit + strange, but they are very similar. */ +static gpg_error_t +cmd_signer (assuan_context_t ctx, char *line) +{ + (void)ctx; + (void)line; + return gpg_error (GPG_ERR_NOT_SUPPORTED); +} + + + +/* ENCRYPT + + Do the actual encryption process. Takes the plaintext from the + INPUT command, writes the ciphertext to the file descriptor set + with the OUTPUT command, take the recipients from all the + recipients set so far with RECIPIENTS. + + If this command fails the clients should try to delete all output + currently done or otherwise mark it as invalid. GPG does ensure + that there won't be any security problem with leftover data on the + output in this case. + + In most cases this command won't fail because most necessary checks + have been done while setting the recipients. However some checks + can only be done right here and thus error may occur anyway (for + example, no recipients at all). + + The input, output and message pipes are closed after this + command. */ +static gpg_error_t +cmd_encrypt (assuan_context_t ctx, char *line) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + gpg_error_t err; + int inp_fd, out_fd; + + (void)line; /* LINE is not used. */ + + if ( !ctrl->server_local->recplist ) + { + write_status_text (STATUS_NO_RECP, "0"); + err = gpg_error (GPG_ERR_NO_USER_ID); + goto leave; + } + + inp_fd = translate_sys2libc_fd (assuan_get_input_fd (ctx), 0); + if (inp_fd == -1) + { + err = set_error (GPG_ERR_ASS_NO_INPUT, NULL); + goto leave; + } + out_fd = translate_sys2libc_fd (assuan_get_output_fd (ctx), 1); + if (out_fd == -1) + { + err = set_error (GPG_ERR_ASS_NO_OUTPUT, NULL); + goto leave; + } + + + /* FIXME: GPGSM does this here: Add all encrypt-to marked recipients + from the default list. */ + + /* fixme: err = ctrl->audit? 0 : start_audit_session (ctrl);*/ + + err = encrypt_crypt (ctrl, inp_fd, NULL, NULL, 0, + ctrl->server_local->recplist, + out_fd); + + leave: + /* Release the recipient list on success. */ + if (!err) + { + release_pk_list (ctrl->server_local->recplist); + ctrl->server_local->recplist = NULL; + } + + /* Close and reset the fds. */ + close_message_fd (ctrl); + assuan_close_input_fd (ctx); + assuan_close_output_fd (ctx); + + if (err) + log_error ("command '%s' failed: %s\n", "ENCRYPT", gpg_strerror (err)); + return err; +} + + + +/* DECRYPT + + This performs the decrypt operation. */ +static gpg_error_t +cmd_decrypt (assuan_context_t ctx, char *line) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + gpg_error_t err; + int inp_fd, out_fd; + + (void)line; /* LINE is not used. */ + + inp_fd = translate_sys2libc_fd (assuan_get_input_fd (ctx), 0); + if (inp_fd == -1) + return set_error (GPG_ERR_ASS_NO_INPUT, NULL); + out_fd = translate_sys2libc_fd (assuan_get_output_fd (ctx), 1); + if (out_fd == -1) + return set_error (GPG_ERR_ASS_NO_OUTPUT, NULL); + + glo_ctrl.lasterr = 0; + err = decrypt_message_fd (ctrl, inp_fd, out_fd); + if (!err) + err = glo_ctrl.lasterr; + + /* Close and reset the fds. */ + close_message_fd (ctrl); + assuan_close_input_fd (ctx); + assuan_close_output_fd (ctx); + + if (err) + log_error ("command '%s' failed: %s\n", "DECRYPT", gpg_strerror (err)); + return err; +} + + + +/* VERIFY + + This does a verify operation on the message send to the input-FD. + The result is written out using status lines. If an output FD was + given, the signed text will be written to that. + + If the signature is a detached one, the server will inquire about + the signed material and the client must provide it. + */ +static gpg_error_t +cmd_verify (assuan_context_t ctx, char *line) +{ + int rc; +#ifdef HAVE_W32_SYSTEM + (void)ctx; + (void)line; + rc = gpg_error (GPG_ERR_NOT_IMPLEMENTED); +#else + ctrl_t ctrl = assuan_get_pointer (ctx); + gnupg_fd_t fd = assuan_get_input_fd (ctx); + gnupg_fd_t out_fd = assuan_get_output_fd (ctx); + estream_t out_fp = NULL; + + /* FIXME: Revamp this code it is nearly to 3 years old and was only + intended as a quick test. */ + + (void)line; + + if (fd == GNUPG_INVALID_FD) + return gpg_error (GPG_ERR_ASS_NO_INPUT); + + if (out_fd != GNUPG_INVALID_FD) + { + es_syshd_t syshd; + +#ifdef HAVE_W32_SYSTEM + syshd.type = ES_SYSHD_HANDLE; + syshd.u.handle = out_fd; +#else + syshd.type = ES_SYSHD_FD; + syshd.u.fd = out_fd; +#endif + out_fp = es_sysopen_nc (&syshd, "w"); + if (!out_fp) + return set_error (gpg_err_code_from_syserror (), "fdopen() failed"); + } + + log_debug ("WARNING: The server mode is WORK " + "IN PROGRESS and not ready for use\n"); + + rc = gpg_verify (ctrl, fd, ctrl->server_local->message_fd, out_fp); + + es_fclose (out_fp); + close_message_fd (ctrl); + assuan_close_input_fd (ctx); + assuan_close_output_fd (ctx); +#endif + + if (rc) + log_error ("command '%s' failed: %s\n", "VERIFY", gpg_strerror (rc)); + return rc; +} + + + +/* SIGN [--detached] + + Sign the data set with the INPUT command and write it to the sink + set by OUTPUT. With "--detached" specified, a detached signature + is created. */ +static gpg_error_t +cmd_sign (assuan_context_t ctx, char *line) +{ + (void)ctx; + (void)line; + return gpg_error (GPG_ERR_NOT_SUPPORTED); +} + + + +/* IMPORT + + Import keys as read from the input-fd, return status message for + each imported one. The import checks the validity of the key. */ +static gpg_error_t +cmd_import (assuan_context_t ctx, char *line) +{ + (void)ctx; + (void)line; + return gpg_error (GPG_ERR_NOT_SUPPORTED); +} + + + +/* EXPORT [--data [--armor|--base64]] [--] pattern + + Similar to the --export command line command, this command exports + public keys matching PATTERN. The output is send to the output fd + unless the --data option has been used in which case the output + gets send inline using regular data lines. The options "--armor" + and "--base" ospecify an output format if "--data" has been used. + Recall that in general the output format is set with the OUTPUT + command. + */ +static gpg_error_t +cmd_export (assuan_context_t ctx, char *line) +{ + (void)ctx; + (void)line; + return gpg_error (GPG_ERR_NOT_SUPPORTED); +} + + + +/* DELKEYS + + Fixme +*/ +static gpg_error_t +cmd_delkeys (assuan_context_t ctx, char *line) +{ + (void)ctx; + (void)line; + return gpg_error (GPG_ERR_NOT_SUPPORTED); +} + + + +/* MESSAGE FD[=] + + Set the file descriptor to read a message which is used with + detached signatures. */ +static gpg_error_t +cmd_message (assuan_context_t ctx, char *line) +{ + int rc; + gnupg_fd_t fd; + ctrl_t ctrl = assuan_get_pointer (ctx); + + rc = assuan_command_parse_fd (ctx, line, &fd); + if (rc) + return rc; + if (fd == GNUPG_INVALID_FD) + return gpg_error (GPG_ERR_ASS_NO_INPUT); + ctrl->server_local->message_fd = fd; + return 0; +} + + + +/* LISTKEYS [] + LISTSECRETKEYS [] + + fixme +*/ +static gpg_error_t +do_listkeys (assuan_context_t ctx, char *line, int mode) +{ + (void)ctx; + (void)line; + (void)mode; + + return gpg_error (GPG_ERR_NOT_SUPPORTED); +} + + +static gpg_error_t +cmd_listkeys (assuan_context_t ctx, char *line) +{ + return do_listkeys (ctx, line, 3); +} + + +static gpg_error_t +cmd_listsecretkeys (assuan_context_t ctx, char *line) +{ + return do_listkeys (ctx, line, 2); +} + + + +/* GENKEY + + Read the parameters in native format from the input fd and create a + new OpenPGP key. + */ +static gpg_error_t +cmd_genkey (assuan_context_t ctx, char *line) +{ + (void)ctx; + (void)line; + return gpg_error (GPG_ERR_NOT_SUPPORTED); +} + + +/* GETINFO + + Multipurpose function to return a variety of information. + Supported values for WHAT are: + + version - Return the version of the program. + pid - Return the process id of the server. + + */ +static gpg_error_t +cmd_getinfo (assuan_context_t ctx, char *line) +{ + int rc; + + if (!strcmp (line, "version")) + { + const char *s = VERSION; + rc = assuan_send_data (ctx, s, strlen (s)); + } + else if (!strcmp (line, "pid")) + { + char numbuf[50]; + + snprintf (numbuf, sizeof numbuf, "%lu", (unsigned long)getpid ()); + rc = assuan_send_data (ctx, numbuf, strlen (numbuf)); + } + else + rc = set_error (GPG_ERR_ASS_PARAMETER, "unknown value for WHAT"); + return rc; +} + +static const char hlp_passwd[] = + "PASSWD \n" + "\n" + "Change the passphrase of the secret key for USERID."; +static gpg_error_t +cmd_passwd (assuan_context_t ctx, char *line) +{ + /* ctrl_t ctrl = assuan_get_pointer (ctx); */ + gpg_error_t err; + + (void)ctx; + (void)line; + /* line = skip_options (line); */ + + err = gpg_error (GPG_ERR_NOT_SUPPORTED); + + return err; +} + + + + +/* Helper to register our commands with libassuan. */ +static int +register_commands (assuan_context_t ctx) +{ + static struct + { + const char *name; + assuan_handler_t handler; + const char * const help; + } table[] = { + { "RECIPIENT", cmd_recipient }, + { "SIGNER", cmd_signer }, + { "ENCRYPT", cmd_encrypt }, + { "DECRYPT", cmd_decrypt }, + { "VERIFY", cmd_verify }, + { "SIGN", cmd_sign }, + { "IMPORT", cmd_import }, + { "EXPORT", cmd_export }, + { "INPUT", NULL }, + { "OUTPUT", NULL }, + { "MESSAGE", cmd_message }, + { "LISTKEYS", cmd_listkeys }, + { "LISTSECRETKEYS",cmd_listsecretkeys }, + { "GENKEY", cmd_genkey }, + { "DELKEYS", cmd_delkeys }, + { "GETINFO", cmd_getinfo }, + { "PASSWD", cmd_passwd, hlp_passwd}, + { NULL } + }; + int i, rc; + + for (i=0; table[i].name; i++) + { + rc = assuan_register_command (ctx, table[i].name, + table[i].handler, table[i].help); + if (rc) + return rc; + } + return 0; +} + + + + +/* Startup the server. CTRL must have been allocated by the caller + and set to the default values. */ +int +gpg_server (ctrl_t ctrl) +{ + int rc; +#ifndef HAVE_W32_SYSTEM + int filedes[2]; +#endif + assuan_context_t ctx = NULL; + static const char hello[] = ("GNU Privacy Guard's OpenPGP server " + VERSION " ready"); + + /* We use a pipe based server so that we can work from scripts. + assuan_init_pipe_server will automagically detect when we are + called with a socketpair and ignore FILEDES in this case. */ +#ifndef HAVE_W32_SYSTEM + filedes[0] = assuan_fdopen (0); + filedes[1] = assuan_fdopen (1); +#endif + rc = assuan_new (&ctx); + if (rc) + { + log_error ("failed to allocate the assuan context: %s\n", + gpg_strerror (rc)); + goto leave; + } + +#ifdef HAVE_W32_SYSTEM + rc = gpg_error (GPG_ERR_NOT_IMPLEMENTED); +#else + rc = assuan_init_pipe_server (ctx, filedes); +#endif + if (rc) + { + log_error ("failed to initialize the server: %s\n", gpg_strerror (rc)); + goto leave; + } + + rc = register_commands (ctx); + if (rc) + { + log_error ("failed to the register commands with Assuan: %s\n", + gpg_strerror(rc)); + goto leave; + } + + assuan_set_pointer (ctx, ctrl); + if (opt.verbose || opt.debug) + { + char *tmp; + + tmp = xtryasprintf ("Home: %s\n" + "Config: %s\n" + "%s", + gnupg_homedir (), + "fixme: need config filename", + hello); + if (tmp) + { + assuan_set_hello_line (ctx, tmp); + xfree (tmp); + } + } + else + assuan_set_hello_line (ctx, hello); + assuan_register_reset_notify (ctx, reset_notify); + assuan_register_input_notify (ctx, input_notify); + assuan_register_output_notify (ctx, output_notify); + assuan_register_option_handler (ctx, option_handler); + + ctrl->server_local = xtrycalloc (1, sizeof *ctrl->server_local); + if (!ctrl->server_local) + { + rc = gpg_error_from_syserror (); + goto leave; + } + ctrl->server_local->assuan_ctx = ctx; + ctrl->server_local->message_fd = GNUPG_INVALID_FD; + + for (;;) + { + rc = assuan_accept (ctx); + if (rc == -1) + { + rc = 0; + break; + } + else if (rc) + { + log_info ("Assuan accept problem: %s\n", gpg_strerror (rc)); + break; + } + + rc = assuan_process (ctx); + if (rc) + { + log_info ("Assuan processing failed: %s\n", gpg_strerror (rc)); + continue; + } + } + + leave: + if (ctrl->server_local) + { + release_pk_list (ctrl->server_local->recplist); + + xfree (ctrl->server_local); + ctrl->server_local = NULL; + } + assuan_release (ctx); + return rc; +} + + +/* Helper to notify the client about Pinentry events. Because that + might disturb some older clients, this is only done when enabled + via an option. If it is not enabled we tell Windows to allow + setting the foreground window right here. Returns an gpg error + code. */ +gpg_error_t +gpg_proxy_pinentry_notify (ctrl_t ctrl, const unsigned char *line) +{ + const char *s; + + if (opt.verbose + && !strncmp (line, "PINENTRY_LAUNCHED", 17) + && (line[17]==' '||!line[17])) + { + for (s = line + 17; *s && spacep (s); s++) + ; + log_info (_("pinentry launched (%s)\n"), s); + } + + if (!ctrl || !ctrl->server_local + || !ctrl->server_local->allow_pinentry_notify) + { + gnupg_allow_set_foregound_window ((pid_t)strtoul (line+17, NULL, 10)); + /* Client might be interested in that event - send as status line. */ + if (!strncmp (line, "PINENTRY_LAUNCHED", 17) + && (line[17]==' '||!line[17])) + { + for (line += 17; *line && spacep (line); line++) + ; + write_status_text (STATUS_PINENTRY_LAUNCHED, line); + } + return 0; + } + return assuan_inquire (ctrl->server_local->assuan_ctx, line, NULL, NULL, 0); +} diff --git a/g10/seskey.c b/g10/seskey.c new file mode 100644 index 0000000..1549017 --- /dev/null +++ b/g10/seskey.c @@ -0,0 +1,359 @@ +/* seskey.c - make session keys etc. + * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, + * 2006, 2009, 2010 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include +#include +#include +#include + +#include "gpg.h" +#include "../common/util.h" +#include "options.h" +#include "main.h" +#include "../common/i18n.h" + + +/* Generate a new session key in *DEK that is appropriate for the + * algorithm DEK->ALGO (i.e., ensure that the key is not weak). + * + * This function overwrites DEK->KEYLEN, DEK->KEY. The rest of the + * fields are left as is. */ +void +make_session_key( DEK *dek ) +{ + gcry_cipher_hd_t chd; + int i, rc; + + dek->keylen = openpgp_cipher_get_algo_keylen (dek->algo); + + if (openpgp_cipher_open (&chd, dek->algo, GCRY_CIPHER_MODE_CFB, + (GCRY_CIPHER_SECURE + | (dek->algo >= 100 ? + 0 : GCRY_CIPHER_ENABLE_SYNC))) ) + BUG(); + gcry_randomize (dek->key, dek->keylen, GCRY_STRONG_RANDOM ); + for (i=0; i < 16; i++ ) + { + rc = gcry_cipher_setkey (chd, dek->key, dek->keylen); + if (!rc) + { + gcry_cipher_close (chd); + return; + } + if (gpg_err_code (rc) != GPG_ERR_WEAK_KEY) + BUG(); + log_info(_("weak key created - retrying\n") ); + /* Renew the session key until we get a non-weak key. */ + gcry_randomize (dek->key, dek->keylen, GCRY_STRONG_RANDOM); + } + log_fatal (_("cannot avoid weak key for symmetric cipher; " + "tried %d times!\n"), i); +} + + +/* Encode the session key stored in DEK as an MPI in preparation to + * encrypt it with the public key algorithm OPENPGP_PK_ALGO with a key + * whose length (the size of the public key) is NBITS. + * + * On success, returns an MPI, which the caller must free using + * gcry_mpi_release(). */ +gcry_mpi_t +encode_session_key (int openpgp_pk_algo, DEK *dek, unsigned int nbits) +{ + size_t nframe = (nbits+7) / 8; + byte *p; + byte *frame; + int i,n; + u16 csum; + gcry_mpi_t a; + + if (DBG_CRYPTO) + log_debug ("encode_session_key: encoding %d byte DEK", dek->keylen); + + csum = 0; + for (p = dek->key, i=0; i < dek->keylen; i++) + csum += *p++; + + /* Shortcut for ECDH. It's padding is minimal to simply make the + output be a multiple of 8 bytes. */ + if (openpgp_pk_algo == PUBKEY_ALGO_ECDH) + { + /* Pad to 8 byte granulatiry; the padding byte is the number of + * padded bytes. + * + * A DEK(k bytes) CSUM(2 bytes) 0x 0x 0x 0x ... 0x + * +---- x times ---+ + */ + nframe = (( 1 + dek->keylen + 2 /* The value so far is always odd. */ + + 7 ) & (~7)); + + /* alg+key+csum fit and the size is congruent to 8. */ + log_assert (!(nframe%8) && nframe > 1 + dek->keylen + 2 ); + + frame = xmalloc_secure (nframe); + n = 0; + frame[n++] = dek->algo; + memcpy (frame+n, dek->key, dek->keylen); + n += dek->keylen; + frame[n++] = csum >> 8; + frame[n++] = csum; + i = nframe - n; /* Number of padded bytes. */ + memset (frame+n, i, i); /* Use it as the value of each padded byte. */ + log_assert (n+i == nframe); + + if (DBG_CRYPTO) + log_debug ("encode_session_key: " + "[%d] %02x %02x %02x ... %02x %02x %02x\n", + (int) nframe, frame[0], frame[1], frame[2], + frame[nframe-3], frame[nframe-2], frame[nframe-1]); + + if (gcry_mpi_scan (&a, GCRYMPI_FMT_USG, frame, nframe, &nframe)) + BUG(); + xfree(frame); + return 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 according to PKCS#1 v1.5 (see section + * 13.1.1 of RFC 4880): + * + * 0 2 RND(i 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 (at least 1) 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 + */ + + frame = xmalloc_secure( nframe ); + n = 0; + frame[n++] = 0; + frame[n++] = 2; + /* The number of random bytes are the number of otherwise unused + bytes. See diagram above. */ + i = nframe - 6 - dek->keylen; + log_assert( i > 0 ); + p = gcry_random_bytes_secure (i, GCRY_STRONG_RANDOM); + /* Replace zero bytes by new values. */ + for (;;) + { + int j, k; + byte *pp; + + /* Count the zero bytes. */ + for (j=k=0; j < i; j++ ) + if (!p[j]) + k++; + if (!k) + break; /* Okay: no zero bytes. */ + k += k/128 + 3; /* Better get some more. */ + pp = gcry_random_bytes_secure (k, GCRY_STRONG_RANDOM); + 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; + log_assert (n == nframe); + if (gcry_mpi_scan( &a, GCRYMPI_FMT_USG, frame, n, &nframe)) + BUG(); + xfree (frame); + return a; +} + + +static gcry_mpi_t +do_encode_md( gcry_md_hd_t md, int algo, size_t len, unsigned nbits, + const byte *asn, size_t asnlen ) +{ + size_t nframe = (nbits+7) / 8; + byte *frame; + int i,n; + gcry_mpi_t a; + + if (len + asnlen + 4 > nframe) + { + log_error ("can't encode a %d bit MD into a %d bits frame, algo=%d\n", + (int)(len*8), (int)nbits, algo); + return NULL; + } + + /* 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 = gcry_md_is_secure (md)? xmalloc_secure (nframe) : xmalloc (nframe); + n = 0; + frame[n++] = 0; + frame[n++] = 1; /* block type */ + i = nframe - len - asnlen -3 ; + log_assert( i > 1 ); + memset( frame+n, 0xff, i ); n += i; + frame[n++] = 0; + memcpy( frame+n, asn, asnlen ); n += asnlen; + memcpy( frame+n, gcry_md_read (md, algo), len ); n += len; + log_assert( n == nframe ); + + if (gcry_mpi_scan( &a, GCRYMPI_FMT_USG, frame, n, &nframe )) + BUG(); + 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. + */ +gcry_mpi_t +encode_md_value (PKT_public_key *pk, gcry_md_hd_t md, int hash_algo) +{ + gcry_mpi_t frame; + size_t mdlen; + + log_assert (hash_algo); + log_assert (pk); + + if (pk->pubkey_algo == PUBKEY_ALGO_EDDSA) + { + /* EdDSA signs data of arbitrary length. Thus no special + treatment is required. */ + frame = gcry_mpi_set_opaque_copy (NULL, gcry_md_read (md, hash_algo), + 8*gcry_md_get_algo_dlen (hash_algo)); + } + else if (pk->pubkey_algo == PUBKEY_ALGO_DSA + || pk->pubkey_algo == PUBKEY_ALGO_ECDSA) + { + /* It's a DSA signature, so find out the size of q. */ + + size_t qbits = gcry_mpi_get_nbits (pk->pkey[1]); + + /* pkey[1] is Q for ECDSA, which is an uncompressed point, + i.e. 04 */ + if (pk->pubkey_algo == PUBKEY_ALGO_ECDSA) + qbits = ecdsa_qbits_from_Q (qbits); + + /* Make sure it is a multiple of 8 bits. */ + if ((qbits%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 (qbits < 160) + { + log_error (_("%s key %s uses an unsafe (%zu bit) hash\n"), + openpgp_pk_algo_name (pk->pubkey_algo), + keystr_from_pk (pk), qbits); + return NULL; + } + + + /* ECDSA 521 is special has it is larger than the largest hash + we have (SHA-512). Thus we change the size for further + processing to 512. */ + if (pk->pubkey_algo == PUBKEY_ALGO_ECDSA && qbits > 512) + qbits = 512; + + /* Check if we're too short. Too long is safe as we'll + automatically left-truncate. */ + mdlen = gcry_md_get_algo_dlen (hash_algo); + if (mdlen < qbits/8) + { + log_error (_("%s key %s requires a %zu bit or larger hash " + "(hash is %s)\n"), + openpgp_pk_algo_name (pk->pubkey_algo), + keystr_from_pk (pk), qbits, + gcry_md_algo_name (hash_algo)); + return NULL; + } + + /* Note that we do the truncation by passing QBITS/8 as length to + mpi_scan. */ + if (gcry_mpi_scan (&frame, GCRYMPI_FMT_USG, + gcry_md_read (md, hash_algo), qbits/8, NULL)) + BUG(); + } + else + { + gpg_error_t rc; + byte *asn; + size_t asnlen; + + rc = gcry_md_algo_info (hash_algo, GCRYCTL_GET_ASNOID, NULL, &asnlen); + if (rc) + log_fatal ("can't get OID of digest algorithm %d: %s\n", + hash_algo, gpg_strerror (rc)); + asn = xtrymalloc (asnlen); + if (!asn) + return NULL; + if ( gcry_md_algo_info (hash_algo, GCRYCTL_GET_ASNOID, asn, &asnlen) ) + BUG(); + frame = do_encode_md (md, hash_algo, gcry_md_get_algo_dlen (hash_algo), + gcry_mpi_get_nbits (pk->pkey[0]), asn, asnlen); + xfree (asn); + } + + return frame; +} diff --git a/g10/sig-check.c b/g10/sig-check.c new file mode 100644 index 0000000..eeaf6f0 --- /dev/null +++ b/g10/sig-check.c @@ -0,0 +1,1229 @@ +/* sig-check.c - Check a signature + * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, + * 2004, 2006 Free Software Foundation, Inc. + * Copyright (C) 2015, 2016 g10 Code GmbH + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include +#include +#include +#include + +#include "gpg.h" +#include "../common/util.h" +#include "packet.h" +#include "keydb.h" +#include "main.h" +#include "../common/status.h" +#include "../common/i18n.h" +#include "options.h" +#include "pkglue.h" +#include "../common/compliance.h" + +static int check_signature_end (PKT_public_key *pk, PKT_signature *sig, + gcry_md_hd_t digest, + int *r_expired, int *r_revoked, + PKT_public_key *ret_pk); + +static int check_signature_end_simple (PKT_public_key *pk, PKT_signature *sig, + gcry_md_hd_t digest); + + +/* Statistics for signature verification. */ +struct +{ + unsigned int total; /* Total number of verifications. */ + unsigned int cached; /* Number of seen cache entries. */ + unsigned int goodsig;/* Number of good verifications from the cache. */ + unsigned int badsig; /* Number of bad verifications from the cache. */ +} cache_stats; + + +/* Dump verification stats. */ +void +sig_check_dump_stats (void) +{ + log_info ("sig_cache: total=%u cached=%u good=%u bad=%u\n", + cache_stats.total, cache_stats.cached, + cache_stats.goodsig, cache_stats.badsig); +} + + +static gpg_error_t +check_key_verify_compliance (PKT_public_key *pk) +{ + gpg_error_t err = 0; + + if (!gnupg_pk_is_allowed (opt.compliance, PK_USE_VERIFICATION, + pk->pubkey_algo, 0, pk->pkey, + nbits_from_pk (pk), + NULL)) + { + /* Compliance failure. */ + log_info (_("key %s may not be used for signing in %s mode\n"), + keystr_from_pk (pk), + gnupg_compliance_option_string (opt.compliance)); + if (opt.flags.override_compliance_check) + log_info (_("continuing verification anyway due to option %s\n"), + "--override-compliance-failure"); + else + { + log_inc_errorcount (); /* We used log info above. */ + err = gpg_error (GPG_ERR_PUBKEY_ALGO); + } + } + + return err; +} + + + +/* Check a signature. This is shorthand for check_signature2 with + the unnamed arguments passed as NULL. */ +int +check_signature (ctrl_t ctrl, PKT_signature *sig, gcry_md_hd_t digest) +{ + return check_signature2 (ctrl, sig, digest, NULL, NULL, NULL, NULL, NULL); +} + + +/* Check a signature. + * + * Looks up the public key that created the signature (SIG->KEYID) + * from the key db. Makes sure that the signature is valid (it was + * not created prior to the key, the public key was created in the + * past, and the signature does not include any unsupported critical + * features), finishes computing the hash of the signature data, and + * checks that the signature verifies the digest. If the key that + * generated the signature is a subkey, this function also verifies + * that there is a valid backsig from the subkey to the primary key. + * Finally, if status fd is enabled and the signature class is 0x00 or + * 0x01, then a STATUS_SIG_ID is emitted on the status fd. + * + * SIG is the signature to check. + * + * DIGEST contains a valid hash context that already includes the + * signed data. This function adds the relevant meta-data from the + * signature packet to compute the final hash. (See Section 5.2 of + * RFC 4880: "The concatenation of the data being signed and the + * signature data from the version number through the hashed subpacket + * data (inclusive) is hashed.") + * + * If FORCED_PK is not NULL this public key is used to verify the + * signature and no other public key is looked up. + * + * If R_EXPIREDATE is not NULL, R_EXPIREDATE is set to the key's + * expiry. + * + * If R_EXPIRED is not NULL, *R_EXPIRED is set to 1 if PK has expired + * (0 otherwise). Note: PK being expired does not cause this function + * to fail. + * + * If R_REVOKED is not NULL, *R_REVOKED is set to 1 if PK has been + * revoked (0 otherwise). Note: PK being revoked does not cause this + * function to fail. + * + * If R_PK is not NULL, the public key is stored at that address if it + * was found; other wise NULL is stored. + * + * Returns 0 on success. An error code otherwise. */ +gpg_error_t +check_signature2 (ctrl_t ctrl, + PKT_signature *sig, gcry_md_hd_t digest, + PKT_public_key *forced_pk, + u32 *r_expiredate, + int *r_expired, int *r_revoked, PKT_public_key **r_pk) +{ + int rc=0; + PKT_public_key *pk; + + if (r_expiredate) + *r_expiredate = 0; + if (r_expired) + *r_expired = 0; + if (r_revoked) + *r_revoked = 0; + if (r_pk) + *r_pk = NULL; + + pk = xtrycalloc (1, sizeof *pk); + if (!pk) + return gpg_error_from_syserror (); + + if ((rc=openpgp_md_test_algo(sig->digest_algo))) + { + /* We don't have this digest. */ + } + else if (!gnupg_digest_is_allowed (opt.compliance, 0, sig->digest_algo)) + { + /* Compliance failure. */ + log_info (_("digest algorithm '%s' may not be used in %s mode\n"), + gcry_md_algo_name (sig->digest_algo), + gnupg_compliance_option_string (opt.compliance)); + rc = gpg_error (GPG_ERR_DIGEST_ALGO); + } + else if ((rc=openpgp_pk_test_algo(sig->pubkey_algo))) + { + /* We don't have this pubkey algo. */ + } + else if (!gcry_md_is_enabled (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 = gpg_error (GPG_ERR_GENERAL); + } + else if (get_pubkey_for_sig (ctrl, pk, sig, forced_pk)) + rc = gpg_error (GPG_ERR_NO_PUBKEY); + else if ((rc = check_key_verify_compliance (pk))) + ;/* Compliance failure. */ + else if (!pk->flags.valid) + { + /* You cannot have a good sig from an invalid key. */ + rc = gpg_error (GPG_ERR_BAD_PUBKEY); + } + else + { + if (r_expiredate) + *r_expiredate = pk->expiredate; + + rc = check_signature_end (pk, sig, digest, r_expired, r_revoked, NULL); + + /* Check the backsig. This is a back signature (0x19) 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 signatures issued by it. */ + if (!rc && !pk->flags.primary && pk->flags.backsig < 2) + { + if (!pk->flags.backsig) + { + log_info (_("WARNING: signing subkey %s is not" + " cross-certified\n"),keystr_from_pk(pk)); + log_info (_("please see %s for more information\n"), + "https://gnupg.org/faq/subkey-cross-certify.html"); + /* The default option --require-cross-certification + * makes this warning an error. */ + if (opt.flags.require_cross_cert) + rc = gpg_error (GPG_ERR_GENERAL); + } + else if(pk->flags.backsig == 1) + { + log_info (_("WARNING: signing subkey %s has an invalid" + " cross-certification\n"), keystr_from_pk(pk)); + rc = gpg_error (GPG_ERR_GENERAL); + } + } + + } + + 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. + * + * Note that before 2.0.10, we used RIPE-MD160 for the hash + * and accidentally didn't include the timestamp and algorithm + * information in the hash. Given that this feature is not + * commonly used and that a replay attacks detection should + * not solely be based on this feature (because it does not + * work with RSA), we take the freedom and switch to SHA-1 + * with 2.0.10 to take advantage of hardware supported SHA-1 + * implementations. We also include the missing information + * in the hash. Note also the SIG_ID as computed by gpg 1.x + * and gpg 2.x didn't matched either because 2.x used to print + * MPIs not in PGP format. */ + u32 a = sig->timestamp; + int nsig = pubkey_get_nsig (sig->pubkey_algo); + unsigned char *p, *buffer; + size_t n, nbytes; + int i; + char hashbuf[20]; + + nbytes = 6; + for (i=0; i < nsig; i++ ) + { + if (gcry_mpi_print (GCRYMPI_FMT_USG, NULL, 0, &n, sig->data[i])) + BUG(); + nbytes += n; + } + + /* Make buffer large enough to be later used as output buffer. */ + if (nbytes < 100) + nbytes = 100; + nbytes += 10; /* Safety margin. */ + + /* Fill and hash buffer. */ + buffer = p = xmalloc (nbytes); + *p++ = sig->pubkey_algo; + *p++ = sig->digest_algo; + *p++ = (a >> 24) & 0xff; + *p++ = (a >> 16) & 0xff; + *p++ = (a >> 8) & 0xff; + *p++ = a & 0xff; + nbytes -= 6; + for (i=0; i < nsig; i++ ) + { + if (gcry_mpi_print (GCRYMPI_FMT_PGP, p, nbytes, &n, sig->data[i])) + BUG(); + p += n; + nbytes -= n; + } + gcry_md_hash_buffer (GCRY_MD_SHA1, hashbuf, buffer, p-buffer); + + p = make_radix64_string (hashbuf, 20); + sprintf (buffer, "%s %s %lu", + p, strtimestamp (sig->timestamp), (ulong)sig->timestamp); + xfree (p); + write_status_text (STATUS_SIG_ID, buffer); + xfree (buffer); + } + + if (r_pk) + *r_pk = pk; + else + { + release_public_key_parts (pk); + xfree (pk); + } + + return rc; +} + + +/* The signature SIG was generated with the public key PK. Check + * whether the signature is valid in the following sense: + * + * - Make sure the public key was created before the signature was + * generated. + * + * - Make sure the public key was created in the past + * + * - Check whether PK has expired (set *R_EXPIRED to 1 if so and 0 + * otherwise) + * + * - Check whether PK has been revoked (set *R_REVOKED to 1 if so + * and 0 otherwise). + * + * If either of the first two tests fail, returns an error code. + * Otherwise returns 0. (Thus, this function doesn't fail if the + * public key is expired or revoked.) */ +static int +check_signature_metadata_validity (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; + if ( d < 86400 ) + { + log_info (ngettext + ("public key %s is %lu second newer than the signature\n", + "public key %s is %lu seconds newer than the signature\n", + d), keystr_from_pk (pk), d); + } + else + { + d /= 86400; + log_info (ngettext + ("public key %s is %lu day newer than the signature\n", + "public key %s is %lu days newer than the signature\n", + d), keystr_from_pk (pk), d); + } + if (!opt.ignore_time_conflict) + return GPG_ERR_TIME_CONFLICT; /* pubkey newer than signature. */ + } + + cur_time = make_timestamp (); + if (pk->timestamp > cur_time) + { + ulong d = pk->timestamp - cur_time; + if (d < 86400) + { + log_info (ngettext("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", + d), keystr_from_pk (pk), d); + } + else + { + d /= 86400; + log_info (ngettext("key %s was created %lu day" + " in the future (time warp or clock problem)\n", + "key %s was created %lu days" + " in the future (time warp or clock problem)\n", + d), keystr_from_pk (pk), d); + } + if (!opt.ignore_time_conflict) + return GPG_ERR_TIME_CONFLICT; + } + + /* Check whether the key has expired. We check the has_expired + * flag which is set after a full evaluation of the key (getkey.c) + * as well as a simple compare to the current time in case the + * merge has for whatever reasons not been done. */ + if (pk->has_expired || (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 ) ); + snprintf (buf, sizeof buf, "%lu",(ulong)pk->expiredate); + write_status_text (STATUS_KEYEXPIRED, buf); + if (r_expired) + *r_expired = 1; + } + + if (pk->flags.revoked) + { + if (opt.verbose) + log_info (_("Note: signature key %s has been revoked\n"), + keystr_from_pk(pk)); + if (r_revoked) + *r_revoked=1; + } + + return 0; +} + + +/* Finish generating a signature and check it. Concretely: make sure + * that the signature is valid (it was not created prior to the key, + * the public key was created in the past, and the signature does not + * include any unsupported critical features), finish computing the + * digest by adding the relevant data from the signature packet, and + * check that the signature verifies the digest. + * + * DIGEST contains a hash context, which has already hashed the signed + * data. This function adds the relevant meta-data from the signature + * packet to compute the final hash. (See Section 5.2 of RFC 4880: + * "The concatenation of the data being signed and the signature data + * from the version number through the hashed subpacket data + * (inclusive) is hashed.") + * + * SIG is the signature to check. + * + * PK is the public key used to generate the signature. + * + * If R_EXPIRED is not NULL, *R_EXPIRED is set to 1 if PK has expired + * (0 otherwise). Note: PK being expired does not cause this function + * to fail. + * + * If R_REVOKED is not NULL, *R_REVOKED is set to 1 if PK has been + * revoked (0 otherwise). Note: PK being revoked does not cause this + * function to fail. + * + * If RET_PK is not NULL, PK is copied into RET_PK on success. + * + * Returns 0 on success. An error code other. */ +static int +check_signature_end (PKT_public_key *pk, PKT_signature *sig, + gcry_md_hd_t digest, + int *r_expired, int *r_revoked, PKT_public_key *ret_pk) +{ + int rc = 0; + + if ((rc = check_signature_metadata_validity (pk, sig, + r_expired, r_revoked))) + return rc; + + if ((rc = check_signature_end_simple (pk, sig, digest))) + return rc; + + if (!rc && ret_pk) + copy_public_key(ret_pk,pk); + + return rc; +} + + +/* This function is similar to check_signature_end, but it only checks + * whether the signature was generated by PK. It does not check + * expiration, revocation, etc. */ +static int +check_signature_end_simple (PKT_public_key *pk, PKT_signature *sig, + gcry_md_hd_t digest) +{ + gcry_mpi_t result = NULL; + int rc = 0; + + if (!opt.flags.allow_weak_digest_algos) + { + if (is_weak_digest (sig->digest_algo)) + { + print_digest_rejected_note (sig->digest_algo); + return GPG_ERR_DIGEST_ALGO; + } + } + + /* For key signatures check that the key has a cert usage. We may + * do this only for subkeys because the primary may always issue key + * signature. The latter may not be reflected in the pubkey_usage + * field because we need to check the key signatures to extract the + * key usage. */ + if (!pk->flags.primary + && IS_CERT (sig) && !(pk->pubkey_usage & PUBKEY_USAGE_CERT)) + { + rc = gpg_error (GPG_ERR_WRONG_KEY_USAGE); + if (!opt.quiet) + log_info (_("bad key signature from key %s: %s (0x%02x, 0x%x)\n"), + keystr_from_pk (pk), gpg_strerror (rc), + sig->sig_class, pk->pubkey_usage); + return rc; + } + + /* For data signatures check that the key has sign usage. */ + if (!IS_BACK_SIG (sig) && IS_SIG (sig) + && !(pk->pubkey_usage & PUBKEY_USAGE_SIG)) + { + rc = gpg_error (GPG_ERR_WRONG_KEY_USAGE); + if (!opt.quiet) + log_info (_("bad data signature from key %s: %s (0x%02x, 0x%x)\n"), + keystr_from_pk (pk), gpg_strerror (rc), + sig->sig_class, pk->pubkey_usage); + return rc; + } + + /* Make sure the digest algo is enabled (in case of a detached + * signature). */ + gcry_md_enable (digest, sig->digest_algo); + + /* Complete the digest. */ + if (sig->version >= 4) + gcry_md_putc (digest, sig->version); + + gcry_md_putc( digest, sig->sig_class ); + if (sig->version < 4) + { + u32 a = sig->timestamp; + gcry_md_putc (digest, ((a >> 24) & 0xff)); + gcry_md_putc (digest, ((a >> 16) & 0xff)); + gcry_md_putc (digest, ((a >> 8) & 0xff)); + gcry_md_putc (digest, ( a & 0xff)); + } + else + { + byte buf[6]; + size_t n; + gcry_md_putc (digest, sig->pubkey_algo); + gcry_md_putc (digest, sig->digest_algo); + if (sig->hashed) + { + n = sig->hashed->len; + gcry_md_putc (digest, (n >> 8) ); + gcry_md_putc (digest, n ); + gcry_md_write (digest, sig->hashed->data, n); + n += 6; + } + else + { + /* Two octets for the (empty) length of the hashed + * section. */ + gcry_md_putc (digest, 0); + gcry_md_putc (digest, 0); + n = 6; + } + /* Add some magic per Section 5.2.4 of RFC 4880. */ + buf[0] = sig->version; + buf[1] = 0xff; + buf[2] = n >> 24; + buf[3] = n >> 16; + buf[4] = n >> 8; + buf[5] = n; + gcry_md_write( digest, buf, 6 ); + } + gcry_md_final( digest ); + + /* Convert the digest to an MPI. */ + result = encode_md_value (pk, digest, sig->digest_algo ); + if (!result) + return GPG_ERR_GENERAL; + + /* Verify the signature. */ + rc = pk_verify (pk->pubkey_algo, result, sig->data, pk->pkey); + gcry_mpi_release (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 = GPG_ERR_BAD_SIGNATURE; + } + + return rc; +} + + +/* Add a uid node to a hash context. See section 5.2.4, paragraph 4 + * of RFC 4880. */ +static void +hash_uid_packet (PKT_user_id *uid, gcry_md_hd_t md, PKT_signature *sig ) +{ + 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; + gcry_md_write( md, buf, 5 ); + } + gcry_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; + gcry_md_write( md, buf, 5 ); + } + gcry_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 (gpg_err_code (result) == GPG_ERR_BAD_SIGNATURE) + { + sig->flags.checked = 1; + sig->flags.valid = 0; + } + else + { + sig->flags.checked = 0; + sig->flags.valid = 0; + } +} + + +/* SIG is a key revocation signature. Check if this signature was + * generated by any of the public key PK's designated revokers. + * + * PK is the public key that SIG allegedly revokes. + * + * SIG is the revocation signature to check. + * + * This function avoids infinite recursion, which can happen if two + * keys are designed revokers for each other and they revoke each + * other. This is done by observing that if a key A is revoked by key + * B we still consider the revocation to be valid even if B is + * revoked. Thus, we don't need to determine whether B is revoked to + * determine whether A has been revoked by B, we just need to check + * the signature. + * + * Returns 0 if sig is valid (i.e. pk is revoked), non-0 if not + * revoked. We are careful to make sure that GPG_ERR_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. + * + * XXX: 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 */ +int +check_revocation_keys (ctrl_t ctrl, PKT_public_key *pk, PKT_signature *sig) +{ + static int busy=0; + int i; + int rc = GPG_ERR_GENERAL; + + log_assert (IS_KEY_REV(sig)); + log_assert ((sig->keyid[0]!=pk->keyid[0]) || (sig->keyid[0]!=pk->keyid[1])); + + /* Avoid infinite recursion. Consider the following: + * + * - We want to check if A is revoked. + * + * - C is a designated revoker for B and has revoked B. + * + * - B is a designated revoker for A and has revoked A. + * + * When checking if A is revoked (in merge_selfsigs_main), we + * observe that A has a designed revoker. As such, we call this + * function. This function sees that there is a valid revocation + * signature, which is signed by B. It then calls check_signature() + * to verify that the signature is good. To check the sig, we need + * to lookup B. Looking up B means calling merge_selfsigs_main, + * which checks whether B is revoked, which calls this function to + * see if B was revoked by some key. + * + * In this case, the added level of indirection doesn't hurt. It + * just means a bit more work. However, if C == A, then we'd end up + * in a loop. But, it doesn't make sense to look up C anyways: even + * if B is revoked, we conservatively consider a valid revocation + * signed by B to revoke A. Since this is the only place where this + * type of recursion can occur, we simply cause this function to + * fail if it is entered recursively. */ + 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->flags.dont_cache = 1; + return rc; + } + + busy=1; + + /* es_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;inumrevkeys;i++) + { + /* The revoker's keyid. */ + u32 keyid[2]; + + keyid_from_fingerprint (ctrl, pk->revkey[i].fpr, + MAX_FINGERPRINT_LEN, keyid); + + if(keyid[0]==sig->keyid[0] && keyid[1]==sig->keyid[1]) + /* The signature was generated by a designated revoker. + Verify the signature. */ + { + gcry_md_hd_t md; + + if (gcry_md_open (&md, sig->digest_algo, 0)) + BUG (); + hash_public_key(md,pk); + /* Note: check_signature only checks that the signature + is good. It does not fail if the key is revoked. */ + rc = check_signature (ctrl, sig, md); + cache_sig_result(sig,rc); + gcry_md_close (md); + break; + } + } + + busy=0; + + return rc; +} + +/* Check that the backsig BACKSIG from the subkey SUB_PK to its + * primary key MAIN_PK is valid. + * + * 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) +{ + gcry_md_hd_t md; + int rc; + + /* Always check whether the algorithm is available. Although + gcry_md_open would throw an error, some libgcrypt versions will + print a debug message in that case too. */ + if ((rc=openpgp_md_test_algo (backsig->digest_algo))) + return rc; + + if(!opt.no_sig_cache && backsig->flags.checked) + return backsig->flags.valid? 0 : gpg_error (GPG_ERR_BAD_SIGNATURE); + + rc = gcry_md_open (&md, backsig->digest_algo,0); + if (!rc) + { + hash_public_key(md,main_pk); + hash_public_key(md,sub_pk); + rc = check_signature_end (sub_pk, backsig, md, NULL, NULL, NULL); + cache_sig_result(backsig,rc); + gcry_md_close(md); + } + + return rc; +} + + +/* Check that a signature over a key is valid. This is a + * specialization of check_key_signature2 with the unnamed parameters + * passed as NULL. See the documentation for that function for more + * details. */ +int +check_key_signature (ctrl_t ctrl, kbnode_t root, kbnode_t node, + int *is_selfsig) +{ + return check_key_signature2 (ctrl, root, node, NULL, NULL, + is_selfsig, NULL, NULL); +} + + +/* Returns whether SIGNER generated the signature SIG over the packet + * PACKET, which is a key, subkey or uid, and comes from the key block + * KB. (KB is PACKET's corresponding keyblock; we don't assume that + * SIG has been added to the keyblock.) + * + * If SIGNER is set, then checks whether SIGNER generated the + * signature. Otherwise, uses SIG->KEYID to find the alleged signer. + * This parameter can be used to effectively override the alleged + * signer that is stored in SIG. + * + * KB may be NULL if SIGNER is set. + * + * Unlike check_key_signature, this function ignores any cached + * results! That is, it does not consider SIG->FLAGS.CHECKED and + * SIG->FLAGS.VALID nor does it set them. + * + * This doesn't check the signature's semantic mean. Concretely, it + * doesn't check whether a non-self signed revocation signature was + * created by a designated revoker. In fact, it doesn't return an + * error for a binding generated by a completely different key! + * + * Returns 0 if the signature is valid. Returns GPG_ERR_SIG_CLASS if + * this signature can't be over PACKET. Returns GPG_ERR_NOT_FOUND if + * the key that generated the signature (according to SIG) could not + * be found. Returns GPG_ERR_BAD_SIGNATURE if the signature is bad. + * Other errors codes may be returned if something else goes wrong. + * + * IF IS_SELFSIG is not NULL, sets *IS_SELFSIG to 1 if this is a + * self-signature (by the key's primary key) or 0 if not. + * + * If RET_PK is not NULL, returns a copy of the public key that + * generated the signature (i.e., the signer) on success. This must + * be released by the caller using release_public_key_parts (). */ +gpg_error_t +check_signature_over_key_or_uid (ctrl_t ctrl, PKT_public_key *signer, + PKT_signature *sig, KBNODE kb, PACKET *packet, + int *is_selfsig, PKT_public_key *ret_pk) +{ + int rc; + PKT_public_key *pripk = kb->pkt->pkt.public_key; + gcry_md_hd_t md; + int signer_alloced = 0; + int stub_is_selfsig; + + if (!is_selfsig) + is_selfsig = &stub_is_selfsig; + + rc = openpgp_pk_test_algo (sig->pubkey_algo); + if (rc) + return rc; + rc = openpgp_md_test_algo (sig->digest_algo); + if (rc) + return rc; + + /* A signature's class indicates the type of packet that it + signs. */ + if (IS_BACK_SIG (sig) || IS_KEY_SIG (sig) || IS_KEY_REV (sig)) + { + /* Key revocations can only be over primary keys. */ + if (packet->pkttype != PKT_PUBLIC_KEY) + return gpg_error (GPG_ERR_SIG_CLASS); + } + else if (IS_SUBKEY_SIG (sig) || IS_SUBKEY_REV (sig)) + { + if (packet->pkttype != PKT_PUBLIC_SUBKEY) + return gpg_error (GPG_ERR_SIG_CLASS); + } + else if (IS_UID_SIG (sig) || IS_UID_REV (sig)) + { + if (packet->pkttype != PKT_USER_ID) + return gpg_error (GPG_ERR_SIG_CLASS); + } + else + return gpg_error (GPG_ERR_SIG_CLASS); + + /* PACKET is the right type for SIG. */ + + if (signer) + { + if (signer->keyid[0] == pripk->keyid[0] + && signer->keyid[1] == pripk->keyid[1]) + *is_selfsig = 1; + else + *is_selfsig = 0; + } + else + { + /* Get the signer. If possible, avoid a look up. */ + if (sig->keyid[0] == pripk->keyid[0] + && sig->keyid[1] == pripk->keyid[1]) + { + /* Issued by the primary key. */ + signer = pripk; + *is_selfsig = 1; + } + else + { + /* See if one of the subkeys was the signer (although this + * is extremely unlikely). */ + kbnode_t ctx = NULL; + kbnode_t n; + + while ((n = walk_kbnode (kb, &ctx, 0))) + { + PKT_public_key *subk; + + if (n->pkt->pkttype != PKT_PUBLIC_SUBKEY) + continue; + + subk = n->pkt->pkt.public_key; + if (sig->keyid[0] == subk->keyid[0] + && sig->keyid[1] == subk->keyid[1]) + { + /* Issued by a subkey. */ + signer = subk; + break; + } + } + + if (! signer) + { + /* Signer by some other key. */ + *is_selfsig = 0; + if (ret_pk) + { + signer = ret_pk; + /* FIXME: Using memset here is probematic because it + * assumes that there are no allocated fields in + * SIGNER. */ + memset (signer, 0, sizeof (*signer)); + signer_alloced = 1; + } + else + { + signer = xmalloc_clear (sizeof (*signer)); + signer_alloced = 2; + } + + if (IS_CERT (sig)) + signer->req_usage = PUBKEY_USAGE_CERT; + + rc = get_pubkey_for_sig (ctrl, signer, sig, NULL); + if (rc) + { + xfree (signer); + signer = NULL; + signer_alloced = 0; + goto leave; + } + } + } + } + + /* We checked above that we supported this algo, so an error here is + * a bug. */ + if (gcry_md_open (&md, sig->digest_algo, 0)) + BUG (); + + /* Hash the relevant data. */ + + if (IS_KEY_SIG (sig) || IS_KEY_REV (sig)) + { + log_assert (packet->pkttype == PKT_PUBLIC_KEY); + hash_public_key (md, packet->pkt.public_key); + rc = check_signature_end_simple (signer, sig, md); + } + else if (IS_BACK_SIG (sig)) + { + log_assert (packet->pkttype == PKT_PUBLIC_KEY); + hash_public_key (md, packet->pkt.public_key); + hash_public_key (md, signer); + rc = check_signature_end_simple (signer, sig, md); + } + else if (IS_SUBKEY_SIG (sig) || IS_SUBKEY_REV (sig)) + { + log_assert (packet->pkttype == PKT_PUBLIC_SUBKEY); + hash_public_key (md, pripk); + hash_public_key (md, packet->pkt.public_key); + rc = check_signature_end_simple (signer, sig, md); + } + else if (IS_UID_SIG (sig) || IS_UID_REV (sig)) + { + log_assert (packet->pkttype == PKT_USER_ID); + if (sig->digest_algo == DIGEST_ALGO_SHA1 && !*is_selfsig + && sig->timestamp > 1547856000 + && !opt.flags.allow_weak_key_signatures) + { + /* If the signature was created using SHA-1 we consider this + * signature invalid because it makes it possible to mount a + * chosen-prefix collision. We don't do this for + * self-signatures or for signatures created before the + * somewhat arbitrary cut-off date 2019-01-19. */ + print_sha1_keysig_rejected_note (); + rc = gpg_error (GPG_ERR_DIGEST_ALGO); + } + else + { + hash_public_key (md, pripk); + hash_uid_packet (packet->pkt.user_id, md, sig); + rc = check_signature_end_simple (signer, sig, md); + } + } + else + { + /* We should never get here. (The first if above should have + * already caught this error.) */ + BUG (); + } + + gcry_md_close (md); + + leave: + if (! rc && ret_pk && ret_pk != signer) + copy_public_key (ret_pk, signer); + + if (signer_alloced) + { + /* We looked up SIGNER; it is not a pointer into KB. */ + release_public_key_parts (signer); + /* Free if we also allocated the memory. */ + if (signer_alloced == 2) + xfree (signer); + } + + return rc; +} + + +/* Check that a signature over a key (e.g., a key revocation, key + * binding, user id certification, etc.) is valid. If the function + * detects a self-signature, it uses the public key from the specified + * key block and does not bother looking up the key specified in the + * signature packet. + * + * ROOT is a keyblock. + * + * NODE references a signature packet that appears in the keyblock + * that should be verified. + * + * If CHECK_PK is set, the specified key is sometimes preferred for + * verifying signatures. See the implementation for details. + * + * If RET_PK is not NULL, the public key that successfully verified + * the signature is copied into *RET_PK. + * + * If IS_SELFSIG is not NULL, *IS_SELFSIG is set to 1 if NODE is a + * self-signature. + * + * If R_EXPIREDATE is not NULL, *R_EXPIREDATE is set to the expiry + * date. + * + * If R_EXPIRED is not NULL, *R_EXPIRED is set to 1 if PK has been + * expired (0 otherwise). Note: PK being revoked does not cause this + * function to fail. + * + * + * If OPT.NO_SIG_CACHE is not set, this function will first check if + * the result of a previous verification is already cached in the + * signature packet's data structure. + * + * 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 (ctrl_t ctrl, + kbnode_t root, kbnode_t node, PKT_public_key *check_pk, + PKT_public_key *ret_pk, int *is_selfsig, + u32 *r_expiredate, int *r_expired ) +{ + 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; + log_assert (node->pkt->pkttype == PKT_SIGNATURE); + log_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 ) + { + cache_stats.total++; + if (sig->flags.checked) /* Cached status available. */ + { + cache_stats.cached++; + 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. */ + rc = check_signature_metadata_validity (pk, sig, r_expired, NULL); + if (rc) + return rc; + if (sig->flags.valid) + { + cache_stats.goodsig++; + return 0; + } + cache_stats.badsig++; + return gpg_error (GPG_ERR_BAD_SIGNATURE); + } + } + + rc = openpgp_pk_test_algo(sig->pubkey_algo); + if (rc) + return rc; + rc = openpgp_md_test_algo(algo); + if (rc) + return rc; + + if (IS_KEY_REV (sig)) + { + 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 (ctrl, pk, sig); + else + { + rc = check_signature_metadata_validity (pk, sig, + r_expired, NULL); + if (! rc) + rc = check_signature_over_key_or_uid (ctrl, pk, sig, + root, root->pkt, + is_selfsig, ret_pk); + } + } + else if (IS_SUBKEY_REV (sig) || IS_SUBKEY_SIG (sig)) + { + kbnode_t snode = find_prev_kbnode (root, node, PKT_PUBLIC_SUBKEY); + + if (snode) + { + rc = check_signature_metadata_validity (pk, sig, + r_expired, NULL); + if (! rc) + { + /* A subkey revocation (0x28) must be a self-sig, but a + * subkey signature (0x18) needn't be. */ + rc = check_signature_over_key_or_uid (ctrl, + IS_SUBKEY_SIG (sig) + ? NULL : pk, + sig, root, snode->pkt, + is_selfsig, ret_pk); + } + } + else + { + if (opt.verbose) + { + if (IS_SUBKEY_REV (sig)) + log_info (_("key %s: no subkey for subkey" + " revocation signature\n"), keystr_from_pk(pk)); + else if (sig->sig_class == 0x18) + log_info(_("key %s: no subkey for subkey" + " binding signature\n"), keystr_from_pk(pk)); + } + rc = GPG_ERR_SIG_CLASS; + } + } + else if (IS_KEY_SIG (sig)) /* direct key signature */ + { + rc = check_signature_metadata_validity (pk, sig, + r_expired, NULL); + if (! rc) + rc = check_signature_over_key_or_uid (ctrl, pk, sig, root, root->pkt, + is_selfsig, ret_pk); + } + else if (IS_UID_SIG (sig) || IS_UID_REV (sig)) + { + kbnode_t unode = find_prev_kbnode (root, node, PKT_USER_ID); + + if (unode) + { + rc = check_signature_metadata_validity (pk, sig, r_expired, NULL); + if (! rc) + { + /* If this is a self-sig, ignore check_pk. */ + rc = check_signature_over_key_or_uid + (ctrl, + keyid_cmp (pk_keyid (pk), sig->keyid) == 0 ? pk : check_pk, + sig, root, unode->pkt, NULL, ret_pk); + } + } + 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 = GPG_ERR_SIG_CLASS; + } + } + else + { + log_info ("sig issued by %s with class %d (digest: %02x %02x)" + " is not valid over a user id or a key id, ignoring.\n", + keystr (sig->keyid), sig->sig_class, + sig->digest_start[0], sig->digest_start[1]); + rc = gpg_error (GPG_ERR_BAD_SIGNATURE); + } + + cache_sig_result (sig, rc); + + return rc; +} diff --git a/g10/sign.c b/g10/sign.c new file mode 100644 index 0000000..f272319 --- /dev/null +++ b/g10/sign.c @@ -0,0 +1,1819 @@ +/* sign.c - sign data + * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, + * 2007, 2010, 2012 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include +#include +#include +#include +#include + +#include "gpg.h" +#include "options.h" +#include "packet.h" +#include "../common/status.h" +#include "../common/iobuf.h" +#include "keydb.h" +#include "../common/util.h" +#include "main.h" +#include "filter.h" +#include "../common/ttyio.h" +#include "trustdb.h" +#include "../common/status.h" +#include "../common/i18n.h" +#include "pkglue.h" +#include "../common/sysutils.h" +#include "call-agent.h" +#include "../common/mbox-util.h" +#include "../common/compliance.h" + +#ifdef HAVE_DOSISH_SYSTEM +#define LF "\r\n" +#else +#define LF "\n" +#endif + +/* Bitflags to convey hints on what kind of signayire is created. */ +#define SIGNHINT_KEYSIG 1 +#define SIGNHINT_SELFSIG 2 + + +/* Hack */ +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_public_key *pksk) +{ + const char *string; + char *p = NULL; + strlist_t pu = NULL; + struct notation *nd = NULL; + struct expando_args args; + + log_assert (sig->version >= 4); + + memset (&args, 0, sizeof(args)); + args.pk = pk; + args.pksk = pksk; + + /* Notation data. */ + if (IS_SIG(sig) && opt.sig_notations) + nd = opt.sig_notations; + else if (IS_CERT(sig) && opt.cert_notations) + nd = opt.cert_notations; + + if (nd) + { + struct notation *item; + + for (item = nd; item; item = item->next) + { + item->altvalue = pct_expando (item->value,&args); + if (!item->altvalue) + log_error (_("WARNING: unable to %%-expand notation " + "(too large). Using unexpanded.\n")); + } + + keygen_add_notations (sig, nd); + + for (item = nd; item; item = item->next) + { + xfree (item->altvalue); + item->altvalue = NULL; + } + } + + /* Set policy URL. */ + if (IS_SIG(sig) && opt.sig_policy_url) + pu = opt.sig_policy_url; + else if (IS_CERT(sig) && opt.cert_policy_url) + pu = opt.cert_policy_url; + + for (; pu; pu = pu->next) + { + string = pu->d; + + p = pct_expando (string, &args); + if (!p) + { + log_error(_("WARNING: unable to %%-expand policy URL " + "(too large). Using unexpanded.\n")); + p = xstrdup(string); + } + + build_sig_subpkt (sig, (SIGSUBPKT_POLICY + | ((pu->flags & 1)?SIGSUBPKT_FLAG_CRITICAL:0)), + p, strlen (p)); + + xfree (p); + } + + /* Preferred keyserver URL. */ + if (IS_SIG(sig) && opt.sig_keyserver_url) + pu = opt.sig_keyserver_url; + + for (; pu; pu = pu->next) + { + string = pu->d; + + p = pct_expando (string, &args); + if (!p) + { + log_error (_("WARNING: unable to %%-expand preferred keyserver URL" + " (too large). Using unexpanded.\n")); + p = xstrdup (string); + } + + build_sig_subpkt (sig, (SIGSUBPKT_PREF_KS + | ((pu->flags & 1)?SIGSUBPKT_FLAG_CRITICAL:0)), + p, strlen (p)); + xfree (p); + } + + /* Set signer's user id. */ + if (IS_SIG (sig) && !opt.flags.disable_signer_uid) + { + char *mbox; + + /* For now we use the uid which was used to locate the key. */ + if (pksk->user_id && (mbox = mailbox_from_userid (pksk->user_id->name))) + { + if (DBG_LOOKUP) + log_debug ("setting Signer's UID to '%s'\n", mbox); + build_sig_subpkt (sig, SIGSUBPKT_SIGNERS_UID, mbox, strlen (mbox)); + xfree (mbox); + } + else if (opt.sender_list) + { + /* If a list of --sender was given we scan that list and use + * the first one matching a user id of the current key. */ + + /* FIXME: We need to get the list of user ids for the PKSK + * packet. That requires either a function to look it up + * again or we need to extend the key packet struct to link + * to the primary key which in turn could link to the user + * ids. Too much of a change right now. Let's take just + * one from the supplied list and hope that the caller + * passed a matching one. */ + build_sig_subpkt (sig, SIGSUBPKT_SIGNERS_UID, + opt.sender_list->d, strlen (opt.sender_list->d)); + } + } +} + + + +/* + * Put the Key Block subpakcet into SIG for key PKSK. Returns an + * error code on failure. + */ +static gpg_error_t +mk_sig_subpkt_key_block (ctrl_t ctrl, PKT_signature *sig, PKT_public_key *pksk) +{ + gpg_error_t err; + char *mbox; + char *filterexp = NULL; + int save_opt_armor = opt.armor; + int save_opt_verbose = opt.verbose; + char hexfpr[2*MAX_FINGERPRINT_LEN + 1]; + void *data = NULL; + size_t datalen; + kbnode_t keyblock = NULL; + + push_export_filters (); + opt.armor = 0; + + hexfingerprint (pksk, hexfpr, sizeof hexfpr); + + /* Get the user id so that we know which one to insert into the + * key. */ + if (pksk->user_id + && (mbox = mailbox_from_userid (pksk->user_id->name))) + { + if (DBG_LOOKUP) + log_debug ("including key with UID '%s' (specified)\n", mbox); + filterexp = xasprintf ("keep-uid= -- mbox = %s", mbox); + xfree (mbox); + } + else if (opt.sender_list) + { + /* If --sender was given we use the first one from that list. */ + if (DBG_LOOKUP) + log_debug ("including key with UID '%s' (--sender)\n", + opt.sender_list->d); + filterexp = xasprintf ("keep-uid= -- mbox = %s", opt.sender_list->d); + } + else /* Use the primary user id. */ + { + if (DBG_LOOKUP) + log_debug ("including key with primary UID\n"); + filterexp = xstrdup ("keep-uid= primary -t"); + } + + if (DBG_LOOKUP) + log_debug ("export filter expression: %s\n", filterexp); + err = parse_and_set_export_filter (filterexp); + if (err) + goto leave; + xfree (filterexp); + filterexp = xasprintf ("drop-subkey= fpr <> %s && usage !~ e", hexfpr); + if (DBG_LOOKUP) + log_debug ("export filter expression: %s\n", filterexp); + err = parse_and_set_export_filter (filterexp); + if (err) + goto leave; + + + opt.verbose = 0; + err = export_pubkey_buffer (ctrl, hexfpr, EXPORT_MINIMAL|EXPORT_CLEAN, + "", 1, /* Prefix with the reserved byte. */ + NULL, &keyblock, &data, &datalen); + opt.verbose = save_opt_verbose; + if (err) + { + log_error ("failed to get to be included key: %s\n", gpg_strerror (err)); + goto leave; + } + + build_sig_subpkt (sig, SIGSUBPKT_KEY_BLOCK, data, datalen); + + leave: + xfree (data); + release_kbnode (keyblock); + xfree (filterexp); + opt.armor = save_opt_armor; + pop_export_filters (); + return err; +} + + +/* + * Helper to hash a user ID packet. + */ +static void +hash_uid (gcry_md_hd_t md, int sigversion, const PKT_user_id *uid) +{ + byte buf[5]; + + (void)sigversion; + + 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; + } + gcry_md_write( md, buf, 5 ); + + if (uid->attrib_data) + gcry_md_write (md, uid->attrib_data, uid->attrib_len ); + else + gcry_md_write (md, uid->name, uid->len ); +} + + +/* + * Helper to hash some parts from the signature + */ +static void +hash_sigversion_to_magic (gcry_md_hd_t md, const PKT_signature *sig) +{ + byte buf[6]; + size_t n; + + gcry_md_putc (md, sig->version); + gcry_md_putc (md, sig->sig_class); + gcry_md_putc (md, sig->pubkey_algo); + gcry_md_putc (md, sig->digest_algo); + if (sig->hashed) + { + n = sig->hashed->len; + gcry_md_putc (md, (n >> 8) ); + gcry_md_putc (md, n ); + gcry_md_write (md, sig->hashed->data, n ); + n += 6; + } + else + { + gcry_md_putc (md, 0); /* Always hash the length of the subpacket. */ + gcry_md_putc (md, 0); + n = 6; + } + /* Add some magic. */ + buf[0] = sig->version; + buf[1] = 0xff; + buf[2] = n >> 24; /* (n is only 16 bit, so this is always 0) */ + buf[3] = n >> 16; + buf[4] = n >> 8; + buf[5] = n; + gcry_md_write (md, buf, 6); +} + + +/* Perform the sign operation. If CACHE_NONCE is given the agent is + * advised to use that cached passphrase for the key. SIGNHINTS has + * hints so that we can do some additional checks. */ +static int +do_sign (ctrl_t ctrl, PKT_public_key *pksk, PKT_signature *sig, + gcry_md_hd_t md, int mdalgo, + const char *cache_nonce, unsigned int signhints) +{ + gpg_error_t err; + byte *dp; + char *hexgrip; + + if (pksk->timestamp > sig->timestamp ) + { + ulong d = pksk->timestamp - sig->timestamp; + log_info (ngettext("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", + d), keystr_from_pk (pksk), d); + if (!opt.ignore_time_conflict) + return gpg_error (GPG_ERR_TIME_CONFLICT); + } + + print_pubkey_algo_note (pksk->pubkey_algo); + + if (!mdalgo) + mdalgo = gcry_md_get_algo (md); + + if ((signhints & SIGNHINT_KEYSIG) && !(signhints & SIGNHINT_SELFSIG) + && mdalgo == GCRY_MD_SHA1 + && !opt.flags.allow_weak_key_signatures) + { + /* We do not allow the creation of third-party key signatures + * using SHA-1 because we also reject them when verifying. Note + * that this will render dsa1024 keys unsuitable for such + * keysigs and in turn the WoT. */ + print_sha1_keysig_rejected_note (); + err = gpg_error (GPG_ERR_DIGEST_ALGO); + goto leave; + } + + /* Check compliance. */ + if (! gnupg_digest_is_allowed (opt.compliance, 1, mdalgo)) + { + log_error (_("digest algorithm '%s' may not be used in %s mode\n"), + gcry_md_algo_name (mdalgo), + gnupg_compliance_option_string (opt.compliance)); + err = gpg_error (GPG_ERR_DIGEST_ALGO); + goto leave; + } + + if (! gnupg_pk_is_allowed (opt.compliance, PK_USE_SIGNING, + pksk->pubkey_algo, 0, + pksk->pkey, nbits_from_pk (pksk), NULL)) + { + log_error (_("key %s may not be used for signing in %s mode\n"), + keystr_from_pk (pksk), + gnupg_compliance_option_string (opt.compliance)); + err = gpg_error (GPG_ERR_PUBKEY_ALGO); + goto leave; + } + + if (!gnupg_rng_is_compliant (opt.compliance)) + { + err = gpg_error (GPG_ERR_FORBIDDEN); + log_error (_("%s is not compliant with %s mode\n"), + "RNG", + gnupg_compliance_option_string (opt.compliance)); + write_status_error ("random-compliance", err); + goto leave; + } + + print_digest_algo_note (mdalgo); + dp = gcry_md_read (md, mdalgo); + sig->digest_algo = mdalgo; + sig->digest_start[0] = dp[0]; + sig->digest_start[1] = dp[1]; + mpi_release (sig->data[0]); + sig->data[0] = NULL; + mpi_release (sig->data[1]); + sig->data[1] = NULL; + + + err = hexkeygrip_from_pk (pksk, &hexgrip); + if (!err) + { + char *desc; + gcry_sexp_t s_sigval; + + desc = gpg_format_keydesc (ctrl, pksk, FORMAT_KEYDESC_NORMAL, 1); + err = agent_pksign (NULL/*ctrl*/, cache_nonce, hexgrip, desc, + pksk->keyid, pksk->main_keyid, pksk->pubkey_algo, + dp, gcry_md_get_algo_dlen (mdalgo), mdalgo, + &s_sigval); + xfree (desc); + + if (err) + ; + else if (pksk->pubkey_algo == GCRY_PK_RSA + || pksk->pubkey_algo == GCRY_PK_RSA_S) + sig->data[0] = get_mpi_from_sexp (s_sigval, "s", GCRYMPI_FMT_USG); + else if (openpgp_oid_is_ed25519 (pksk->pkey[0])) + { + sig->data[0] = get_mpi_from_sexp (s_sigval, "r", GCRYMPI_FMT_OPAQUE); + sig->data[1] = get_mpi_from_sexp (s_sigval, "s", GCRYMPI_FMT_OPAQUE); + } + else + { + sig->data[0] = get_mpi_from_sexp (s_sigval, "r", GCRYMPI_FMT_USG); + sig->data[1] = get_mpi_from_sexp (s_sigval, "s", GCRYMPI_FMT_USG); + } + + gcry_sexp_release (s_sigval); + } + xfree (hexgrip); + + leave: + if (err) + { + log_error (_("signing failed: %s\n"), gpg_strerror (err)); + if (gpg_err_source (err) == GPG_ERR_SOURCE_SCD + && gpg_err_code (err) == GPG_ERR_INV_ID) + print_further_info ("a reason might be a card with replaced keys"); + } + else + { + if (opt.verbose) + { + char *ustr = get_user_id_string_native (ctrl, sig->keyid); + log_info (_("%s/%s signature from: \"%s\"\n"), + openpgp_pk_algo_name (pksk->pubkey_algo), + openpgp_md_algo_name (sig->digest_algo), + ustr); + xfree (ustr); + } + } + return err; +} + + +static int +complete_sig (ctrl_t ctrl, + PKT_signature *sig, PKT_public_key *pksk, gcry_md_hd_t md, + const char *cache_nonce, unsigned int signhints) +{ + int rc; + + /* if (!(rc = check_secret_key (pksk, 0))) */ + rc = do_sign (ctrl, pksk, sig, md, 0, cache_nonce, signhints); + return rc; +} + + +/* Return true if the key seems to be on a version 1 OpenPGP card. + This works by asking the agent and may fail if the card has not yet + been used with the agent. */ +static int +openpgp_card_v1_p (PKT_public_key *pk) +{ + gpg_error_t err; + int result; + + /* Shortcut if we are not using RSA: The v1 cards only support RSA + thus there is no point in looking any further. */ + if (!is_RSA (pk->pubkey_algo)) + return 0; + + if (!pk->flags.serialno_valid) + { + char *hexgrip; + + err = hexkeygrip_from_pk (pk, &hexgrip); + if (err) + { + log_error ("error computing a keygrip: %s\n", gpg_strerror (err)); + return 0; /* Ooops. */ + } + + xfree (pk->serialno); + agent_get_keyinfo (NULL, hexgrip, &pk->serialno, NULL); + xfree (hexgrip); + pk->flags.serialno_valid = 1; + } + + if (!pk->serialno) + result = 0; /* Error from a past agent_get_keyinfo or no card. */ + else + { + /* The version number of the card is included in the serialno. */ + result = !strncmp (pk->serialno, "D2760001240101", 14); + } + return result; +} + + +/* Get a matching hash algorithm for DSA and ECDSA. */ +static int +match_dsa_hash (unsigned int qbytes) +{ + if (qbytes <= 20) + return DIGEST_ALGO_SHA1; + + if (qbytes <= 28) + return DIGEST_ALGO_SHA224; + + if (qbytes <= 32) + return DIGEST_ALGO_SHA256; + + if (qbytes <= 48) + return DIGEST_ALGO_SHA384; + + if (qbytes <= 66 ) /* 66 corresponds to 521 (64 to 512) */ + return DIGEST_ALGO_SHA512; + + return DEFAULT_DIGEST_ALGO; + /* DEFAULT_DIGEST_ALGO will certainly fail, but it's the best wrong + answer we have if a digest larger than 512 bits is requested. */ +} + + +/* + First try --digest-algo. If that isn't set, see if the recipient + has a preferred algorithm (which is also filtered through + --personal-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 --personal-digest-prefs that is + usable for the pubkey algorithm. If --personal-digest-prefs isn't + set, then take the OpenPGP default (i.e. SHA-1). + + Note that Ed25519+EdDSA takes an input of arbitrary length and thus + we don't enforce any particular algorithm like we do for standard + ECDSA. However, we use SHA256 as the default algorithm. + + 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_public_key *pk) +{ + if (opt.def_digest_algo) + { + return opt.def_digest_algo; + } + else if (recipient_digest_algo && !is_weak_digest (recipient_digest_algo)) + { + return recipient_digest_algo; + } + else if (pk->pubkey_algo == PUBKEY_ALGO_EDDSA + && openpgp_oid_is_ed25519 (pk->pkey[0])) + { + if (opt.personal_digest_prefs) + return opt.personal_digest_prefs[0].value; + else + return DIGEST_ALGO_SHA256; + } + else if (pk->pubkey_algo == PUBKEY_ALGO_DSA + || pk->pubkey_algo == PUBKEY_ALGO_ECDSA) + { + unsigned int qbytes = gcry_mpi_get_nbits (pk->pkey[1]); + + if (pk->pubkey_algo == PUBKEY_ALGO_ECDSA) + qbytes = ecdsa_qbits_from_Q (qbytes); + qbytes = qbytes/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. We ignore the personal_digest_prefs + for ECDSA because they should always macth the curve and + truncated hashes are not useful either. Even worse, + smartcards may reject non matching hash lengths for curves + (e.g. using SHA-512 with brainpooolP385r1 on a Yubikey). */ + + if (pk->pubkey_algo == PUBKEY_ALGO_DSA && opt.personal_digest_prefs) + { + prefitem_t *prefs; + + if (qbytes != 20 || opt.flags.dsa2) + { + for (prefs=opt.personal_digest_prefs; prefs->type; prefs++) + if (gcry_md_get_algo_dlen (prefs->value) >= qbytes) + return prefs->value; + } + else + { + for (prefs=opt.personal_digest_prefs; prefs->type; prefs++) + if (gcry_md_get_algo_dlen (prefs->value) == qbytes) + return prefs->value; + } + } + + return match_dsa_hash(qbytes); + } + else if (openpgp_card_v1_p (pk)) + { + /* The sk lives on a smartcard, and old smartcards only handle + SHA-1 and RIPEMD/160. Newer smartcards (v2.0) don't have + this restriction anymore. Fortunately the serial number + encodes the version of the card and thus we know that this + key is on a v1 card. */ + 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 (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 void +print_status_sig_created (PKT_public_key *pk, PKT_signature *sig, int what) +{ + byte array[MAX_FINGERPRINT_LEN]; + char buf[100+MAX_FINGERPRINT_LEN*2]; + size_t n; + + snprintf (buf, sizeof buf - 2*MAX_FINGERPRINT_LEN, "%c %d %d %02x %lu ", + what, sig->pubkey_algo, sig->digest_algo, sig->sig_class, + (ulong)sig->timestamp ); + fingerprint_from_pk (pk, array, &n); + bin2hex (array, n, buf + strlen (buf)); + + 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_public_key *pk; + 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; + } + + pk = sk_rover->pk; + ops = xmalloc_clear (sizeof *ops); + ops->sig_class = sigclass; + ops->digest_algo = hash_for (pk); + ops->pubkey_algo = pk->pubkey_algo; + keyid_from_pk (pk, 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, NULL); + if (rc) { + log_error ("build onepass_sig packet failed: %s\n", + gpg_strerror (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 && opt.verbose) + 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' || ptmode == 'u' || ptmode == 'm') + filesize = 0; + } + else + filesize = opt.set_filesize? opt.set_filesize : 0; /* stdin */ + + if (!opt.no_literal) { + PACKET pkt; + + /* Note that PT has been initialized above in no_literal mode. */ + pt->timestamp = make_timestamp (); + pt->mode = ptmode; + pt->len = filesize; + pt->new_ctb = !pt->len; + 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", + gpg_strerror (rc) ); + pt->buf = NULL; + free_packet (&pkt, NULL); + } + else { + byte copy_buffer[4096]; + int bytes_copied; + + while ((bytes_copied = iobuf_read(inp, copy_buffer, 4096)) != -1) + if ( (rc=iobuf_write(out, copy_buffer, bytes_copied)) ) { + log_error ("copying input to output failed: %s\n", + gpg_strerror (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 (ctrl_t ctrl, + SK_LIST sk_list, IOBUF out, gcry_md_hd_t hash, + int sigclass, u32 timestamp, u32 duration, + int status_letter, const char *cache_nonce) +{ + SK_LIST sk_rover; + + /* Loop over the certificates with secret keys. */ + for (sk_rover = sk_list; sk_rover; sk_rover = sk_rover->next) + { + PKT_public_key *pk; + PKT_signature *sig; + gcry_md_hd_t md; + gpg_error_t err; + + pk = sk_rover->pk; + + /* Build the signature packet. */ + sig = xtrycalloc (1, sizeof *sig); + if (!sig) + return gpg_error_from_syserror (); + + if (duration || opt.sig_policy_url + || opt.sig_notations || opt.sig_keyserver_url) + sig->version = 4; + else + sig->version = pk->version; + + keyid_from_pk (pk, sig->keyid); + sig->digest_algo = hash_for (pk); + sig->pubkey_algo = pk->pubkey_algo; + if (timestamp) + sig->timestamp = timestamp; + else + sig->timestamp = make_timestamp(); + if (duration) + sig->expiredate = sig->timestamp + duration; + sig->sig_class = sigclass; + + if (gcry_md_copy (&md, hash)) + BUG (); + + if (sig->version >= 4) + { + build_sig_subpkt_from_sig (sig, pk); + mk_notation_policy_etc (sig, NULL, pk); + if (opt.flags.include_key_block && IS_SIG (sig)) + err = mk_sig_subpkt_key_block (ctrl, sig, pk); + else + err = 0; + } + else + err = 0; /* Actually never reached. */ + hash_sigversion_to_magic (md, sig); + gcry_md_final (md); + + if (!err) + err = do_sign (ctrl, pk, sig, md, hash_for (pk), cache_nonce, 0); + gcry_md_close (md); + if (!err) + { + /* Write the packet. */ + PACKET pkt; + + init_packet (&pkt); + pkt.pkttype = PKT_SIGNATURE; + pkt.pkt.signature = sig; + err = build_packet (out, &pkt); + if (!err && is_status_enabled()) + print_status_sig_created (pk, sig, status_letter); + free_packet (&pkt, NULL); + if (err) + log_error ("build signature packet failed: %s\n", + gpg_strerror (err)); + } + else + free_seckey_enc (sig); + + if (err) + return err; + } + + return 0; +} + + +/* Sign the files whose names are in FILENAME using all secret keys + * which can be taken from LOCUSR, if this is NULL, use the default + * secret key. + * If DETACHED has the value true, make a detached signature. + * If FILENAMES->d is NULL read from stdin and ignore the detached mode. + * If ENCRYPTFLAG is true, use REMUSER (or ask if it is NULL) to + * encrypt the signed data for these users. If ENCRYPTFLAG is 2 + * symmetric encryption is also used. + * 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 (ctrl_t ctrl, strlist_t filenames, int detached, strlist_t locusr, + int encryptflag, strlist_t 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; + + pfx = new_progress_context (); + afx = new_armor_context (); + memset( &zfx, 0, sizeof zfx); + memset( &mfx, 0, sizeof mfx); + memset( &efx, 0, sizeof efx); + efx.ctrl = ctrl; + 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.ask_sig_expire && !opt.batch) + duration = ask_expire_interval(1,opt.def_sig_expire); + else + duration = parse_expire_string(opt.def_sig_expire); + + /* Note: In the old non-agent version the following call used to + unprotect the secret key. This is now done on demand by the agent. */ + if( (rc = build_sk_list (ctrl, locusr, &sk_list, PUBKEY_USAGE_SIG )) ) + goto leave; + + if (encryptflag + && (rc=build_pk_list (ctrl, remusr, &pk_list))) + 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; + gpg_err_set_errno (EPERM); + } + if( !inp ) + { + rc = gpg_error_from_syserror (); + log_error (_("can't open '%s': %s\n"), fname? fname: "[stdin]", + strerror(errno) ); + goto leave; + } + + handle_progress (pfx, inp, fname); + } + + if( outfile ) { + if (is_secured_filename ( outfile )) { + out = NULL; + gpg_err_set_errno (EPERM); + } + else + out = iobuf_create (outfile, 0); + if( !out ) + { + rc = gpg_error_from_syserror (); + log_error(_("can't create '%s': %s\n"), outfile, strerror(errno) ); + goto leave; + } + else if( opt.verbose ) + log_info(_("writing to '%s'\n"), outfile ); + } + else if( (rc = open_outfile (-1, fname, + opt.armor? 1: detached? 2:0, 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 ); + } + + if ( gcry_md_open (&mfx.md, 0, 0) ) + BUG (); + if (DBG_HASHING) + gcry_md_debug (mfx.md, "sign"); + + /* If we're encrypting and signing, it is reasonable to pick the + hash algorithm to use out of the recipient 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"), + gcry_md_algo_name (opt.def_digest_algo), + opt.def_digest_algo ); + } + else + { + int algo; + int conflict = 0; + struct pref_hint hint = { 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->pk->pubkey_algo == PUBKEY_ALGO_DSA + || sk_rover->pk->pubkey_algo == PUBKEY_ALGO_ECDSA) + { + int temp_hashlen = (gcry_mpi_get_nbits + (sk_rover->pk->pkey[1])); + + if (sk_rover->pk->pubkey_algo == PUBKEY_ALGO_ECDSA) + { + temp_hashlen = ecdsa_qbits_from_Q (temp_hashlen); + if (!temp_hashlen) + conflict = 1; /* Better don't use the prefs. */ + temp_hashlen = (temp_hashlen+7)/8; + /* Fixup for that funny nistp521 (yes, 521) + * were we need to use a 512 bit hash algo. */ + if (temp_hashlen == 66) + temp_hashlen = 64; + } + else + temp_hashlen = (temp_hashlen+7)/8; + + /* Pick a hash that is large enough for our + largest q or matches our Q but if tehreare + several of them we run into a conflict and + don't use the preferences. */ + + if (hint.digest_length < temp_hashlen) + { + if (sk_rover->pk->pubkey_algo == PUBKEY_ALGO_ECDSA) + { + if (hint.exact) + conflict = 1; + hint.exact = 1; + } + hint.digest_length = temp_hashlen; + } + } + } + + if (!conflict + && (algo = select_algo_from_prefs (pk_list,PREFTYPE_HASH, + -1,&hint)) > 0) + { + /* Note that we later check that the algo is not weak. */ + recipient_digest_algo = algo; + } + } + } + + for (sk_rover = sk_list; sk_rover; sk_rover = sk_rover->next) + gcry_md_enable (mfx.md, hash_for (sk_rover->pk)); + + if( !multifile ) + iobuf_push_filter( inp, md_filter, &mfx ); + + if( detached && !encryptflag) + afx->what = 2; + + if( opt.armor && !outfile ) + push_armor_filter (afx, out); + + 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) + { + 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) { + rc = write_onepass_sig_packets (sk_list, out, + opt.textmode && !outfile ? 0x01:0x00); + if (rc) + goto leave; + } + + write_status_begin_signing (mfx.md); + + /* Setup the inner packet. */ + if( detached ) { + if( multifile ) { + strlist_t 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; + gpg_err_set_errno (EPERM); + } + if( !inp ) + { + rc = gpg_error_from_syserror (); + log_error(_("can't open '%s': %s\n"), + sl->d,strerror(errno)); + goto leave; + } + handle_progress (pfx, inp, sl->d); + if( opt.verbose ) + log_printf (" '%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 ) + log_printf ("\n"); + } + 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 ? + (opt.mimemode? 'm':'t'):'b'); + } + + /* catch errors from above */ + if (rc) + goto leave; + + /* write the signatures */ + rc = write_signature_packets (ctrl, sk_list, out, mfx.md, + opt.textmode && !outfile? 0x01 : 0x00, + 0, duration, detached ? 'D':'S', NULL); + if( rc ) + goto leave; + + + leave: + if( rc ) + iobuf_cancel(out); + else { + iobuf_close(out); + if (encryptflag) + write_status( STATUS_END_ENCRYPTION ); + } + iobuf_close(inp); + gcry_md_close ( mfx.md ); + release_sk_list( sk_list ); + release_pk_list( pk_list ); + recipient_digest_algo=0; + release_progress_context (pfx); + release_armor_context (afx); + return rc; +} + + + +/**************** + * make a clear signature. note that opt.armor is not needed + */ +int +clearsign_file (ctrl_t ctrl, + const char *fname, strlist_t locusr, const char *outfile ) +{ + armor_filter_context_t *afx; + progress_filter_context_t *pfx; + gcry_md_hd_t textmd = NULL; + IOBUF inp = NULL, out = NULL; + PACKET pkt; + int rc = 0; + SK_LIST sk_list = NULL; + SK_LIST sk_rover = NULL; + u32 duration=0; + + pfx = new_progress_context (); + afx = new_armor_context (); + init_packet( &pkt ); + + 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); + + /* Note: In the old non-agent version the following call used to + unprotect the secret key. This is now done on demand by the agent. */ + if( (rc=build_sk_list (ctrl, locusr, &sk_list, PUBKEY_USAGE_SIG )) ) + goto leave; + + /* prepare iobufs */ + inp = iobuf_open(fname); + if (inp && is_secured_file (iobuf_get_fd (inp))) + { + iobuf_close (inp); + inp = NULL; + gpg_err_set_errno (EPERM); + } + if( !inp ) { + rc = gpg_error_from_syserror (); + log_error (_("can't open '%s': %s\n"), + fname? fname: "[stdin]", strerror(errno) ); + goto leave; + } + handle_progress (pfx, inp, fname); + + if( outfile ) { + if (is_secured_filename (outfile) ) { + outfile = NULL; + gpg_err_set_errno (EPERM); + } + else + out = iobuf_create (outfile, 0); + if( !out ) + { + rc = gpg_error_from_syserror (); + log_error(_("can't create '%s': %s\n"), outfile, strerror(errno) ); + goto leave; + } + else if( opt.verbose ) + log_info(_("writing to '%s'\n"), outfile ); + } + else if ((rc = open_outfile (-1, fname, 1, 0, &out))) + goto leave; + + iobuf_writestr(out, "-----BEGIN PGP SIGNED MESSAGE-----" LF ); + + { + 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 ) { + int i = hash_for (sk_rover->pk); + + if( !hashs_seen[ i & 0xff ] ) { + s = gcry_md_algo_name ( i ); + if( s ) { + hashs_seen[ i & 0xff ] = 1; + if( any ) + iobuf_put(out, ',' ); + iobuf_writestr(out, s ); + any = 1; + } + } + } + log_assert(any); + iobuf_writestr(out, LF ); + } + + if( opt.not_dash_escaped ) + iobuf_writestr( out, + "NotDashEscaped: You need "GPG_NAME + " to verify this message" LF ); + iobuf_writestr(out, LF ); + + if ( gcry_md_open (&textmd, 0, 0) ) + BUG (); + for (sk_rover = sk_list; sk_rover; sk_rover = sk_rover->next) + gcry_md_enable (textmd, hash_for(sk_rover->pk)); + + if ( DBG_HASHING ) + gcry_md_debug ( textmd, "clearsign" ); + + copy_clearsig_text (out, inp, textmd, !opt.not_dash_escaped, + opt.escape_from); + /* fixme: check for read errors */ + + /* now write the armor */ + afx->what = 2; + push_armor_filter (afx, out); + + /* Write the signatures. */ + rc = write_signature_packets (ctrl, sk_list, out, textmd, 0x01, 0, + duration, 'C', NULL); + if( rc ) + goto leave; + + leave: + if( rc ) + iobuf_cancel(out); + else + iobuf_close(out); + iobuf_close(inp); + gcry_md_close ( textmd ); + release_sk_list( sk_list ); + release_progress_context (pfx); + release_armor_context (afx); + return rc; +} + +/* + * Sign and conventionally encrypt the given file. + * FIXME: Far too much code is duplicated - revamp the whole file. + */ +int +sign_symencrypt_file (ctrl_t ctrl, const char *fname, strlist_t 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; + int canceled; + + pfx = new_progress_context (); + afx = new_armor_context (); + 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.ask_sig_expire && !opt.batch) + duration = ask_expire_interval (1, opt.def_sig_expire); + else + duration = parse_expire_string (opt.def_sig_expire); + + /* Note: In the old non-agent version the following call used to + unprotect the secret key. This is now done on demand by the agent. */ + rc = build_sk_list (ctrl, locusr, &sk_list, 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; + gpg_err_set_errno (EPERM); + } + if( !inp ) { + rc = gpg_error_from_syserror (); + log_error (_("can't open '%s': %s\n"), + fname? fname: "[stdin]", strerror(errno) ); + goto leave; + } + handle_progress (pfx, inp, fname); + + /* prepare key */ + s2k = xmalloc_clear( sizeof *s2k ); + s2k->mode = 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"), + openpgp_cipher_algo_name (algo) ); + cfx.dek = passphrase_to_dek (algo, s2k, 1, 1, NULL, 0, &canceled); + + if (!cfx.dek || !cfx.dek->keylen) { + rc = gpg_error (canceled?GPG_ERR_CANCELED:GPG_ERR_BAD_PASSPHRASE); + log_error(_("error creating passphrase: %s\n"), gpg_strerror (rc) ); + goto leave; + } + + cfx.dek->use_mdc = use_mdc (NULL, cfx.dek->algo); + + /* now create the outfile */ + rc = open_outfile (-1, fname, opt.armor? 1:0, 0, &out); + if (rc) + goto leave; + + /* prepare to calculate the MD over the input */ + if (opt.textmode) + iobuf_push_filter (inp, text_filter, &tfx); + if ( gcry_md_open (&mfx.md, 0, 0) ) + BUG (); + if ( DBG_HASHING ) + gcry_md_debug (mfx.md, "symc-sign"); + + for (sk_rover = sk_list; sk_rover; sk_rover = sk_rover->next) + gcry_md_enable (mfx.md, hash_for (sk_rover->pk)); + + iobuf_push_filter (inp, md_filter, &mfx); + + /* Push armor output filter */ + if (opt.armor) + push_armor_filter (afx, out); + + /* Write the symmetric key packet */ + /*(current filters: armor)*/ + { + 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", gpg_strerror (rc) ); + xfree(enc); + } + + /* Push the encryption filter */ + iobuf_push_filter (out, cipher_filter_cfb, &cfx ); + + /* Push the compress filter */ + if (default_compress_algo()) + { + if (cfx.dek && cfx.dek->use_mdc) + zfx.new_ctb = 1; + push_compress_filter (out, &zfx,default_compress_algo() ); + } + + /* Write the one-pass signature packets */ + /*(current filters: zip - encrypt - armor)*/ + rc = write_onepass_sig_packets (sk_list, out, + opt.textmode? 0x01:0x00); + if (rc) + goto leave; + + write_status_begin_signing (mfx.md); + + /* 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 ? (opt.mimemode?'m':'t'):'b'); + if (rc) + goto leave; + + /* Write the signatures */ + /*(current filters: zip - encrypt - armor)*/ + rc = write_signature_packets (ctrl, sk_list, out, mfx.md, + opt.textmode? 0x01 : 0x00, + 0, duration, 'S', NULL); + 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 ); + gcry_md_close( mfx.md ); + xfree(cfx.dek); + xfree(s2k); + release_progress_context (pfx); + release_armor_context (afx); + return rc; +} + + +/**************** + * Create a v4 signature in *RET_SIG. + * + * PK is the primary key to sign (required for all sigs) + * UID is the user id to sign (required for 0x10..0x13, 0x30) + * SUBPK is subkey to sign (required for 0x18, 0x19, 0x28) + * + * PKSK is the signing key + * + * SIGCLASS is the type of signature to create. + * + * DIGEST_ALGO is the digest algorithm. If it is 0 the function + * selects an appropriate one. + * + * TIMESTAMP is the timestamp to use for the signature. 0 means "now" + * + * DURATION is the amount of time (in seconds) until the signature + * expires. + * + * This function creates the following subpackets: issuer, created, + * and expire (if duration is not 0). Additional subpackets can be + * added using MKSUBPKT, which is called after these subpackets are + * added and before the signature is generated. OPAQUE is passed to + * MKSUBPKT. + */ +int +make_keysig_packet (ctrl_t ctrl, + PKT_signature **ret_sig, PKT_public_key *pk, + PKT_user_id *uid, PKT_public_key *subpk, + PKT_public_key *pksk, + int sigclass, int digest_algo, + u32 timestamp, u32 duration, + int (*mksubpkt)(PKT_signature *, void *), void *opaque, + const char *cache_nonce) +{ + PKT_signature *sig; + int rc=0; + int sigversion; + gcry_md_hd_t md; + u32 pk_keyid[2], pksk_keyid[2]; + unsigned int signhints; + + log_assert ((sigclass >= 0x10 && sigclass <= 0x13) || sigclass == 0x1F + || sigclass == 0x20 || sigclass == 0x18 || sigclass == 0x19 + || sigclass == 0x30 || sigclass == 0x28 ); + + sigversion = 4; + if (sigversion < pksk->version) + sigversion = pksk->version; + + if( !digest_algo ) + { + /* Basically, this means use SHA1 always unless 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(pksk->pubkey_algo == PUBKEY_ALGO_DSA) + digest_algo = match_dsa_hash (gcry_mpi_get_nbits (pksk->pkey[1])/8); + else if (pksk->pubkey_algo == PUBKEY_ALGO_ECDSA + || pksk->pubkey_algo == PUBKEY_ALGO_EDDSA) + { + if (openpgp_oid_is_ed25519 (pksk->pkey[0])) + digest_algo = DIGEST_ALGO_SHA256; + else + digest_algo = match_dsa_hash + (ecdsa_qbits_from_Q (gcry_mpi_get_nbits (pksk->pkey[1]))/8); + } + else + digest_algo = DEFAULT_DIGEST_ALGO; + } + + signhints = SIGNHINT_KEYSIG; + keyid_from_pk (pk, pk_keyid); + keyid_from_pk (pksk, pksk_keyid); + if (pk_keyid[0] == pksk_keyid[0] && pk_keyid[1] == pksk_keyid[1]) + signhints |= SIGNHINT_SELFSIG; + + if ( gcry_md_open (&md, digest_algo, 0 ) ) + BUG (); + + /* 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_pk (pksk, sig->keyid); + sig->pubkey_algo = pksk->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; + + build_sig_subpkt_from_sig (sig, pksk); + mk_notation_policy_etc (sig, pk, pksk); + + /* 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 (mksubpkt) + rc = (*mksubpkt)( sig, opaque ); + + if( !rc ) { + hash_sigversion_to_magic (md, sig); + gcry_md_final (md); + + rc = complete_sig (ctrl, sig, pksk, md, cache_nonce, signhints); + } + + gcry_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. + * PK is the public key to work on. + * PKSK is the key used to make the signature. + * + * TODO: Merge this with make_keysig_packet. + */ +gpg_error_t +update_keysig_packet (ctrl_t ctrl, + PKT_signature **ret_sig, + PKT_signature *orig_sig, + PKT_public_key *pk, + PKT_user_id *uid, + PKT_public_key *subpk, + PKT_public_key *pksk, + int (*mksubpkt)(PKT_signature *, void *), + void *opaque) +{ + PKT_signature *sig; + gpg_error_t rc = 0; + int digest_algo; + gcry_md_hd_t md; + u32 pk_keyid[2], pksk_keyid[2]; + unsigned int signhints; + + if ((!orig_sig || !pk || !pksk) + || (orig_sig->sig_class >= 0x10 && orig_sig->sig_class <= 0x13 && !uid) + || (orig_sig->sig_class == 0x18 && !subpk)) + return GPG_ERR_GENERAL; + + if ( opt.cert_digest_algo ) + digest_algo = opt.cert_digest_algo; + else if (pksk->pubkey_algo == PUBKEY_ALGO_DSA + || pksk->pubkey_algo == PUBKEY_ALGO_ECDSA + || pksk->pubkey_algo == PUBKEY_ALGO_EDDSA) + digest_algo = orig_sig->digest_algo; + else if (orig_sig->digest_algo == DIGEST_ALGO_SHA1 + || orig_sig->digest_algo == DIGEST_ALGO_RMD160) + digest_algo = DEFAULT_DIGEST_ALGO; + else + digest_algo = orig_sig->digest_algo; + + signhints = SIGNHINT_KEYSIG; + keyid_from_pk (pk, pk_keyid); + keyid_from_pk (pksk, pksk_keyid); + if (pk_keyid[0] == pksk_keyid[0] && pk_keyid[1] == pksk_keyid[1]) + signhints |= SIGNHINT_SELFSIG; + + if ( gcry_md_open (&md, digest_algo, 0 ) ) + BUG (); + + /* 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); + + sig->digest_algo=digest_algo; + + /* 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. */ + { + int tmout = 0; + while(sig->timestamp<=orig_sig->timestamp) + { + if (++tmout > 5 && !opt.ignore_time_conflict) + { + rc = gpg_error (GPG_ERR_TIME_CONFLICT); + goto leave; + } + gnupg_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. */ + + /* 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, pksk); + + if (mksubpkt) + rc = (*mksubpkt)(sig, opaque); + + if (!rc) { + hash_sigversion_to_magic (md, sig); + gcry_md_final (md); + + rc = complete_sig (ctrl, sig, pksk, md, NULL, signhints); + } + + leave: + gcry_md_close (md); + if( rc ) + free_seckey_enc (sig); + else + *ret_sig = sig; + return rc; +} diff --git a/g10/skclist.c b/g10/skclist.c new file mode 100644 index 0000000..d0511ad --- /dev/null +++ b/g10/skclist.c @@ -0,0 +1,637 @@ +/* skclist.c - Build a list of secret keys + * Copyright (C) 1998, 1999, 2000, 2001, 2006, + * 2010 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include +#include +#include +#include +#include + +#include "gpg.h" +#include "options.h" +#include "packet.h" +#include "../common/status.h" +#include "keydb.h" +#include "../common/util.h" +#include "../common/i18n.h" +#include "keyserver-internal.h" +#include "call-agent.h" + + +/* Return true if Libgcrypt's RNG is in faked mode. */ +int +random_is_faked (void) +{ + return !!gcry_control (GCRYCTL_FAKED_RANDOM_P, 0); +} + + +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_public_key (sk_list->pk); + 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 (ctrl_t ctrl, PKT_public_key *pk) +{ + u32 keyid[2]; + KBNODE node = NULL, u; + int insecure = 0; + + keyid_from_pk (pk, keyid); + node = get_pubkeyblock (ctrl, 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_public_key *pk) +{ + for (; sk_list; sk_list = sk_list->next) + { + if (!cmp_public_keys (sk_list->pk, pk)) + return 0; + } + return -1; +} + +static int +is_duplicated_entry (strlist_t list, strlist_t item) +{ + for (; list && list != item; list = list->next) + { + if (!strcmp (list->d, item->d)) + return 1; + } + return 0; +} + + +gpg_error_t +build_sk_list (ctrl_t ctrl, + strlist_t locusr, SK_LIST *ret_sk_list, unsigned int use) +{ + gpg_error_t err; + SK_LIST sk_list = NULL; + + /* XXX: Change this function to use get_pubkeys instead of + getkey_byname to detect ambiguous key specifications and warn + about duplicate keyblocks. For ambiguous key specifications on + the command line or provided interactively, prompt the user to + select the best key. If a key specification is ambiguous and we + are in batch mode, die. */ + + if (!locusr) /* No user ids given - use the card key or the default key. */ + { + struct agent_card_info_s info; + PKT_public_key *pk; + char *serialno; + + memset (&info, 0, sizeof(info)); + pk = xmalloc_clear (sizeof *pk); + pk->req_usage = use; + + /* Check if a card is available. If any, use the key as a hint. */ + err = agent_scd_serialno (&serialno, NULL); + if (!err) + { + xfree (serialno); + err = agent_scd_getattr ("KEY-FPR", &info); + if (err) + log_error ("error retrieving key fingerprint from card: %s\n", + gpg_strerror (err)); + } + + err = get_seckey_default_or_card (ctrl, pk, + info.fpr1valid? info.fpr1 : NULL, 20); + if (err) + { + free_public_key (pk); + pk = NULL; + log_error ("no default secret key: %s\n", gpg_strerror (err)); + write_status_text (STATUS_INV_SGNR, get_inv_recpsgnr_code (err)); + } + else if ((err = openpgp_pk_test_algo2 (pk->pubkey_algo, use))) + { + free_public_key (pk); + pk = NULL; + log_error ("invalid default secret key: %s\n", gpg_strerror (err)); + write_status_text (STATUS_INV_SGNR, get_inv_recpsgnr_code (err)); + } + else + { + SK_LIST r; + + if (random_is_faked () && !is_insecure (ctrl, pk)) + { + log_info (_("key is not flagged as insecure - " + "can't use it with the faked RNG!\n")); + free_public_key (pk); + pk = NULL; + write_status_text (STATUS_INV_SGNR, + get_inv_recpsgnr_code (GPG_ERR_NOT_TRUSTED)); + } + else + { + r = xmalloc (sizeof *r); + r->pk = pk; + pk = NULL; + r->next = sk_list; + r->mark = 0; + sk_list = r; + } + } + } + else /* Check the given user ids. */ + { + strlist_t locusr_orig = locusr; + + for (; locusr; locusr = locusr->next) + { + PKT_public_key *pk; + + err = 0; + /* Do an early check against 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_info (_("skipped \"%s\": duplicated\n"), locusr->d); + continue; + } + pk = xmalloc_clear (sizeof *pk); + pk->req_usage = use; + if ((err = getkey_byname (ctrl, NULL, pk, locusr->d, 1, NULL))) + { + free_public_key (pk); + pk = NULL; + log_error (_("skipped \"%s\": %s\n"), + locusr->d, gpg_strerror (err)); + write_status_text_and_buffer + (STATUS_INV_SGNR, get_inv_recpsgnr_code (err), + locusr->d, strlen (locusr->d), -1); + } + else if (!key_present_in_sk_list (sk_list, pk)) + { + free_public_key (pk); + pk = NULL; + log_info (_("skipped: secret key already present\n")); + } + else if ((err = openpgp_pk_test_algo2 (pk->pubkey_algo, use))) + { + free_public_key (pk); + pk = NULL; + log_error ("skipped \"%s\": %s\n", locusr->d, gpg_strerror (err)); + write_status_text_and_buffer + (STATUS_INV_SGNR, get_inv_recpsgnr_code (err), + locusr->d, strlen (locusr->d), -1); + } + else + { + SK_LIST r; + + if (pk->version == 4 && (use & PUBKEY_USAGE_SIG) + && pk->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_public_key (pk); + pk = NULL; + write_status_text_and_buffer + (STATUS_INV_SGNR, + get_inv_recpsgnr_code (GPG_ERR_WRONG_KEY_USAGE), + locusr->d, strlen (locusr->d), -1); + } + else if (random_is_faked () && !is_insecure (ctrl, pk)) + { + log_info (_("key is not flagged as insecure - " + "can't use it with the faked RNG!\n")); + free_public_key (pk); + pk = NULL; + write_status_text_and_buffer + (STATUS_INV_SGNR, + get_inv_recpsgnr_code (GPG_ERR_NOT_TRUSTED), + locusr->d, strlen (locusr->d), -1); + } + else + { + r = xmalloc (sizeof *r); + r->pk = pk; + pk = NULL; + r->next = sk_list; + r->mark = 0; + sk_list = r; + } + } + } + } + + if (!err && !sk_list) + { + log_error ("no valid signators\n"); + write_status_text (STATUS_NO_SGNR, "0"); + err = gpg_error (GPG_ERR_NO_USER_ID); + } + + if (err) + release_sk_list (sk_list); + else + *ret_sk_list = sk_list; + return err; +} + + +/* Enumerate some secret keys (specifically, those specified with + * --default-key and --try-secret-key). Use the following procedure: + * + * 1) Initialize a void pointer to NULL + * 2) Pass a reference to this pointer to this function (CONTEXT) + * and provide space for the secret key (SK) + * 3) Call this function as long as it does not return an error (or + * until you are done). The error code GPG_ERR_EOF indicates the + * end of the listing. + * 4) Call this function a last time with SK set to NULL, + * so that can free it's context. + * + * TAKE CARE: When the function returns SK belongs to CONTEXT and may + * not be freed by the caller; neither on success nor on error. + * + * In pseudo-code: + * + * void *ctx = NULL; + * PKT_public_key *sk = xmalloc_clear (sizeof (*sk)); + * + * while ((err = enum_secret_keys (&ctx, sk))) + * { // Process SK. + * if (done) + * break; + * sk = xmalloc_clear (sizeof (*sk)); + * } + * + * // Release any resources used by CTX. + * enum_secret_keys (&ctx, NULL); + * + * if (gpg_err_code (err) != GPG_ERR_EOF) + * ; // An error occurred. + */ +gpg_error_t +enum_secret_keys (ctrl_t ctrl, void **context, PKT_public_key *sk) +{ + gpg_error_t err = 0; + const char *name; + int cardkey; /* We got an encryption fingerprint from the card */ + /* in c->info.fpr2. */ + kbnode_t keyblock; + struct + { + int eof; + int state; + int cardkey_done; + strlist_t sl; + strlist_t card_list; + char *serialno; + char fpr2[2 * MAX_FINGERPRINT_LEN + 3 ]; + struct agent_card_info_s info; + kbnode_t keyblock; + kbnode_t node; + getkey_ctx_t ctx; + SK_LIST results; + } *c = *context; + +#if MAX_FINGERPRINT_LEN < KEYGRIP_LEN +# error buffer too short for this configuration +#endif + + if (!c) + { + /* Make a new context. */ + c = xtrycalloc (1, sizeof *c); + if (!c) + { + err = gpg_error_from_syserror (); + free_public_key (sk); + return err; + } + *context = c; + } + + if (!sk) + { + /* Free the context. */ + xfree (c->serialno); + free_strlist (c->card_list); + release_sk_list (c->results); + release_kbnode (c->keyblock); + getkey_end (ctrl, c->ctx); + xfree (c); + *context = NULL; + return 0; + } + + if (c->eof) + { + free_public_key (sk); + return gpg_error (GPG_ERR_EOF); + } + + for (;;) + { + /* Loop until we have a keyblock. */ + while (!c->keyblock) + { + /* Loop over the list of secret keys. */ + do + { + char *serialno; + + name = NULL; + cardkey = 0; + keyblock = NULL; + switch (c->state) + { + case 0: /* First try to use the --default-key. */ + name = parse_def_secret_key (ctrl); + c->state = 1; + break; + + case 1: /* Init list of keys to try. */ + c->sl = opt.secret_keys_to_try; + c->state++; + break; + + case 2: /* Get next item from list. */ + if (c->sl) + { + name = c->sl->d; + c->sl = c->sl->next; + } + else + c->state++; + break; + + case 3: /* Init list of card keys to try. */ + err = agent_scd_cardlist (&c->card_list); + if (!err) + agent_scd_serialno (&c->serialno, NULL); + c->sl = c->card_list; + c->state++; + break; + + case 4: /* Get next item from card list. */ + if (c->sl) + { + err = agent_scd_serialno (&serialno, c->sl->d); + if (err) + { + if (opt.verbose) + log_info (_("error getting serial number of card: %s\n"), + gpg_strerror (err)); + c->sl = c->sl->next; + continue; + } + + xfree (serialno); + err = agent_scd_getattr ("KEY-FPR", &c->info); + if (!err) + { + if (c->info.fpr2valid) + { + c->fpr2[0] = '0'; + c->fpr2[1] = 'x'; + bin2hex (c->info.fpr2, sizeof c->info.fpr2, + c->fpr2 + 2); + name = c->fpr2; + cardkey = 1; + } + } + else if (gpg_err_code (err) == GPG_ERR_INV_NAME) + { + /* KEY-FPR not supported by the card - get + * the key using the keygrip. */ + char *keyref; + strlist_t kplist, sl; + const char *s; + int i; + + err = agent_scd_getattr_one ("$ENCRKEYID", &keyref); + if (!err) + { + err = agent_scd_keypairinfo (ctrl, &kplist); + if (!err) + { + for (sl = kplist; sl; sl = sl->next) + if ((s = strchr (sl->d, ' ')) + && !strcmp (s+1, keyref)) + break; + if (sl) + { + c->fpr2[0] = '&'; + for (i=1, s=sl->d; + (*s && *s != ' ' + && i < sizeof c->fpr2 - 3); + s++, i++) + c->fpr2[i] = *s; + c->fpr2[i] = 0; + name = c->fpr2; + } + else /* Restore error. */ + err = gpg_error (GPG_ERR_INV_NAME); + free_strlist (kplist); + } + } + xfree (keyref); + } + if (err) + log_error ("error retrieving key from card: %s\n", + gpg_strerror (err)); + + c->sl = c->sl->next; + } + else + { + serialno = c->serialno; + if (serialno) + { + /* Select the original card again. */ + agent_scd_serialno (&c->serialno, serialno); + xfree (serialno); + } + c->state++; + } + break; + + case 5: /* Init search context to enum all secret keys. */ + err = getkey_bynames (ctrl, &c->ctx, NULL, NULL, 1, + &keyblock); + if (err) + { + release_kbnode (keyblock); + keyblock = NULL; + getkey_end (ctrl, c->ctx); + c->ctx = NULL; + } + c->state++; + break; + + case 6: /* Get next item from the context. */ + if (c->ctx) + { + err = getkey_next (ctrl, c->ctx, NULL, &keyblock); + if (err) + { + release_kbnode (keyblock); + keyblock = NULL; + getkey_end (ctrl, c->ctx); + c->ctx = NULL; + } + } + else + c->state++; + break; + + default: /* No more names to check - stop. */ + c->eof = 1; + free_public_key (sk); + return gpg_error (GPG_ERR_EOF); + } + } + while ((!name || !*name) && !keyblock); + + if (keyblock) + c->node = c->keyblock = keyblock; + else + { + err = getkey_byname (ctrl, NULL, NULL, name, 1, &c->keyblock); + if (err) + { + /* getkey_byname might return a keyblock even in the + error case - I have not checked. Thus better release + it. */ + release_kbnode (c->keyblock); + c->keyblock = NULL; + /* If this was a card key we might not yet have the + * public key for it. Thus check whether the card + * can return the fingerprint of the encryption key + * and we can then find the public key via LDAP. */ + if (cardkey && !c->cardkey_done + && gpg_err_code (err) == GPG_ERR_NO_SECKEY) + { + /* Note that this code does not handle the case + * for two readers having both openpgp + * encryption keys. Only one will be tried. */ + c->cardkey_done = 1; + if (opt.debug) + log_debug ("using LDAP to find public key" + " for current card\n"); + if (!keyserver_import_fprint + (ctrl, c->info.fpr2, sizeof c->info.fpr2, + opt.keyserver, KEYSERVER_IMPORT_FLAG_LDAP)) + { + char fpr_string[MAX_FINGERPRINT_LEN * 2 + 1]; + + bin2hex (c->info.fpr2, sizeof c->info.fpr2, + fpr_string); + err = getkey_byname (ctrl, NULL, NULL, fpr_string, 1, + &c->keyblock); + if (err) + { + release_kbnode (c->keyblock); + c->keyblock = NULL; + } + else + c->node = c->keyblock; + } + } + } + else + c->node = c->keyblock; + } + } + + /* Get the next key from the current keyblock. */ + for (; c->node; c->node = c->node->next) + { + if (c->node->pkt->pkttype == PKT_PUBLIC_KEY + || c->node->pkt->pkttype == PKT_PUBLIC_SUBKEY) + { + SK_LIST r; + + /* Skip this candidate if it's already enumerated. */ + for (r = c->results; r; r = r->next) + if (!cmp_public_keys (r->pk, c->node->pkt->pkt.public_key)) + break; + if (r) + continue; + + copy_public_key (sk, c->node->pkt->pkt.public_key); + c->node = c->node->next; + + r = xtrycalloc (1, sizeof (*r)); + if (!r) + { + err = gpg_error_from_syserror (); + free_public_key (sk); + return err; + } + + r->pk = sk; + r->next = c->results; + c->results = r; + + return 0; /* Found. */ + } + } + + /* Dispose the keyblock and continue. */ + release_kbnode (c->keyblock); + c->keyblock = NULL; + } +} diff --git a/g10/t-keydb-get-keyblock.c b/g10/t-keydb-get-keyblock.c new file mode 100644 index 0000000..167a9bb --- /dev/null +++ b/g10/t-keydb-get-keyblock.c @@ -0,0 +1,65 @@ +/* t-keydb-get-keyblock.c - Tests for keydb.c. + * Copyright (C) 2015 g10 Code GmbH + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include "test.c" + +#include "keydb.h" + +static void +do_test (int argc, char *argv[]) +{ + char *fname; + int rc; + KEYDB_HANDLE hd1; + KEYDB_SEARCH_DESC desc1; + KBNODE kb1; + + (void) argc; + (void) argv; + + /* t-keydb-get-keyblock.gpg contains two keys: a modern key followed + by a legacy key. If we get the keyblock for the modern key, we + shouldn't get + + - */ + fname = prepend_srcdir ("t-keydb-get-keyblock.gpg"); + rc = keydb_add_resource (fname, 0); + test_free (fname); + if (rc) + ABORT ("Failed to open keyring."); + + hd1 = keydb_new (); + if (!hd1) + ABORT (""); + + rc = classify_user_id ("8061 5870 F5BA D690 3336 86D0 F2AD 85AC 1E42 B367", + &desc1, 0); + if (rc) + ABORT ("Failed to convert fingerprint for 1E42B367"); + + rc = keydb_search (hd1, &desc1, 1, NULL); + if (rc) + ABORT ("Failed to lookup key associated with 1E42B367"); + + rc = keydb_get_keyblock (hd1, &kb1); + TEST_P ("", ! rc); + + keydb_release (hd1); + release_kbnode (kb1); +} diff --git a/g10/t-keydb-get-keyblock.gpg b/g10/t-keydb-get-keyblock.gpg new file mode 100644 index 0000000..521487e Binary files /dev/null and b/g10/t-keydb-get-keyblock.gpg differ diff --git a/g10/t-keydb-keyring.kbx b/g10/t-keydb-keyring.kbx new file mode 100644 index 0000000..a1d3af0 Binary files /dev/null and b/g10/t-keydb-keyring.kbx differ diff --git a/g10/t-keydb.c b/g10/t-keydb.c new file mode 100644 index 0000000..5eb8d01 --- /dev/null +++ b/g10/t-keydb.c @@ -0,0 +1,104 @@ +/* t-keydb.c - Tests for keydb.c. + * Copyright (C) 2015 g10 Code GmbH + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include "test.c" + +#include "keydb.h" + +static void +do_test (int argc, char *argv[]) +{ + int rc; + KEYDB_HANDLE hd1, hd2; + KEYDB_SEARCH_DESC desc1, desc2; + KBNODE kb1, kb2, p; + char *uid1; + char *uid2; + char *fname; + + (void) argc; + (void) argv; + + fname = prepend_srcdir ("t-keydb-keyring.kbx"); + rc = keydb_add_resource (fname, 0); + test_free (fname); + if (rc) + ABORT ("Failed to open keyring."); + + hd1 = keydb_new (); + if (!hd1) + ABORT (""); + hd2 = keydb_new (); + if (!hd2) + ABORT (""); + + rc = classify_user_id ("2689 5E25 E844 6D44 A26D 8FAF 2F79 98F3 DBFC 6AD9", + &desc1, 0); + if (rc) + ABORT ("Failed to convert fingerprint for DBFC6AD9"); + + rc = keydb_search (hd1, &desc1, 1, NULL); + if (rc) + ABORT ("Failed to lookup key associated with DBFC6AD9"); + + + classify_user_id ("8061 5870 F5BA D690 3336 86D0 F2AD 85AC 1E42 B367", + &desc2, 0); + if (rc) + ABORT ("Failed to convert fingerprint for 1E42B367"); + + rc = keydb_search (hd2, &desc2, 1, NULL); + if (rc) + ABORT ("Failed to lookup key associated with 1E42B367"); + + rc = keydb_get_keyblock (hd2, &kb2); + if (rc) + ABORT ("Failed to get keyblock for 1E42B367"); + + rc = keydb_get_keyblock (hd1, &kb1); + if (rc) + ABORT ("Failed to get keyblock for DBFC6AD9"); + + p = kb1; + while (p && p->pkt->pkttype != PKT_USER_ID) + p = p->next; + if (! p) + ABORT ("DBFC6AD9 has no user id packet"); + uid1 = p->pkt->pkt.user_id->name; + + p = kb2; + while (p && p->pkt->pkttype != PKT_USER_ID) + p = p->next; + if (! p) + ABORT ("1E42B367 has no user id packet"); + uid2 = p->pkt->pkt.user_id->name; + + if (verbose) + { + printf ("user id for DBFC6AD9: %s\n", uid1); + printf ("user id for 1E42B367: %s\n", uid2); + } + + TEST_P ("cache consistency", strcmp (uid1, uid2) != 0); + + release_kbnode (kb1); + release_kbnode (kb2); + keydb_release (hd1); + keydb_release (hd2); +} diff --git a/g10/t-rmd160.c b/g10/t-rmd160.c new file mode 100644 index 0000000..e79d15d --- /dev/null +++ b/g10/t-rmd160.c @@ -0,0 +1,91 @@ +/* t-rmd160.c - Module test for rmd160.c + * Copyright (C) 2008 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include +#include +#include +#include + +#include "rmd160.h" + +#define pass() do { ; } while(0) +#define fail(a) do { fprintf (stderr, "%s:%d: test %d failed\n",\ + __FILE__,__LINE__, (a)); \ + exit (1); \ + } while(0) + +static void +run_test (void) +{ + static struct + { + const char *data; + const char *expect; + } testtbl[] = + { + { "", + "\x9c\x11\x85\xa5\xc5\xe9\xfc\x54\x61\x28" + "\x08\x97\x7e\xe8\xf5\x48\xb2\x25\x8d\x31" }, + { "a", + "\x0b\xdc\x9d\x2d\x25\x6b\x3e\xe9\xda\xae" + "\x34\x7b\xe6\xf4\xdc\x83\x5a\x46\x7f\xfe" }, + { "abc", + "\x8e\xb2\x08\xf7\xe0\x5d\x98\x7a\x9b\x04" + "\x4a\x8e\x98\xc6\xb0\x87\xf1\x5a\x0b\xfc" }, + { "message digest", + "\x5d\x06\x89\xef\x49\xd2\xfa\xe5\x72\xb8" + "\x81\xb1\x23\xa8\x5f\xfa\x21\x59\x5f\x36" }, + { "abcdefghijklmnopqrstuvwxyz", + "\xf7\x1c\x27\x10\x9c\x69\x2c\x1b\x56\xbb" + "\xdc\xeb\x5b\x9d\x28\x65\xb3\x70\x8d\xbc" }, + { "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789", + "\xb0\xe2\x0b\x6e\x31\x16\x64\x02\x86\xed" + "\x3a\x87\xa5\x71\x30\x79\xb2\x1f\x51\x89" }, + { "1234567890" "1234567890" "1234567890" "1234567890" + "1234567890" "1234567890" "1234567890" "1234567890", + "\x9b\x75\x2e\x45\x57\x3d\x4b\x39\xf4\xdb" + "\xd3\x32\x3c\xab\x82\xbf\x63\x32\x6b\xfb" }, + + { NULL, NULL } + }; + int idx; + char digest[20]; + + for (idx=0; testtbl[idx].data; idx++) + { + rmd160_hash_buffer (digest, + testtbl[idx].data, strlen(testtbl[idx].data)); + if (memcmp (digest, testtbl[idx].expect, 20)) + fail (idx); + } +} + + +int +main (int argc, char **argv) +{ + (void)argc; + (void)argv; + + run_test (); + + return 0; +} diff --git a/g10/t-stutter-data.asc b/g10/t-stutter-data.asc new file mode 100644 index 0000000..ad8bfae --- /dev/null +++ b/g10/t-stutter-data.asc @@ -0,0 +1 @@ +É?Òéq`Hêhâ© ŠÅÒV½çxDI2‡3ø”Oœ¢*Gû¨—y¾Iaª«lš{’eîwò{B»c1‡Bµ³ \ No newline at end of file diff --git a/g10/t-stutter.c b/g10/t-stutter.c new file mode 100644 index 0000000..503a920 --- /dev/null +++ b/g10/t-stutter.c @@ -0,0 +1,613 @@ +/* t-stutter.c - Test the stutter exploit. + * Copyright (C) 2016 g10 Code GmbH + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +/* This test is based on the paper: "An Attack on CFB Mode Encryption + * as Used by OpenPGP." This attack uses a padding oracle to decrypt + * the first two bytes of each block (which are normally 16 bytes + * large). Concretely, if an attacker can use this attack if it can + * sense whether the quick integrity check failed. See RFC 4880, + * Section 5.7 for an explanation of this quick check. + * + * The concrete attack, as described in the paper, only works for + * PKT_ENCRYPTED packets; it does not work for PKT_ENCRYPTED_MDC + * packets, which use a slightly different CFB mode (they don't + * include a sync after the IV). But, small modifications should + * allow the attack to work for PKT_ENCRYPTED_MDC packets. + * + * The cost of this attack is 2^15 + i * 2^15 oracle queries, where i + * is the number of blocks the attack wants to decrypt. This attack + * is completely unfeasible when gpg is used interactively, but it + * could work when used as a service. + * + * How to generate a test message: + * + * $ echo 0123456789abcdefghijklmnopqrstuvwxyz | \ + * gpg --disable-mdc -z 0 -c > msg.asc + * $ gpg --list-packets msg.asc + * # Make sure the encryption packet contains a literal packet (without + * # any nesting). + * $ gpgsplit msg.asc + * $ gpg --show-session-key -d msg.asc + * $ ./t-stutter --debug SESSION_KEY 000002-009.encrypted + */ + +#include +#include +#include + +#include "gpg.h" +#include "main.h" +#include "../common/types.h" +#include "../common/util.h" +#include "dek.h" +#include "../common/logging.h" + +#include "test.c" + +static void +log_hexdump (byte *buffer, int length) +{ + int written = 0; + + fprintf (stderr, "%d bytes:\n", length); + while (length > 0) + { + int have = length > 16 ? 16 : length; + int i; + char formatted[2 * 16 + 1]; + char text[16 + 1]; + + fprintf (stderr, "%-8d ", written); + bin2hex (buffer, have, formatted); + for (i = 0; i < 16; i ++) + { + if (i % 2 == 0) + fputc (' ', stderr); + if (i % 8 == 0) + fputc (' ', stderr); + + if (i < have) + fwrite (&formatted[2 * i], 2, 1, stderr); + else + fwrite (" ", 2, 1, stderr); + } + + for (i = 0; i < have; i ++) + { + if (isprint (buffer[i])) + text[i] = buffer[i]; + else + text[i] = '.'; + } + text[i] = 0; + + fprintf (stderr, " "); + if (strlen (text) > 8) + { + fwrite (text, 8, 1, stderr); + fputc (' ', stderr); + fwrite (&text[8], strlen (text) - 8, 1, stderr); + } + else + fwrite (text, strlen (text), 1, stderr); + fputc ('\n', stderr); + + buffer += have; + length -= have; + written += have; + } + + return; +} + +static char * +hexstr (const byte *bytes) +{ + static int i; + static char bufs[100][7]; + + i ++; + if (i == 100) + i = 0; + + sprintf (bufs[i], "0x%02X%02X", bytes[0], bytes[1]); + return bufs[i]; +} + +/* xor the two bytes starting at A with the two bytes starting at B + and return the result. */ +static byte * +bufxor2 (const byte *a, const byte *b) +{ + static int i; + static char bufs[100][2]; + + i ++; + if (i == 100) + i = 0; + + bufs[i][0] = a[0] ^ b[0]; + bufs[i][1] = a[1] ^ b[1]; + return bufs[i]; +} + +/* The session key stays constant. */ +static DEK dek; +int blocksize; + +/* Decode the session key, which is in the format output by gpg + --show-session-key. */ +static void +parse_session_key (char *session_key) +{ + char *tail; + char *p = session_key; + + errno = 0; + dek.algo = strtol (p, &tail, 10); + if (errno || (tail && *tail != ':')) + log_fatal ("Invalid session key specification. " + "Expected: cipher-id:HEXADECIMAL-CHRACTERS\n"); + + /* Skip the ':'. */ + p = tail + 1; + + if (strlen (p) % 2 != 0) + log_fatal ("Session key must consist of an even number of hexadecimal characters.\n"); + + dek.keylen = strlen (p) / 2; + log_assert (dek.keylen <= sizeof (dek.key)); + + if (hex2bin (p, dek.key, dek.keylen) == -1) + log_fatal ("Session key must only contain hexadecimal characters\n"); + + blocksize = openpgp_cipher_get_algo_blklen (dek.algo); + if ( !blocksize || blocksize > 16 ) + log_fatal ("unsupported blocksize %u\n", blocksize ); + + return; +} + +/* The ciphertext, the plaintext as decrypted by the good session key, + and the cfb stream (derived from the ciphertext and the + plaintext). */ +static int msg_len; +static byte *msg; +static byte *msg_plaintext; +static byte *msg_cfb; + +/* Whether we need to resynchronize the CFB after writing the random + data (this is the case for encrypted packets, but not encrypted and + integrity protected packets). */ +static int sync; + +static int +block_offset (int i) +{ + int extra = 0; + + log_assert (i >= 1); + /* Make sure blocksize has been initialized. */ + log_assert (blocksize); + + if (i > 2) + { + i -= 2; + extra = blocksize + 2; + } + return (i - 1) * blocksize + extra; +} + +/* Return the ith block from TEXT. The first block is labeled 1. + Note: consistent with the OpenPGP message format, the second block + (i=2) is just 2 bytes. */ +static byte * +block (byte *text, int len, int i) +{ + int offset = block_offset (i); + + log_assert (offset < len); + return &text[offset]; +} + +/* Return true if the quick integrity check passes. Also, if + PLAINTEXTP is not NULL, return the decrypted plaintext in + *PLAINTEXTP. If CFBP is not NULL, return the CFB byte stream in + *CFBP. */ +static int +oracle (int debug, byte *ciphertext, int len, byte **plaintextp, byte **cfbp) +{ + int rc = 0; + unsigned nprefix; + gcry_cipher_hd_t cipher_hd = NULL; + byte *plaintext = NULL; + byte *cfb = NULL; + + /* Make sure DEK was initialized. */ + log_assert (dek.algo); + log_assert (dek.keylen); + log_assert (blocksize); + + nprefix = blocksize; + if (len < nprefix + 2) + { + /* An invalid message. We can't check that during parsing + because we may not know the used cipher then. */ + rc = gpg_error (GPG_ERR_INV_PACKET); + goto leave; + } + + rc = openpgp_cipher_open (&cipher_hd, dek.algo, + GCRY_CIPHER_MODE_CFB, + (! sync /* ed->mdc_method || dek.algo >= 100 */ ? + 0 : GCRY_CIPHER_ENABLE_SYNC)); + if (rc) + log_fatal ("Failed to open cipher: %s\n", gpg_strerror (rc)); + + rc = gcry_cipher_setkey (cipher_hd, dek.key, dek.keylen); + if (gpg_err_code (rc) == GPG_ERR_WEAK_KEY) + { + log_info ("WARNING: message was encrypted with" + " a weak key in the symmetric cipher.\n"); + rc=0; + } + else if( rc ) + log_fatal ("key setup failed: %s\n", gpg_strerror (rc)); + + gcry_cipher_setiv (cipher_hd, NULL, 0); + + if (debug) + { + log_debug ("Encrypted data:\n"); + log_hexdump(ciphertext, len); + } + plaintext = xmalloc_clear (len); + gcry_cipher_decrypt (cipher_hd, plaintext, blocksize + 2, + ciphertext, blocksize + 2); + gcry_cipher_sync (cipher_hd); + if (len > blocksize+2) + gcry_cipher_decrypt (cipher_hd, + &plaintext[blocksize+2], len-(blocksize+2), + &ciphertext[blocksize+2], len-(blocksize+2)); + + if (debug) + { + log_debug ("Decrypted data:\n"); + log_hexdump (plaintext, len); + log_debug ("R_{b-1,b} = %s\n", hexstr (&plaintext[blocksize - 2])); + log_debug ("R_{b+1,b+2} = %s\n", hexstr (&plaintext[blocksize])); + } + + if (cfbp || debug) + { + int i; + cfb = xmalloc (len); + for (i = 0; i < len; i ++) + cfb[i] = plaintext[i] ^ ciphertext[i]; + + log_assert (len >= blocksize + 2); + + if (debug) + { + log_debug ("cfb:\n"); + log_hexdump (cfb, len); + + log_debug ("E_k([C_1]_{1,2}) = C_2 xor R (%s xor %s) = %s\n", + hexstr (&ciphertext[blocksize]), + hexstr (&plaintext[blocksize]), + hexstr (bufxor2 (&ciphertext[blocksize], + &plaintext[blocksize]))); + if (len >= blocksize + 4) + log_debug ("D = Ek([C1]_{3-b} || C_2)_{1-2} (%s) xor C2 (%s) xor E_k(0)_{b-1,b} (%s) = %s\n", + hexstr (&cfb[blocksize + 2]), + hexstr (&ciphertext[blocksize]), + hexstr (&cfb[blocksize - 2]), + hexstr (bufxor2 (bufxor2 (&cfb[blocksize + 2], + &ciphertext[blocksize]), + &cfb[blocksize - 2]))); + } + } + + if (plaintext[nprefix-2] != plaintext[nprefix] + || plaintext[nprefix-1] != plaintext[nprefix+1]) + { + rc = gpg_error (GPG_ERR_BAD_KEY); + goto leave; + } + + leave: + if (! rc && plaintextp) + *plaintextp = plaintext; + else + xfree (plaintext); + + if (! rc && cfbp) + *cfbp = cfb; + else + xfree (cfb); + + if (cipher_hd) + gcry_cipher_close (cipher_hd); + return rc; +} + +/* Query the oracle with D=D for block B. */ +static int +oracle_test (unsigned int d, int b, int debug) +{ + byte probe[32 + 2]; + + log_assert (blocksize + 2 <= sizeof probe); + log_assert (d < 256 * 256); + + if (b == 1) + memcpy (probe, &msg[2], blocksize); + else + memcpy (probe, block (msg, msg_len, b), blocksize); + + probe[blocksize] = d >> 8; + probe[blocksize + 1] = d & 0xff; + + if (debug) + log_debug ("oracle (0x%04X):\n", d); + + return oracle (debug, probe, blocksize + 2, NULL, NULL) == 0; +} + +static void +do_test (int argc, char *argv[]) +{ + int i; + int debug = 0; + char *filename = NULL; + int help = 0; + + byte *raw_data; + int raw_data_len; + + (void)current_test_group_failed; + for (i = 1; i < argc; i ++) + { + if (strcmp (argv[i], "--debug") == 0) + debug = 1; + else if (! blocksize) + parse_session_key (argv[i]); + else if (! filename) + filename = argv[i]; + else + { + help = 1; + break; + } + } + + if (! blocksize && ! filename && (filename = prepend_srcdir ("t-stutter-data.asc"))) + /* Try defaults. */ + { + parse_session_key ("9:9274A8EC128E850C6DDDF9EAC68BFA84FC7BC05F340DA41D78C93D0640C7C503"); + } + + if (help || ! blocksize || ! filename) + log_fatal ("Usage: %s [--debug] SESSION_KEY ENCRYPTED_PKT\n", argv[0]); + + /* Don't read more than a KB. */ + raw_data_len = 1024; + raw_data = xmalloc (raw_data_len); + + { + FILE *fp; + int r; + + fp = fopen (filename, "r"); + if (! fp) + log_fatal ("Opening %s: %s\n", filename, strerror (errno)); + r = fread (raw_data, 1, raw_data_len, fp); + fclose (fp); + + /* We need at least the random data, the encrypted and literal + packets' headers and some body. */ + if (r < (blocksize + 2 /* Random data. */ + + 2 * blocksize /* Header + some plaintext. */)) + log_fatal ("Not enough data (need at least %d bytes of plain text): %s.\n", + blocksize + 2, strerror (errno)); + raw_data_len = r; + + if (debug) + { + log_debug ("First few bytes of the raw data:\n"); + log_hexdump (raw_data, raw_data_len > 8 ? 8 : raw_data_len); + } + } + + /* Parse the packet's header. */ + { + int ctb = raw_data[0]; + int new_format = ctb & (1 << 7); + int pkttype = (ctb & ((1 << 5) - 1)) >> (new_format ? 0 : 2); + int hdrlen; + + if (new_format) + { + if (debug) + log_debug ("len encoded: 0x%x (%d)\n", raw_data[1], raw_data[1]); + if (raw_data[1] < 192) + hdrlen = 2; + else if (raw_data[1] < 224) + hdrlen = 3; + else if (raw_data[1] == 255) + hdrlen = 5; + else + hdrlen = 2; + } + else + { + int lentype = ctb & 0x3; + if (lentype == 0) + hdrlen = 2; + else if (lentype == 1) + hdrlen = 3; + else if (lentype == 2) + hdrlen = 5; + else + /* Indeterminate. */ + hdrlen = 1; + } + + if (debug) + log_debug ("ctb = %x; %s format, hdrlen: %d, packet: %s\n", + ctb, new_format ? "new" : "old", + hdrlen, + pkttype_str (pkttype)); + + if (! (pkttype == PKT_ENCRYPTED || pkttype == PKT_ENCRYPTED_MDC)) + log_fatal ("%s does not contain an encrypted packet, but a %s.\n", + filename, pkttype_str (pkttype)); + + if (pkttype == PKT_ENCRYPTED_MDC) + { + /* The first byte following the header is the version, which + is 1. */ + log_assert (raw_data[hdrlen] == 1); + hdrlen ++; + sync = 0; + } + else + sync = 1; + + msg = &raw_data[hdrlen]; + msg_len = raw_data_len - hdrlen; + } + + log_assert (msg_len >= blocksize + 2); + + { + /* This can at least partially be guessed. So we just assume that + it is known. */ + int d; + int found; + const byte *m1; + byte e_k_zero[2]; + + if (oracle (debug, msg, msg_len, &msg_plaintext, &msg_cfb) == 0) + { + if (debug) + log_debug ("Session key appears to be good.\n"); + } + else + log_fatal ("Session key is bad!\n"); + + m1 = &msg_plaintext[blocksize + 2]; + if (debug) + log_debug ("First two bytes of plaintext are: %02X (%c) %02X (%c)\n", + m1[0], isprint (m1[0]) ? m1[0] : '?', + m1[1], isprint (m1[1]) ? m1[1] : '?'); + + for (d = 0; d < 256 * 256; d ++) + if ((found = oracle_test (d, 1, 0))) + break; + + if (! found) + log_fatal ("Failed to find d!\n"); + + if (debug) + oracle_test (d, 1, 1); + + if (debug) + log_debug ("D = %d (%x) looks good.\n", d, d); + + { + byte *c2 = block (msg, msg_len, 2); + byte D[2] = { d >> 8, d & 0xFF }; + byte *c3 = block (msg, msg_len, 3); + + memcpy (e_k_zero, + bufxor2 (bufxor2 (c2, D), + bufxor2 (c3, m1)), + sizeof (e_k_zero)); + + if (debug) + { + log_debug ("C2 = %s\n", hexstr (c2)); + log_debug ("D = %s\n", hexstr (D)); + log_debug ("C3 = %s\n", hexstr (c3)); + log_debug ("M = %s\n", hexstr (m1)); + log_debug ("E_k([C1]_{3-b} || C_2) = C3 xor M1 = %s\n", + hexstr (bufxor2 (c3, m1))); + log_debug ("E_k(0)_{b-1,b} = %s\n", hexstr (e_k_zero)); + } + } + + /* Figure out the first 2 bytes of M2... (offset 16 & 17 of the + plain text assuming the blocksize == 16 or bytes 34 & 35 of the + decrypted cipher text, i.e., C4). */ + for (i = 1; block_offset (i + 3) + 2 <= msg_len; i ++) + { + byte e_k_prime[2]; + byte m[2]; + byte *ct = block (msg, msg_len, i + 2); + byte *pt = block (msg_plaintext, msg_len, 2 + i + 1); + + for (d = 0; d < 256 * 256; d ++) + if (oracle_test (d, i + 2, 0)) + { + found = 1; + break; + } + + if (! found) + log_fatal ("Failed to find a valid d for block %d\n", i); + + if (debug) + log_debug ("Block %d: oracle: D = %04X passes integrity check\n", + i, d); + + { + byte D[2] = { d >> 8, d & 0xFF }; + memcpy (e_k_prime, + bufxor2 (bufxor2 (&ct[blocksize - 2], D), e_k_zero), + sizeof (e_k_prime)); + + memcpy (m, bufxor2 (e_k_prime, block (msg, msg_len, i + 3)), + sizeof (m)); + } + + if (debug) + log_debug ("=> block %d starting at %zd starts with: " + "%s (%c%c)\n", + i, (size_t) pt - (size_t) msg_plaintext, + hexstr (m), + isprint (m[0]) ? m[0] : '?', isprint (m[1]) ? m[1] : '?'); + + if (m[0] != pt[0] || m[1] != pt[1]) + { + log_debug ("oracle attack failed! Expected %s (%c%c), got %s\n", + hexstr (pt), + isprint (pt[0]) ? pt[0] : '?', + isprint (pt[1]) ? pt[1] : '?', + hexstr (m)); + tests_failed++; + } + } + + if (i == 1) + log_fatal ("Message is too short, nothing to test.\n"); + } + + xfree (filename); +} diff --git a/g10/tdbdump.c b/g10/tdbdump.c new file mode 100644 index 0000000..a86558b --- /dev/null +++ b/g10/tdbdump.c @@ -0,0 +1,240 @@ +/* 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 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gpg.h" +#include "../common/status.h" +#include "../common/iobuf.h" +#include "keydb.h" +#include "../common/util.h" +#include "trustdb.h" +#include "options.h" +#include "packet.h" +#include "main.h" +#include "../common/i18n.h" +#include "tdbio.h" + + +#define HEXTOBIN(x) ( (x) >= '0' && (x) <= '9' ? ((x)-'0') : \ + (x) >= 'A' && (x) <= 'F' ? ((x)-'A'+10) : ((x)-'a'+10)) + + +/* + * Write a record; die on error. + */ +static void +write_record (ctrl_t ctrl, TRUSTREC *rec) +{ + int rc = tdbio_write_record (ctrl, rec); + if( !rc ) + return; + log_error(_("trust record %lu, type %d: write failed: %s\n"), + rec->recnum, rec->rectype, gpg_strerror (rc) ); + tdbio_invalid(); +} + + +/* + * Dump the entire trustdb to FP or only the entries of one key. + */ +void +list_trustdb (ctrl_t ctrl, estream_t fp, const char *username) +{ + TRUSTREC rec; + + (void)username; + + init_trustdb (ctrl, 0); + /* For now we ignore the user ID. */ + if (1) + { + ulong recnum; + int i; + + es_fprintf (fp, "TrustDB: %s\n", tdbio_get_dbname ()); + for (i = 9 + strlen (tdbio_get_dbname()); i > 0; i-- ) + es_fputc ('-', fp); + es_putc ('\n', fp); + for (recnum=0; !tdbio_read_record (recnum, &rec, 0); recnum++) + tdbio_dump_record (&rec, fp); + } +} + + + + + +/**************** + * Print a list of all defined owner trust value. + */ +void +export_ownertrust (ctrl_t ctrl) +{ + TRUSTREC rec; + ulong recnum; + int i; + byte *p; + + init_trustdb (ctrl, 0); + es_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) + { + /* Skip records with no ownertrust set or those with trust + * set via --trusted-key. */ + if (!rec.r.trust.ownertrust || (rec.r.trust.flags & 1)) + continue; + p = rec.r.trust.fingerprint; + for (i=0; i < 20; i++, p++ ) + es_printf("%02X", *p ); + es_printf (":%u:\n", (unsigned int)rec.r.trust.ownertrust ); + } + } +} + + +void +import_ownertrust (ctrl_t ctrl, const char *fname ) +{ + estream_t 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 (ctrl, 0); + if( iobuf_is_pipe_filename (fname) ) { + fp = es_stdin; + fname = "[stdin]"; + is_stdin = 1; + } + else if( !(fp = es_fopen( fname, "r" )) ) { + log_error ( _("can't open '%s': %s\n"), fname, strerror(errno) ); + return; + } + + if (is_secured_file (es_fileno (fp))) + { + es_fclose (fp); + gpg_err_set_errno (EPERM); + log_error (_("can't open '%s': %s\n"), fname, strerror(errno) ); + return; + } + + while (es_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 (ctrl, fpr, &rec); + if( !rc ) { /* found: update */ + if (rec.r.trust.ownertrust != otrust) + { + if (!opt.quiet) + { + 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; + rec.r.trust.flags &= ~(rec.r.trust.flags & 1); + write_record (ctrl, &rec); + any = 1; + } + } + else if (gpg_err_code (rc) == GPG_ERR_NOT_FOUND) { /* insert */ + if (!opt.quiet) + log_info("inserting ownertrust of %u\n", otrust ); + memset (&rec, 0, sizeof rec); + rec.recnum = tdbio_new_recnum (ctrl); + rec.rectype = RECTYPE_TRUST; + memcpy (rec.r.trust.fingerprint, fpr, 20); + rec.r.trust.ownertrust = otrust; + write_record (ctrl, &rec); + any = 1; + } + else /* error */ + log_error (_("error finding trust record in '%s': %s\n"), + fname, gpg_strerror (rc)); + } + if (es_ferror (fp)) + log_error ( _("read error in '%s': %s\n"), fname, strerror(errno) ); + if (!is_stdin) + es_fclose (fp); + + if (any) + { + revalidation_mark (ctrl); + rc = tdbio_sync (); + if (rc) + log_error (_("trustdb: sync failed: %s\n"), gpg_strerror (rc) ); + } + +} diff --git a/g10/tdbio.c b/g10/tdbio.c new file mode 100644 index 0000000..fdb8871 --- /dev/null +++ b/g10/tdbio.c @@ -0,0 +1,1936 @@ +/* tdbio.c - trust database I/O operations + * Copyright (C) 1998-2002, 2012 Free Software Foundation, Inc. + * Copyright (C) 1998-2015 Werner Koch + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gpg.h" +#include "../common/status.h" +#include "../common/iobuf.h" +#include "../common/util.h" +#include "options.h" +#include "main.h" +#include "../common/i18n.h" +#include "trustdb.h" +#include "tdbio.h" + +#if defined(HAVE_DOSISH_SYSTEM) && !defined(ftruncate) +#define ftruncate chsize +#endif + +#if defined(HAVE_DOSISH_SYSTEM) || defined(__CYGWIN__) +#define MY_O_BINARY O_BINARY +#else +#define MY_O_BINARY 0 +#endif + +/* We use ERRNO despite that the cegcc provided open/read/write + functions don't set ERRNO - at least show that ERRNO does not make + sense. */ +#ifdef HAVE_W32CE_SYSTEM +#undef strerror +#define strerror(a) ("[errno not available]") +#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]; +}; + +/* Size of the cache. The SOFT value is the general one. While in a + transaction this may not be sufficient and thus we may increase it + then up to the HARD limit. */ +#define MAX_CACHE_ENTRIES_SOFT 200 +#define MAX_CACHE_ENTRIES_HARD 10000 + + +/* The cache is controlled by these variables. */ +static CACHE_CTRL cache_list; +static int cache_entries; +static int cache_is_dirty; + + +/* An object to pass information to cmp_krec_fpr. */ +struct cmp_krec_fpr_struct +{ + int pubkey_algo; + const char *fpr; + int fprlen; +}; + +/* An object used to pass information to cmp_[s]dir. */ +struct cmp_xdir_struct +{ + int pubkey_algo; + u32 keyid[2]; +}; + + +/* The name of the trustdb file. */ +static char *db_name; + +/* The handle for locking the trustdb file and a counter to record how + * often this lock has been taken. That counter is required becuase + * dotlock does not implemen recursive locks. */ +static dotlock_t lockhandle; +static unsigned int is_locked; + +/* The file descriptor of the trustdb. */ +static int db_fd = -1; + +/* A flag indicating that a transaction is active. */ +/* static int in_transaction; Not yet used. */ + + + +static void open_db (void); +static void create_hashtable (ctrl_t ctrl, TRUSTREC *vr, int type); + + + +/* + * Take a lock on the trustdb file name. I a lock file can't be + * created the function terminates the process. Except for a + * different return code the function does nothing if the lock has + * already been taken. + * + * Returns: True if lock already exists, False if the lock has + * actually been taken. + */ +static int +take_write_lock (void) +{ + int rc; + + if (!lockhandle) + lockhandle = dotlock_create (db_name, 0); + if (!lockhandle) + log_fatal ( _("can't create lock for '%s'\n"), db_name ); + + if (!is_locked) + { + if (dotlock_take (lockhandle, -1) ) + log_fatal ( _("can't lock '%s'\n"), db_name ); + rc = 0; + } + else + rc = 1; + + if (opt.lock_once) + is_locked = 1; + else + is_locked++; + return rc; +} + + +/* + * Release a lock from the trustdb file unless the global option + * --lock-once has been used. + */ +static void +release_write_lock (void) +{ + if (opt.lock_once) + return; /* Don't care; here IS_LOCKED is fixed to 1. */ + + if (!is_locked) + { + log_error ("Ooops, tdbio:release_write_lock with no lock held\n"); + return; + } + if (--is_locked) + return; + + if (dotlock_release (lockhandle)) + log_error ("Oops, tdbio:release_write_locked failed\n"); +} + + + +/************************************* + ************* record cache ********** + *************************************/ + +/* + * Get the data from the record cache and return a pointer into that + * cache. Caller should copy the returned 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; +} + + +/* + * Write a cached item back to the trustdb file. + * + * Returns: 0 on success or an error code. + */ +static int +write_cache_item (CACHE_CTRL r) +{ + gpg_error_t err; + int n; + + if (lseek (db_fd, r->recno * TRUST_RECORD_LEN, SEEK_SET) == -1) + { + err = gpg_error_from_syserror (); + log_error (_("trustdb rec %lu: lseek failed: %s\n"), + r->recno, strerror (errno)); + return err; + } + n = write (db_fd, r->data, TRUST_RECORD_LEN); + if (n != TRUST_RECORD_LEN) + { + err = gpg_error_from_syserror (); + log_error (_("trustdb rec %lu: write failed (n=%d): %s\n"), + r->recno, n, strerror (errno) ); + return err; + } + r->flags.dirty = 0; + return 0; +} + + +/* + * Put data into the cache. This function may flush + * some cache entries if the cache is filled up. + * + * Returns: 0 on success or an error code. + */ +static 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 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: Put into cache. */ + 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; + + /* We discard a third of the clean entries. */ + n = clean_count / 3; + 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; + } + } + + /* Now put into the cache. */ + log_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: We have to flush some dirty entries. */ +#if 0 /* Transactions are not yet used. */ + if (in_transaction) + { + /* But we can't do this while in a transaction. Thus we + * increase the cache size instead. */ + if (cache_entries < MAX_CACHE_ENTRIES_HARD) + { + 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; + } + /* Hard limit for the cache size reached. */ + log_info (_("trustdb transaction too large\n")); + return GPG_ERR_RESOURCE_LIMIT; + } +#endif + + if (dirty_count) + { + int n; + + /* Discard some dirty entries. */ + n = dirty_count / 5; + if (!n) + n = 1; + + take_write_lock (); + for (unused = NULL, r = cache_list; r; r = r->next) + { + if (r->flags.used && r->flags.dirty) + { + int rc; + + rc = write_cache_item (r); + if (rc) + return rc; + if (!unused) + unused = r; + r->flags.used = 0; + cache_entries--; + if (!--n) + break; + } + } + release_write_lock (); + + /* Now put into the cache. */ + log_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; + } + + /* We should never reach this. */ + BUG(); +} + + +/* Return true if the cache is dirty. */ +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 0 /* Transactions are not yet used. */ + if( in_transaction ) + log_bug("tdbio: syncing while in transaction\n"); +#endif + + if( !cache_is_dirty ) + return 0; + + if (!take_write_lock ()) + 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) + release_write_lock (); + + return 0; +} + + +#if 0 /* Not yet used. */ +/* + * Simple transactions system: + * Everything between begin_transaction and end/cancel_transaction + * is not immediately written but at the time of end_transaction. + * + * NOTE: The transaction code is disabled in the 1.2 branch, as it is + * not yet used. + */ +int +tdbio_begin_transaction () /* Not yet used. */ +{ + 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 () /* Not yet used. */ +{ + int rc; + + if (!in_transaction) + log_bug ("tdbio: no active transaction\n"); + take_write_lock (); + gnupg_block_all_signals (); + in_transaction = 0; + rc = tdbio_sync(); + gnupg_unblock_all_signals(); + release_write_lock (); + return rc; +} + +int +tdbio_cancel_transaction () /* Not yet used. */ +{ + 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 /* Not yet used. */ + + + +/******************************************************** + **************** cached I/O functions ****************** + ********************************************************/ + +/* The cleanup handler for this module. */ +static void +cleanup (void) +{ + if (is_locked) + { + if (!dotlock_release (lockhandle)) + is_locked = 0; + } +} + + +/* + * Update an existing trustdb record. The caller must call + * tdbio_sync. + * + * Returns: 0 on success or an error code. + */ +int +tdbio_update_version_record (ctrl_t ctrl) +{ + TRUSTREC rec; + int rc; + int opt_tm; + + /* Never store a TOFU trust model in the trustdb. Use PGP instead. */ + opt_tm = opt.trust_model; + if (opt_tm == TM_TOFU || opt_tm == TM_TOFU_PGP) + opt_tm = TM_PGP; + + memset (&rec, 0, sizeof rec); + + rc = tdbio_read_record (0, &rec, RECTYPE_VER); + if (!rc) + { + 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_tm; + rec.r.ver.min_cert_level = opt.min_cert_level; + rc = tdbio_write_record (ctrl, &rec); + } + + return rc; +} + + +/* + * Create and write the trustdb version record. + * This is called with the writelock activ. + * Returns: 0 on success or an error code. + */ +static int +create_version_record (ctrl_t ctrl) +{ + TRUSTREC rec; + int rc; + int opt_tm; + + /* Never store a TOFU trust model in the trustdb. Use PGP instead. */ + opt_tm = opt.trust_model; + if (opt_tm == TM_TOFU || opt_tm == TM_TOFU_PGP) + opt_tm = TM_PGP; + + 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_tm == TM_PGP || opt_tm == TM_CLASSIC) + rec.r.ver.trust_model = opt_tm; + else + rec.r.ver.trust_model = TM_PGP; + rec.r.ver.min_cert_level = opt.min_cert_level; + rec.rectype = RECTYPE_VER; + rec.recnum = 0; + rc = tdbio_write_record (ctrl, &rec); + + if (!rc) + tdbio_sync (); + + if (!rc) + create_hashtable (ctrl, &rec, 0); + + return rc; +} + + +/* + * Set the file name for the trustdb to NEW_DBNAME and if CREATE is + * true create that file. If NEW_DBNAME is NULL a default name is + * used, if the it does not contain a path component separator ('/') + * the global GnuPG home directory is used. + * + * Returns: 0 on success or an error code. + * + * On the first call this function registers an atexit handler. + * + */ +int +tdbio_set_dbname (ctrl_t ctrl, const char *new_dbname, + int create, int *r_nofile) +{ + char *fname, *p; + struct stat statbuf; + static int initialized = 0; + int save_slash; + + if (!initialized) + { + atexit (cleanup); + initialized = 1; + } + + *r_nofile = 0; + + if (!new_dbname) + { + fname = make_filename (gnupg_homedir (), + "trustdb" EXTSEP_S GPGEXT_GPG, NULL); + } + else if (*new_dbname != DIRSEP_C ) + { + if (strchr (new_dbname, DIRSEP_C)) + fname = make_filename (new_dbname, NULL); + else + fname = make_filename (gnupg_homedir (), new_dbname, NULL); + } + else + { + fname = xstrdup (new_dbname); + } + + xfree (db_name); + db_name = fname; + + /* Quick check for (likely) case where there already is a + * trustdb.gpg. This check is not required in theory, but it helps + * in practice avoiding costly operations of preparing and taking + * the lock. */ + if (!gnupg_stat (fname, &statbuf) && statbuf.st_size > 0) + { + /* OK, we have the valid trustdb.gpg already. */ + return 0; + } + else if (!create) + { + *r_nofile = 1; + return 0; + } + + /* Here comes: No valid trustdb.gpg AND CREATE==1 */ + + /* + * Make sure the directory exists. This should be done before + * acquiring the lock, which assumes the existence of the directory. + */ + p = strrchr (fname, DIRSEP_C); +#if HAVE_W32_SYSTEM + { + /* Windows may either have a slash or a backslash. Take + care of it. */ + char *pp = strrchr (fname, '/'); + if (!p || pp > p) + p = pp; + } +#endif /*HAVE_W32_SYSTEM*/ + log_assert (p); + save_slash = *p; + *p = 0; + if (gnupg_access (fname, F_OK)) + { + try_make_homedir (fname); + if (gnupg_access (fname, F_OK)) + log_fatal (_("%s: directory does not exist!\n"), fname); + } + *p = save_slash; + + take_write_lock (); + + if (gnupg_access (fname, R_OK) + || gnupg_stat (fname, &statbuf) + || statbuf.st_size == 0) + { + estream_t fp; + TRUSTREC rec; + int rc; + mode_t oldmask; + +#ifdef HAVE_W32CE_SYSTEM + /* We know how the cegcc implementation of access works ;-). */ + if (GetLastError () == ERROR_FILE_NOT_FOUND) + gpg_err_set_errno (ENOENT); + else + gpg_err_set_errno (EIO); +#endif /*HAVE_W32CE_SYSTEM*/ + if (errno && errno != ENOENT) + log_fatal ( _("can't access '%s': %s\n"), fname, strerror (errno)); + + oldmask = umask (077); + if (is_secured_filename (fname)) + { + fp = NULL; + gpg_err_set_errno (EPERM); + } + else + fp = es_fopen (fname, "wb"); + umask(oldmask); + if (!fp) + log_fatal (_("can't create '%s': %s\n"), fname, strerror (errno)); + es_fclose (fp); + + db_fd = gnupg_open (db_name, O_RDWR | MY_O_BINARY, 0); + if (db_fd == -1) + log_fatal (_("can't open '%s': %s\n"), db_name, strerror (errno)); + + rc = create_version_record (ctrl); + if (rc) + log_fatal (_("%s: failed to create version record: %s"), + fname, gpg_strerror (rc)); + + /* 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); + } + + release_write_lock (); + return 0; +} + + +/* + * Return the full name of the trustdb. + */ +const char * +tdbio_get_dbname () +{ + return db_name; +} + + +/* + * Open the trustdb. This may only be called if it has not yet been + * opened and after a successful call to tdbio_set_dbname. On return + * the trustdb handle (DB_FD) is guaranteed to be open. + */ +static void +open_db () +{ + TRUSTREC rec; + + log_assert( db_fd == -1 ); + +#ifdef HAVE_W32CE_SYSTEM + { + DWORD prevrc = 0; + wchar_t *wname = utf8_to_wchar (db_name); + if (wname) + { + db_fd = (int)CreateFile (wname, GENERIC_READ|GENERIC_WRITE, + FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, + OPEN_EXISTING, 0, NULL); + xfree (wname); + } + if (db_fd == -1) + log_fatal ("can't open '%s': %d, %d\n", db_name, + (int)prevrc, (int)GetLastError ()); + } +#else /*!HAVE_W32CE_SYSTEM*/ + db_fd = gnupg_open (db_name, O_RDWR | MY_O_BINARY, 0); + if (db_fd == -1 && (errno == EACCES +#ifdef EROFS + || errno == EROFS +#endif + ) + ) { + /* Take care of read-only trustdbs. */ + db_fd = gnupg_open (db_name, O_RDONLY | MY_O_BINARY, 0); + if (db_fd != -1 && !opt.quiet) + log_info (_("Note: trustdb not writable\n")); + } + if ( db_fd == -1 ) + log_fatal( _("can't open '%s': %s\n"), db_name, strerror(errno) ); +#endif /*!HAVE_W32CE_SYSTEM*/ + register_secured_file (db_name); + + /* Read the version record. */ + if (tdbio_read_record (0, &rec, RECTYPE_VER ) ) + log_fatal( _("%s: invalid trustdb\n"), db_name ); +} + + +/* + * Append a new empty hashtable to the trustdb. TYPE gives the type + * of the hash table. The only defined type is 0 for a trust hash. + * On return the hashtable has been created, written, the version + * record update, and the data flushed to the disk. On a fatal error + * the function terminates the process. + */ +static void +create_hashtable (ctrl_t ctrl, 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; + log_assert (recnum); /* This is will never be the first record. */ + + if (!type) + vr->r.ver.trusthashtbl = recnum; + + /* Now write the records making up the hash table. */ + 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 (ctrl, &rec); + if (rc) + log_fatal (_("%s: failed to create hashtable: %s\n"), + db_name, gpg_strerror (rc)); + } + /* Update the version record and flush. */ + rc = tdbio_write_record (ctrl, vr); + if (!rc) + rc = tdbio_sync (); + if (rc) + log_fatal (_("%s: error updating version record: %s\n"), + db_name, gpg_strerror (rc)); +} + + +/* + * Check whether open trustdb matches the global trust options given + * for this process. On a read problem the process is terminated. + * + * Return: 1 for yes, 0 for no. + */ +int +tdbio_db_matches_options() +{ + static int yes_no = -1; + + if (yes_no == -1) + { + TRUSTREC vr; + int rc; + int opt_tm, tm; + + rc = tdbio_read_record (0, &vr, RECTYPE_VER); + if( rc ) + log_fatal( _("%s: error reading version record: %s\n"), + db_name, gpg_strerror (rc) ); + + /* Consider tofu and pgp the same. */ + tm = vr.r.ver.trust_model; + if (tm == TM_TOFU || tm == TM_TOFU_PGP) + tm = TM_PGP; + opt_tm = opt.trust_model; + if (opt_tm == TM_TOFU || opt_tm == TM_TOFU_PGP) + opt_tm = TM_PGP; + + 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 + && tm == opt_tm + && vr.r.ver.min_cert_level == opt.min_cert_level; + } + + return yes_no; +} + + +/* + * Read and return the trust model identifier from the trustdb. On a + * read problem the process is terminated. + */ +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, gpg_strerror (rc) ); + return vr.r.ver.trust_model; +} + + +/* + * Read and return the nextstamp value from the trustdb. On a read + * problem the process is terminated. + */ +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, gpg_strerror (rc)); + return vr.r.ver.nextcheck; +} + + +/* + * Write the STAMP nextstamp timestamp to the trustdb. On a read or + * write problem the process is terminated. + * + * Return: True if the stamp actually changed. + */ +int +tdbio_write_nextcheck (ctrl_t ctrl, 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, gpg_strerror (rc)); + + if (vr.r.ver.nextcheck == stamp) + return 0; + + vr.r.ver.nextcheck = stamp; + rc = tdbio_write_record (ctrl, &vr); + if (rc) + log_fatal (_("%s: error writing version record: %s\n"), + db_name, gpg_strerror (rc)); + return 1; +} + + + +/* + * Return the record number of the trusthash table or create one if it + * does not yet exist. On a read or write problem the process is + * terminated. + * + * Return: record number + */ +static ulong +get_trusthashrec (ctrl_t ctrl) +{ + static ulong trusthashtbl; /* Record number of the trust hashtable. */ + + (void)ctrl; + + 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, gpg_strerror (rc) ); + + if (!vr.r.ver.trusthashtbl) + { + /* Oops: the trustdb is corrupt because the hashtable is + * always created along with the version record. However, + * if something went initially wrong it may happen that + * there is just the version record. We try to fix it here. + * If we can't do that we return 0 - this is the version + * record and thus the actual read will detect the mismatch + * and bail out. Note that create_hashtable updates VR. */ + take_write_lock (); + if (lseek (db_fd, 0, SEEK_END) == TRUST_RECORD_LEN) + create_hashtable (ctrl, &vr, 0); + release_write_lock (); + } + trusthashtbl = vr.r.ver.trusthashtbl; + } + + return trusthashtbl; +} + + + +/* + * Update a hashtable in the trustdb. TABLE gives the start of the + * table, KEY and KEYLEN are the key, NEWRECNUM is the record number + * to insert into the table. + * + * Return: 0 on success or an error code. + */ +static int +upd_hashtable (ctrl_t ctrl, 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", gpg_strerror (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 (ctrl, &rec); + if (rc) + { + log_error ("upd_hashtable: write htbl failed: %s\n", + gpg_strerror (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", + gpg_strerror (rc)); + return rc; + } + + if (rec.rectype == RECTYPE_HTBL) + { + hashrec = item; + level++; + if (level >= keylen) + { + log_error ("hashtable has invalid indirections.\n"); + return GPG_ERR_TRUSTDB; + } + goto next_level; + } + else if (rec.rectype == RECTYPE_HLST) /* Extend the list. */ + { + /* Check 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", + gpg_strerror (rc) ); + return rc; + } + } + else + break; /* key is not in the list */ + } + + /* 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]) + { + /* Empty slot found. */ + rec.r.hlst.rnum[i] = newrecnum; + rc = tdbio_write_record (ctrl, &rec); + if (rc) + log_error ("upd_hashtable: write hlst failed: %s\n", + gpg_strerror (rc)); + return rc; /* Done. */ + } + } + + if (rec.r.hlst.next) + { + /* read the next reord of the list. */ + rc = tdbio_read_record (rec.r.hlst.next, &rec, RECTYPE_HLST); + if (rc) + { + log_error ("upd_hashtable: read hlst failed: %s\n", + gpg_strerror (rc)); + return rc; + } + } + else + { + /* Append a new record to the list. */ + rec.r.hlst.next = item = tdbio_new_recnum (ctrl); + rc = tdbio_write_record (ctrl, &rec); + if (rc) + { + log_error ("upd_hashtable: write hlst failed: %s\n", + gpg_strerror (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 (ctrl, &rec); + if (rc) + log_error ("upd_hashtable: write ext hlst failed: %s\n", + gpg_strerror (rc)); + return rc; /* Done. */ + } + } /* end loop over list 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 (ctrl); + rec.r.hlst.rnum[0] = item; /* Old key record */ + rec.r.hlst.rnum[1] = newrecnum; /* and new key record */ + rc = tdbio_write_record (ctrl, &rec); + if (rc) + { + log_error( "upd_hashtable: write new hlst failed: %s\n", + gpg_strerror (rc) ); + return rc; + } + /* Update the hashtable record. */ + lastrec.r.htbl.item[msb % ITEMS_PER_HTBL_RECORD] = rec.recnum; + rc = tdbio_write_record (ctrl, &lastrec); + if (rc) + log_error ("upd_hashtable: update htbl failed: %s\n", + gpg_strerror (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); + if (opt.verbose > 1) + list_trustdb (ctrl, es_stderr, NULL); + return GPG_ERR_TRUSTDB; + } + } + + return 0; +} + + +/* + * Drop an entry from a hashtable. TABLE gives the start of the + * table, KEY and KEYLEN are the key. + * + * Return: 0 on success or an error code. + */ +static int +drop_from_hashtable (ctrl_t ctrl, 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", gpg_strerror (rc)); + return rc; + } + + item = rec.r.htbl.item[msb % ITEMS_PER_HTBL_RECORD]; + if (!item) + return 0; /* Not found - forget about it. */ + + if (item == recnum) /* Table points direct to the record. */ + { + rec.r.htbl.item[msb % ITEMS_PER_HTBL_RECORD] = 0; + rc = tdbio_write_record (ctrl, &rec); + if (rc) + log_error ("drop_from_hashtable: write htbl failed: %s\n", + gpg_strerror (rc)); + return rc; + } + + rc = tdbio_read_record (item, &rec, 0); + if (rc) + { + log_error ("drop_from_hashtable: read item failed: %s\n", + gpg_strerror (rc)); + return rc; + } + + if (rec.rectype == RECTYPE_HTBL) + { + hashrec = item; + level++; + if (level >= keylen) + { + log_error ("hashtable has invalid indirections.\n"); + return GPG_ERR_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; /* Mark as free. */ + rc = tdbio_write_record (ctrl, &rec); + if (rc) + log_error("drop_from_hashtable: write htbl failed: %s\n", + gpg_strerror (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", + gpg_strerror (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 GPG_ERR_TRUSTDB; +} + + + +/* + * Lookup a record via the hashtable TABLE by (KEY,KEYLEN) and return + * the result in REC. The return value of CMP() should be True if the + * record is the desired one. + * + * Return: 0 if found, GPG_ERR_NOT_FOUND, or another error code. + */ +static gpg_error_t +lookup_hashtable (ulong table, const byte *key, size_t keylen, + int (*cmpfnc)(const void*, const TRUSTREC *), + const void *cmpdata, TRUSTREC *rec ) +{ + int rc; + ulong hashrec, item; + int msb; + int level = 0; + + if (!table) + { + rc = gpg_error (GPG_ERR_INV_RECORD); + log_error("lookup_hashtable failed: %s\n", "request for record 0"); + return rc; + } + + 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", gpg_strerror (rc) ); + return rc; + } + + item = rec->r.htbl.item[msb % ITEMS_PER_HTBL_RECORD]; + if (!item) + return gpg_error (GPG_ERR_NOT_FOUND); + + rc = tdbio_read_record (item, rec, 0); + if (rc) + { + log_error( "hashtable read failed: %s\n", gpg_strerror (rc) ); + return rc; + } + if (rec->rectype == RECTYPE_HTBL) + { + hashrec = item; + level++; + if (level >= keylen) + { + log_error ("hashtable has invalid indirections\n"); + return GPG_ERR_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", + gpg_strerror (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", + gpg_strerror (rc) ); + return rc; + } + } + else + return gpg_error (GPG_ERR_NOT_FOUND); + } + } + + if ((*cmpfnc)(cmpdata, rec)) + return 0; /* really found */ + + return gpg_error (GPG_ERR_NOT_FOUND); /* no: not found */ +} + + +/* + * Update the trust hash table TR or create the table if it does not + * exist. + * + * Return: 0 on success or an error code. + */ +static int +update_trusthashtbl (ctrl_t ctrl, TRUSTREC *tr) +{ + return upd_hashtable (ctrl, get_trusthashrec (ctrl), + tr->r.trust.fingerprint, 20, tr->recnum); +} + + +/* + * Dump the trustdb record REC to stream FP. + */ +void +tdbio_dump_record (TRUSTREC *rec, estream_t fp) +{ + int i; + ulong rnum = rec->recnum; + + es_fprintf (fp, "rec %5lu, ", rnum); + + switch (rec->rectype) + { + case 0: + es_fprintf (fp, "blank\n"); + break; + + case RECTYPE_VER: + es_fprintf (fp, + "version, td=%lu, f=%lu, m/c/d=%d/%d/%d tm=%d mcl=%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.min_cert_level, + rec->r.ver.nextcheck, + strtimestamp(rec->r.ver.nextcheck) + ); + break; + + case RECTYPE_FREE: + es_fprintf (fp, "free, next=%lu\n", rec->r.free.next); + break; + + case RECTYPE_HTBL: + es_fprintf (fp, "htbl,"); + for (i=0; i < ITEMS_PER_HTBL_RECORD; i++) + es_fprintf (fp, " %lu", rec->r.htbl.item[i]); + es_putc ('\n', fp); + break; + + case RECTYPE_HLST: + es_fprintf (fp, "hlst, next=%lu,", rec->r.hlst.next); + for (i=0; i < ITEMS_PER_HLST_RECORD; i++) + es_fprintf (fp, " %lu", rec->r.hlst.rnum[i]); + es_putc ('\n', fp); + break; + + case RECTYPE_TRUST: + es_fprintf (fp, "trust "); + for (i=0; i < 20; i++) + es_fprintf (fp, "%02X", rec->r.trust.fingerprint[i]); + es_fprintf (fp, ", ot=%d, d=%d, vl=%lu, mo=%d, f=%02x\n", + rec->r.trust.ownertrust, + rec->r.trust.depth, rec->r.trust.validlist, + rec->r.trust.min_ownertrust, rec->r.trust.flags); + break; + + case RECTYPE_VALID: + es_fprintf (fp, "valid "); + for (i=0; i < 20; i++) + es_fprintf(fp, "%02X", rec->r.valid.namehash[i]); + es_fprintf (fp, ", v=%d, next=%lu, f=%d, m=%d\n", + rec->r.valid.validity, rec->r.valid.next, + rec->r.valid.full_count, rec->r.valid.marginal_count); + break; + + default: + es_fprintf (fp, "unknown type %d\n", rec->rectype ); + break; + } +} + + +/* + * Read the record with number RECNUM into the structure REC. If + * EXPECTED is not 0 reading any other record type will return an + * error. + * + * Return: 0 on success or an error code. + */ +int +tdbio_read_record (ulong recnum, TRUSTREC *rec, int expected) +{ + byte readbuf[TRUST_RECORD_LEN]; + const byte *buf, *p; + gpg_error_t err = 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) + { + err = gpg_error_from_syserror (); + log_error (_("trustdb: lseek failed: %s\n"), strerror (errno)); + return err; + } + n = read (db_fd, readbuf, TRUST_RECORD_LEN); + if (!n) + { + return gpg_error (GPG_ERR_EOF); + } + else if (n != TRUST_RECORD_LEN) + { + err = gpg_error_from_syserror (); + log_error (_("trustdb: read failed (n=%d): %s\n"), + n, strerror(errno)); + return err; + } + 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 gpg_error (GPG_ERR_TRUSTDB); + } + p++; /* Skip reserved byte. */ + switch (rec->rectype) + { + case 0: /* unused (free) record */ + break; + + case RECTYPE_VER: /* version record */ + if (memcmp(buf+1, GPGEXT_GPG, 3)) + { + log_error (_("%s: not a trustdb file\n"), db_name ); + err = gpg_error (GPG_ERR_TRUSTDB); + } + else + { + 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++; + rec->r.ver.min_cert_level = *p++; + p += 2; + rec->r.ver.created = buf32_to_ulong(p); + p += 4; + rec->r.ver.nextcheck = buf32_to_ulong(p); + p += 4; + p += 4; + p += 4; + rec->r.ver.firstfree = buf32_to_ulong(p); + p += 4; + p += 4; + rec->r.ver.trusthashtbl = buf32_to_ulong(p); + if (recnum) + { + log_error( _("%s: version record with recnum %lu\n"), db_name, + (ulong)recnum ); + err = gpg_error (GPG_ERR_TRUSTDB); + } + else if (rec->r.ver.version != 3) + { + log_error( _("%s: invalid file version %d\n"), db_name, + rec->r.ver.version ); + err = gpg_error (GPG_ERR_TRUSTDB); + } + } + break; + + case RECTYPE_FREE: + rec->r.free.next = buf32_to_ulong(p); + break; + + case RECTYPE_HTBL: + for (i=0; i < ITEMS_PER_HTBL_RECORD; i++) + { + rec->r.htbl.item[i] = buf32_to_ulong(p); + p += 4; + } + break; + + case RECTYPE_HLST: + rec->r.hlst.next = buf32_to_ulong(p); + p += 4; + for (i=0; i < ITEMS_PER_HLST_RECORD; i++) + { + rec->r.hlst.rnum[i] = buf32_to_ulong(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++; + rec->r.trust.flags = *p++; + rec->r.trust.validlist = buf32_to_ulong(p); + break; + + case RECTYPE_VALID: + memcpy (rec->r.valid.namehash, p, 20); + p+=20; + rec->r.valid.validity = *p++; + rec->r.valid.next = buf32_to_ulong(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); + err = gpg_error (GPG_ERR_TRUSTDB); + break; + } + + return err; +} + + +/* + * Write the record from the struct REC. + * + * Return: 0 on success or an error code. + */ +int +tdbio_write_record (ctrl_t ctrl, TRUSTREC *rec) +{ + byte buf[TRUST_RECORD_LEN]; + byte *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, GPGEXT_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++ = rec->r.ver.min_cert_level; + p += 2; + 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++ = rec->r.trust.flags; + 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 (ctrl, rec); + + return rc; +} + + +/* + * Delete the record at record number RECNUm from the trustdb. + * + * Return: 0 on success or an error code. + */ +int +tdbio_delete_record (ctrl_t ctrl, 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 (ctrl, get_trusthashrec (ctrl), + rec.r.trust.fingerprint, 20, rec.recnum); + } + + if (rc) + return rc; + + /* Now we can change 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, gpg_strerror (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 (ctrl, &rec); + if (!rc) + rc = tdbio_write_record (ctrl, &vr); + + return rc; +} + + +/* + * Create a new record and return its record number. + */ +ulong +tdbio_new_recnum (ctrl_t ctrl) +{ + 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, gpg_strerror (rc)); + if (vr.r.ver.firstfree) + { + recnum = vr.r.ver.firstfree; + rc = tdbio_read_record (recnum, &rec, RECTYPE_FREE); + if (rc) + log_fatal (_("%s: error reading free record: %s\n"), + db_name, gpg_strerror (rc)); + /* Update dir record. */ + vr.r.ver.firstfree = rec.r.free.next; + rc = tdbio_write_record (ctrl, &vr); + if (rc) + log_fatal (_("%s: error writing dir record: %s\n"), + db_name, gpg_strerror (rc)); + /* Zero out the new record. */ + memset (&rec, 0, sizeof rec); + rec.rectype = 0; /* Mark as unused record (actually already done + my the memset). */ + rec.recnum = recnum; + rc = tdbio_write_record (ctrl, &rec); + if (rc) + log_fatal (_("%s: failed to zero a record: %s\n"), + db_name, gpg_strerror (rc)); + } + else /* Not found - append a new record. */ + { + offset = lseek (db_fd, 0, SEEK_END); + if (offset == (off_t)(-1)) + log_fatal ("trustdb: lseek to end failed: %s\n", strerror (errno)); + recnum = offset / TRUST_RECORD_LEN; + log_assert (recnum); /* This 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) + { + rc = gpg_error_from_syserror (); + log_error (_("trustdb rec %lu: lseek failed: %s\n"), + recnum, strerror (errno)); + } + else + { + int n; + + n = write (db_fd, &rec, TRUST_RECORD_LEN); + if (n != TRUST_RECORD_LEN) + { + rc = gpg_error_from_syserror (); + log_error (_("trustdb rec %lu: write failed (n=%d): %s\n"), + recnum, n, gpg_strerror (rc)); + } + } + + if (rc) + log_fatal (_("%s: failed to append a record: %s\n"), + db_name, gpg_strerror (rc)); + } + + return recnum ; +} + + + +/* Helper function for tdbio_search_trust_byfpr. */ +static int +cmp_trec_fpr ( const void *fpr, const TRUSTREC *rec ) +{ + return (rec->rectype == RECTYPE_TRUST + && !memcmp (rec->r.trust.fingerprint, fpr, 20)); +} + + +/* + * Given a 20 byte FINGERPRINT search its trust record and return + * that at REC. + * + * Return: 0 if found, GPG_ERR_NOT_FOUND, or another error code. + */ +gpg_error_t +tdbio_search_trust_byfpr (ctrl_t ctrl, const byte *fingerprint, TRUSTREC *rec) +{ + int rc; + + /* Locate the trust record using the hash table */ + rc = lookup_hashtable (get_trusthashrec (ctrl), fingerprint, 20, + cmp_trec_fpr, fingerprint, rec ); + return rc; +} + + +/* + * Given a primary public key object PK search its trust record and + * return that at REC. + * + * Return: 0 if found, GPG_ERR_NOT_FOUND, or another error code. + */ +gpg_error_t +tdbio_search_trust_bypk (ctrl_t ctrl, 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 (ctrl, fingerprint, rec); +} + + +/* + * Terminate the process with a message about a corrupted trustdb. + */ +void +tdbio_invalid (void) +{ + log_error (_("Error: The trustdb is corrupted.\n")); + how_to_fix_the_trustdb (); + g10_exit (2); +} diff --git a/g10/tdbio.h b/g10/tdbio.h new file mode 100644 index 0000000..9452d76 --- /dev/null +++ b/g10/tdbio.h @@ -0,0 +1,122 @@ +/* tdbio.h - Trust database I/O functions + * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2012 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#ifndef G10_TDBIO_H +#define G10_TDBIO_H + +#include "../common/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; + byte min_cert_level; + 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; + byte flags; + } 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 (ctrl_t ctrl); +int tdbio_set_dbname (ctrl_t ctrl, const char *new_dbname, + int create, int *r_nofile); +const char *tdbio_get_dbname(void); +void tdbio_dump_record( TRUSTREC *rec, estream_t fp ); +int tdbio_read_record( ulong recnum, TRUSTREC *rec, int expected ); +int tdbio_write_record (ctrl_t ctrl, TRUSTREC *rec); +int tdbio_db_matches_options(void); +byte tdbio_read_model(void); +ulong tdbio_read_nextcheck (void); +int tdbio_write_nextcheck (ctrl_t ctrl, 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 (ctrl_t ctrl, ulong recnum); +ulong tdbio_new_recnum (ctrl_t ctrl); +gpg_error_t tdbio_search_trust_byfpr (ctrl_t ctrl, const byte *fingerprint, + TRUSTREC *rec); +gpg_error_t tdbio_search_trust_bypk (ctrl_t ctrl, PKT_public_key *pk, + TRUSTREC *rec); + +void tdbio_how_to_fix (void); +void tdbio_invalid(void); + +#endif /*G10_TDBIO_H*/ diff --git a/g10/test-stubs.c b/g10/test-stubs.c new file mode 100644 index 0000000..2ae4a41 --- /dev/null +++ b/g10/test-stubs.c @@ -0,0 +1,591 @@ +/* test-stubs.c - The GnuPG signature verify utility + * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2005, 2006, + * 2008, 2009, 2012 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include +#include +#include +#include +#include +#include +#include + +#define INCLUDED_BY_MAIN_MODULE 1 +#include "gpg.h" +#include "../common/util.h" +#include "packet.h" +#include "../common/iobuf.h" +#include "main.h" +#include "options.h" +#include "keydb.h" +#include "trustdb.h" +#include "filter.h" +#include "../common/ttyio.h" +#include "../common/i18n.h" +#include "../common/sysutils.h" +#include "../common/status.h" +#include "call-agent.h" + +int g10_errors_seen; + + +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 because + * this utility assumes that all keys in the keyring are trustworthy + */ +int +check_signatures_trust (ctrl_t ctrl, PKT_signature *sig) +{ + (void)ctrl; + (void)sig; + return 0; +} + +void +read_trust_options (ctrl_t ctrl, + byte *trust_model, ulong *created, ulong *nextcheck, + byte *marginals, byte *completes, byte *cert_depth, + byte *min_cert_level) +{ + (void)ctrl; + (void)trust_model; + (void)created; + (void)nextcheck; + (void)marginals; + (void)completes; + (void)cert_depth; + (void)min_cert_level; +} + +/* Stub: + * We don't have the trustdb , so we have to provide some stub functions + * instead + */ + +int +cache_disabled_value (ctrl_t ctrl, PKT_public_key *pk) +{ + (void)ctrl; + (void)pk; + return 0; +} + +void +check_trustdb_stale (ctrl_t ctrl) +{ + (void)ctrl; +} + +int +get_validity_info (ctrl_t ctrl, kbnode_t kb, PKT_public_key *pk, + PKT_user_id *uid) +{ + (void)ctrl; + (void)kb; + (void)pk; + (void)uid; + return '?'; +} + +unsigned int +get_validity (ctrl_t ctrl, kbnode_t kb, PKT_public_key *pk, PKT_user_id *uid, + PKT_signature *sig, int may_ask) +{ + (void)ctrl; + (void)kb; + (void)pk; + (void)uid; + (void)sig; + (void)may_ask; + return 0; +} + +const char * +trust_value_to_string (unsigned int value) +{ + (void)value; + return "err"; +} + +const char * +uid_trust_string_fixed (ctrl_t ctrl, PKT_public_key *key, PKT_user_id *uid) +{ + (void)ctrl; + (void)key; + (void)uid; + return "err"; +} + +int +get_ownertrust_info (ctrl_t ctrl, PKT_public_key *pk, int no_create) +{ + (void)ctrl; + (void)pk; + (void)no_create; + return '?'; +} + +unsigned int +get_ownertrust (ctrl_t ctrl, PKT_public_key *pk) +{ + (void)ctrl; + (void)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) +{ + (void)spec; + return NULL; +} + +int +keyserver_any_configured (ctrl_t ctrl) +{ + (void)ctrl; + return 0; +} + +int +keyserver_import_keyid (u32 *keyid, void *dummy, unsigned int flags) +{ + (void)keyid; + (void)dummy; + (void)flags; + return -1; +} + +int +keyserver_import_fprint (ctrl_t ctrl, const byte *fprint,size_t fprint_len, + struct keyserver_spec *keyserver, unsigned int flags) +{ + (void)ctrl; + (void)fprint; + (void)fprint_len; + (void)keyserver; + (void)flags; + return -1; +} + +int +keyserver_import_fprint_ntds (ctrl_t ctrl, + const byte *fprint, size_t fprint_len) +{ + (void)ctrl; + (void)fprint; + (void)fprint_len; + return -1; +} + +int +keyserver_import_cert (const char *name) +{ + (void)name; + return -1; +} + +int +keyserver_import_pka (const char *name,unsigned char *fpr) +{ + (void)name; + (void)fpr; + return -1; +} + +gpg_error_t +keyserver_import_wkd (ctrl_t ctrl, const char *name, unsigned int flags, + unsigned char **fpr, size_t *fpr_len) +{ + (void)ctrl; + (void)name; + (void)flags; + (void)fpr; + (void)fpr_len; + return GPG_ERR_BUG; +} + +int +keyserver_import_mbox (const char *name,struct keyserver_spec *spec) +{ + (void)name; + (void)spec; + return -1; +} + +int +keyserver_import_ntds (ctrl_t ctrl, const char *mbox, + unsigned char **fpr, size_t *fprlen) +{ + (void)ctrl; + (void)mbox; + (void)fpr; + (void)fprlen; + return -1; +} + +int +keyserver_import_ldap (const char *name) +{ + (void)name; + return -1; +} + +gpg_error_t +read_key_from_file_or_buffer (ctrl_t ctrl, const char *fname, + const void *buffer, size_t buflen, + kbnode_t *r_keyblock) +{ + (void)ctrl; + (void)fname; + (void)buffer; + (void)buflen; + (void)r_keyblock; + return -1; +} + +gpg_error_t +import_included_key_block (ctrl_t ctrl, kbnode_t keyblock) +{ + (void)ctrl; + (void)keyblock; + return -1; +} + + +/* Stub: + * No encryption here but mainproc links to these functions. + */ +gpg_error_t +get_session_key (ctrl_t ctrl, PKT_pubkey_enc *k, DEK *dek) +{ + (void)ctrl; + (void)k; + (void)dek; + return GPG_ERR_GENERAL; +} + +/* Stub: */ +gpg_error_t +get_override_session_key (DEK *dek, const char *string) +{ + (void)dek; + (void)string; + return GPG_ERR_GENERAL; +} + +/* Stub: */ +int +decrypt_data (ctrl_t ctrl, void *procctx, PKT_encrypted *ed, DEK *dek, + int *compliance_error) +{ + (void)ctrl; + (void)procctx; + (void)ed; + (void)dek; + (void)compliance_error; + return GPG_ERR_GENERAL; +} + + +/* Stub: + * No interactive commands, so we don't need the helptexts + */ +void +display_online_help (const char *keyword) +{ + (void)keyword; +} + +/* Stub: + * We don't use secret keys, but getkey.c links to this + */ +int +check_secret_key (PKT_public_key *pk, int n) +{ + (void)pk; + (void)n; + return GPG_ERR_GENERAL; +} + +/* Stub: + * No secret key, so no passphrase needed + */ +DEK * +passphrase_to_dek (int cipher_algo, STRING2KEY *s2k, int create, int nocache, + const char *tmp, unsigned int flags, int *canceled) +{ + (void)cipher_algo; + (void)s2k; + (void)create; + (void)nocache; + (void)tmp; + (void)flags; + + if (canceled) + *canceled = 0; + return NULL; +} + +void +passphrase_clear_cache (const char *cacheid) +{ + (void)cacheid; +} + +struct keyserver_spec * +parse_preferred_keyserver(PKT_signature *sig) +{ + (void)sig; + return NULL; +} + +struct keyserver_spec * +parse_keyserver_uri (const char *uri, int require_scheme, + const char *configname, unsigned int configlineno) +{ + (void)uri; + (void)require_scheme; + (void)configname; + (void)configlineno; + return NULL; +} + +void +free_keyserver_spec (struct keyserver_spec *keyserver) +{ + (void)keyserver; +} + +/* Stubs to avoid linking to photoid.c */ +void +show_photos (const struct user_attribute *attrs, int count, PKT_public_key *pk) +{ + (void)attrs; + (void)count; + (void)pk; +} + +int +parse_image_header (const struct user_attribute *attr, byte *type, u32 *len) +{ + (void)attr; + (void)type; + (void)len; + return 0; +} + +char * +image_type_to_string (byte type, int string) +{ + (void)type; + (void)string; + return NULL; +} + +#ifdef ENABLE_CARD_SUPPORT +int +agent_scd_getattr (const char *name, struct agent_card_info_s *info) +{ + (void)name; + (void)info; + return 0; +} +#endif /* ENABLE_CARD_SUPPORT */ + +/* We do not do any locking, so use these stubs here */ +void +dotlock_disable (void) +{ +} + +dotlock_t +dotlock_create (const char *file_to_lock, unsigned int flags) +{ + (void)file_to_lock; + (void)flags; + return NULL; +} + +void +dotlock_destroy (dotlock_t h) +{ + (void)h; +} + +int +dotlock_take (dotlock_t h, long timeout) +{ + (void)h; + (void)timeout; + return 0; +} + +int +dotlock_release (dotlock_t h) +{ + (void)h; + return 0; +} + +void +dotlock_remove_lockfiles (void) +{ +} + +gpg_error_t +agent_probe_secret_key (ctrl_t ctrl, PKT_public_key *pk) +{ + (void)ctrl; + (void)pk; + return gpg_error (GPG_ERR_NO_SECKEY); +} + +gpg_error_t +agent_probe_any_secret_key (ctrl_t ctrl, kbnode_t keyblock) +{ + (void)ctrl; + (void)keyblock; + return gpg_error (GPG_ERR_NO_SECKEY); +} + +gpg_error_t +agent_get_keyinfo (ctrl_t ctrl, const char *hexkeygrip, + char **r_serialno, int *r_cleartext) +{ + (void)ctrl; + (void)hexkeygrip; + (void)r_cleartext; + *r_serialno = NULL; + return gpg_error (GPG_ERR_NO_SECKEY); +} + +gpg_error_t +gpg_dirmngr_get_pka (ctrl_t ctrl, const char *userid, + unsigned char **r_fpr, size_t *r_fprlen, + char **r_url) +{ + (void)ctrl; + (void)userid; + if (r_fpr) + *r_fpr = NULL; + if (r_fprlen) + *r_fprlen = 0; + if (r_url) + *r_url = NULL; + return gpg_error (GPG_ERR_NOT_FOUND); +} + +gpg_error_t +export_pubkey_buffer (ctrl_t ctrl, const char *keyspec, unsigned int options, + const void *prefix, size_t prefixlen, + export_stats_t stats, + kbnode_t *r_keyblock, void **r_data, size_t *r_datalen) +{ + (void)ctrl; + (void)keyspec; + (void)options; + (void)prefix; + (void)prefixlen; + (void)stats; + + *r_keyblock = NULL; + *r_data = NULL; + *r_datalen = 0; + return gpg_error (GPG_ERR_NOT_IMPLEMENTED); +} + +gpg_error_t +tofu_write_tfs_record (ctrl_t ctrl, estream_t fp, + PKT_public_key *pk, const char *user_id) +{ + (void)ctrl; + (void)fp; + (void)pk; + (void)user_id; + return gpg_error (GPG_ERR_GENERAL); +} + +gpg_error_t +tofu_get_policy (ctrl_t ctrl, PKT_public_key *pk, PKT_user_id *user_id, + enum tofu_policy *policy) +{ + (void)ctrl; + (void)pk; + (void)user_id; + (void)policy; + return gpg_error (GPG_ERR_GENERAL); +} + +const char * +tofu_policy_str (enum tofu_policy policy) +{ + (void)policy; + + return "unknown"; +} + +void +tofu_begin_batch_update (ctrl_t ctrl) +{ + (void)ctrl; +} + +void +tofu_end_batch_update (ctrl_t ctrl) +{ + (void)ctrl; +} + +gpg_error_t +tofu_notice_key_changed (ctrl_t ctrl, kbnode_t kb) +{ + (void) ctrl; + (void) kb; + + return 0; +} + +int +get_revocation_reason (PKT_signature *sig, char **r_reason, + char **r_comment, size_t *r_commentlen) +{ + (void)sig; + (void)r_commentlen; + + if (r_reason) + *r_reason = NULL; + if (r_comment) + *r_comment = NULL; + return 0; +} diff --git a/g10/test.c b/g10/test.c new file mode 100644 index 0000000..648148a --- /dev/null +++ b/g10/test.c @@ -0,0 +1,192 @@ +/* test.c - Infrastructure for unit tests. + * Copyright (C) 2015 g10 Code GmbH + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include +#include +#include +#include + +#define INCLUDED_BY_MAIN_MODULE 1 +#include "gpg.h" + +/* A unit test consists of one or more tests. Tests can be broken + into groups and each group can consist of one or more tests. */ + +/* The number of test groups. */ +static int test_groups; +/* The current test group. */ +static char *test_group; + +/* Whether there was already a failure in the current test group. */ +static int current_test_group_failed; +/* The number of test groups with a failure. */ +static int test_groups_failed; + +/* The total number of tests. */ +static int tests; +/* The total number of tests that failed. */ +static int tests_failed; + +/* Flag to request verbose diagnostics. This is set if the envvar + "verbose" exists and is not the empty string. */ +static int verbose; + +#define TEST_GROUP(description) \ + do { \ + test_group = (description); \ + test_groups ++; \ + current_test_group_failed = 0; \ + } while (0) + +#define STRINGIFY2(x) #x +#define STRINGIFY(x) STRINGIFY2(x) + +/* Execute a test. */ +#define TEST(description, test, expected) \ + do { \ + int test_result; \ + int expected_result; \ + \ + tests ++; \ + if (verbose) \ + { \ + printf ("%d. Checking %s...", \ + tests, (description) ?: ""); \ + fflush (stdout); \ + } \ + test_result = (test); \ + expected_result = (expected); \ + \ + if (test_result == expected_result) \ + { \ + if (verbose) printf (" ok.\n"); \ + } \ + else \ + { \ + if (!verbose) \ + printf ("%d. Checking %s...", \ + tests, (description) ?: ""); \ + printf (" failed.\n"); \ + printf (" %s == %s failed.\n", \ + STRINGIFY(test), \ + STRINGIFY(expected)); \ + tests_failed ++; \ + if (! current_test_group_failed) \ + { \ + current_test_group_failed = 1; \ + test_groups_failed ++; \ + } \ + } \ + } while (0) + +/* Test that a condition evaluates to true. */ +#define TEST_P(description, test) \ + TEST(description, !!(test), 1) + +/* Like CHECK, but if the test fails, abort the program. */ +#define ASSERT(description, test, expected) \ + do { \ + int tests_failed_pre = tests_failed; \ + CHECK(description, test, expected); \ + if (tests_failed_pre != tests_failed) \ + exit_tests (1); \ + } while (0) + +/* Call this if something went wrong. */ +#define ABORT(message) \ + do { \ + printf ("aborting..."); \ + if (message) \ + printf (" %s\n", (message)); \ + \ + exit_tests (1); \ + } while (0) + +/* You need to fill this function in. */ +static void do_test (int argc, char *argv[]); + + +/* Print stats and call the real exit. If FORCE is set use + EXIT_FAILURE even if no test has failed. */ +static void +exit_tests (int force) +{ + if (tests_failed == 0) + { + if (verbose) + printf ("All %d tests passed.\n", tests); + exit (!!force); + } + else + { + printf ("%d of %d tests failed", + tests_failed, tests); + if (test_groups > 1) + printf (" (%d of %d groups)", + test_groups_failed, test_groups); + printf ("\n"); + exit (1); + } +} + + +/* Prepend FNAME with the srcdir environment variable's value and + return a malloced filename. Caller must release the returned + string using test_free. */ +char * +prepend_srcdir (const char *fname) +{ + static const char *srcdir; + char *result; + + if (!srcdir && !(srcdir = getenv ("abs_top_srcdir"))) + srcdir = "."; + + result = malloc (strlen (srcdir) + strlen ("/g10/") + strlen (fname) + 1); + strcpy (result, srcdir); + strcat (result, "/g10/"); + strcat (result, fname); + return result; +} + + +void +test_free (void *a) +{ + if (a) + free (a); +} + + +int +main (int argc, char *argv[]) +{ + const char *s; + + (void) test_group; + + s = getenv ("verbose"); + if (s && *s) + verbose = 1; + + do_test (argc, argv); + exit_tests (0); + + return !!tests_failed; +} diff --git a/g10/textfilter.c b/g10/textfilter.c new file mode 100644 index 0000000..3e68900 --- /dev/null +++ b/g10/textfilter.c @@ -0,0 +1,245 @@ +/* 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 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include +#include +#include +#include +#include + +#include "gpg.h" +#include "../common/status.h" +#include "../common/iobuf.h" +#include "../common/util.h" +#include "filter.h" +#include "../common/i18n.h" +#include "options.h" +#include "../common/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; + + log_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 ) + mem2str (buf, "text_filter", *ret_len); + 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, gcry_md_hd_t md, + int escape_dash, int escape_from) +{ + unsigned int maxlen; + byte *buffer = NULL; /* malloced buffer */ + unsigned int bufsize = 0; /* and size of this buffer */ + unsigned int n; + int truncated = 0; + int pending_lf = 0; + + if( !escape_dash ) + escape_from = 0; + + write_status_begin_signing (md); + + 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 ) { + gcry_md_putc ( md, '\r' ); + gcry_md_putc ( md, '\n' ); + } + gcry_md_write ( md, buffer, + len_without_trailing_chars (buffer, n, " \t\r\n")); + } + else + gcry_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 ) + gcry_md_putc( md, '\n' ); + } + + if( truncated ) + log_info(_("input line longer than %d characters\n"), MAX_LINELEN ); + + xfree (buffer); + return 0; /* okay */ +} diff --git a/g10/tofu.c b/g10/tofu.c new file mode 100644 index 0000000..9cdcfaa --- /dev/null +++ b/g10/tofu.c @@ -0,0 +1,4033 @@ +/* tofu.c - TOFU trust model. + * Copyright (C) 2015, 2016 g10 Code GmbH + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +/* TODO: + + - Format the fingerprints nicely when printing (similar to gpg + --list-keys) + */ + +#include +#include +#include +#include +#include +#include + +#include "gpg.h" +#include "../common/types.h" +#include "../common/logging.h" +#include "../common/stringhelp.h" +#include "options.h" +#include "../common/mbox-util.h" +#include "../common/i18n.h" +#include "../common/ttyio.h" +#include "trustdb.h" +#include "../common/mkdir_p.h" +#include "gpgsql.h" +#include "../common/status.h" + +#include "tofu.h" + + +#define CONTROL_L ('L' - 'A' + 1) + +/* Number of days with signed / ecnrypted messages required to + * indicate that enough history is available for basic trust. */ +#define BASIC_TRUST_THRESHOLD 4 +/* Number of days with signed / encrypted messages required to + * indicate that a lot of history is available. */ +#define FULL_TRUST_THRESHOLD 21 + + +/* A struct with data pertaining to the tofu DB. There is one such + struct per session and it is cached in session's ctrl structure. + To initialize this or get the current singleton, call opendbs(). + There is no need to explicitly release it; cleanup is done when the + CTRL object is released. */ +struct tofu_dbs_s +{ + sqlite3 *db; + char *want_lock_file; + time_t want_lock_file_ctime; + + struct + { + sqlite3_stmt *savepoint_batch; + sqlite3_stmt *savepoint_batch_commit; + + sqlite3_stmt *record_binding_get_old_policy; + sqlite3_stmt *record_binding_update; + sqlite3_stmt *get_policy_select_policy_and_conflict; + sqlite3_stmt *get_trust_bindings_with_this_email; + sqlite3_stmt *get_trust_gather_other_user_ids; + sqlite3_stmt *get_trust_gather_signature_stats; + sqlite3_stmt *get_trust_gather_encryption_stats; + sqlite3_stmt *register_already_seen; + sqlite3_stmt *register_signature; + sqlite3_stmt *register_encryption; + } s; + + int in_batch_transaction; + int in_transaction; + time_t batch_update_started; +}; + + +#define STRINGIFY(s) STRINGIFY2(s) +#define STRINGIFY2(s) #s + +/* The grouping parameters when collecting signature statistics. */ + +/* If a message is signed a couple of hours in the future, just assume + some clock skew. */ +#define TIME_AGO_FUTURE_IGNORE (2 * 60 * 60) +/* Days. */ +#define TIME_AGO_UNIT_SMALL (24 * 60 * 60) +#define TIME_AGO_SMALL_THRESHOLD (7 * TIME_AGO_UNIT_SMALL) +/* Months. */ +#define TIME_AGO_UNIT_MEDIUM (30 * 24 * 60 * 60) +#define TIME_AGO_MEDIUM_THRESHOLD (2 * TIME_AGO_UNIT_MEDIUM) +/* Years. */ +#define TIME_AGO_UNIT_LARGE (365 * 24 * 60 * 60) +#define TIME_AGO_LARGE_THRESHOLD (2 * TIME_AGO_UNIT_LARGE) + +/* Local prototypes. */ +static gpg_error_t end_transaction (ctrl_t ctrl, int only_batch); +static char *email_from_user_id (const char *user_id); +static int show_statistics (tofu_dbs_t dbs, + const char *fingerprint, const char *email, + enum tofu_policy policy, + estream_t outfp, int only_status_fd, time_t now); + +const char * +tofu_policy_str (enum tofu_policy policy) +{ + switch (policy) + { + case TOFU_POLICY_NONE: return "none"; + case TOFU_POLICY_AUTO: return "auto"; + case TOFU_POLICY_GOOD: return "good"; + case TOFU_POLICY_UNKNOWN: return "unknown"; + case TOFU_POLICY_BAD: return "bad"; + case TOFU_POLICY_ASK: return "ask"; + default: return "???"; + } +} + +/* Convert a binding policy (e.g., TOFU_POLICY_BAD) to a trust level + (e.g., TRUST_BAD) in light of the current configuration. */ +int +tofu_policy_to_trust_level (enum tofu_policy policy) +{ + if (policy == TOFU_POLICY_AUTO) + /* If POLICY is AUTO, fallback to OPT.TOFU_DEFAULT_POLICY. */ + policy = opt.tofu_default_policy; + + switch (policy) + { + case TOFU_POLICY_AUTO: + /* If POLICY and OPT.TOFU_DEFAULT_POLICY are both AUTO, default + to marginal trust. */ + return TRUST_MARGINAL; + case TOFU_POLICY_GOOD: + return TRUST_FULLY; + case TOFU_POLICY_UNKNOWN: + return TRUST_UNKNOWN; + case TOFU_POLICY_BAD: + return TRUST_NEVER; + case TOFU_POLICY_ASK: + return TRUST_UNKNOWN; + default: + log_bug ("Bad value for trust policy: %d\n", + opt.tofu_default_policy); + return 0; + } +} + + + +/* Start a transaction on DB. If ONLY_BATCH is set, then this will + start a batch transaction if we haven't started a batch transaction + and one has been requested. */ +static gpg_error_t +begin_transaction (ctrl_t ctrl, int only_batch) +{ + tofu_dbs_t dbs = ctrl->tofu.dbs; + int rc; + char *err = NULL; + + log_assert (dbs); + + /* If we've been in batch update mode for a while (on average, more + * than 500 ms), to prevent starving other gpg processes, we drop + * and retake the batch lock. + * + * Note: gnupg_get_time has a one second resolution, if we wanted a + * higher resolution, we could use npth_clock_gettime. */ + if (/* No real transactions. */ + dbs->in_transaction == 0 + /* There is an open batch transaction. */ + && dbs->in_batch_transaction + /* And some time has gone by since it was started. */ + && dbs->batch_update_started != gnupg_get_time ()) + { + struct stat statbuf; + + /* If we are in a batch update, then batch updates better have + been enabled. */ + log_assert (ctrl->tofu.batch_updated_wanted); + + /* Check if another process wants to run. (We just ignore any + * stat failure. A waiter might have to wait a bit longer, but + * otherwise there should be no impact.) */ + if (gnupg_stat (dbs->want_lock_file, &statbuf) == 0 + && statbuf.st_ctime != dbs->want_lock_file_ctime) + { + end_transaction (ctrl, 2); + + /* Yield to allow another process a chance to run. Note: + * testing suggests that anything less than a 100ms tends to + * not result in the other process getting the lock. */ + gnupg_usleep (100000); + } + else + dbs->batch_update_started = gnupg_get_time (); + } + + if (/* We don't have an open batch transaction. */ + !dbs->in_batch_transaction + && (/* Batch mode is enabled or we are starting a new transaction. */ + ctrl->tofu.batch_updated_wanted || dbs->in_transaction == 0)) + { + struct stat statbuf; + + /* We are in batch mode, but we don't have an open batch + * transaction. Since the batch save point must be the outer + * save point, it must be taken before the inner save point. */ + log_assert (dbs->in_transaction == 0); + + rc = gpgsql_stepx (dbs->db, &dbs->s.savepoint_batch, + NULL, NULL, &err, + "begin immediate transaction;", GPGSQL_ARG_END); + if (rc) + { + log_error (_("error beginning transaction on TOFU database: %s\n"), + err); + sqlite3_free (err); + return gpg_error (GPG_ERR_GENERAL); + } + + dbs->in_batch_transaction = 1; + dbs->batch_update_started = gnupg_get_time (); + + if (gnupg_stat (dbs->want_lock_file, &statbuf) == 0) + dbs->want_lock_file_ctime = statbuf.st_ctime; + } + + if (only_batch) + return 0; + + log_assert (dbs->in_transaction >= 0); + dbs->in_transaction ++; + + rc = gpgsql_exec_printf (dbs->db, NULL, NULL, &err, + "savepoint inner%d;", + dbs->in_transaction); + if (rc) + { + log_error (_("error beginning transaction on TOFU database: %s\n"), + err); + sqlite3_free (err); + return gpg_error (GPG_ERR_GENERAL); + } + + return 0; +} + + +/* Commit a transaction. If ONLY_BATCH is 1, then this only ends the + * batch transaction if we have left batch mode. If ONLY_BATCH is 2, + * this commits any open batch transaction even if we are still in + * batch mode. */ +static gpg_error_t +end_transaction (ctrl_t ctrl, int only_batch) +{ + tofu_dbs_t dbs = ctrl->tofu.dbs; + int rc; + char *err = NULL; + + if (only_batch || (! only_batch && dbs->in_transaction == 1)) + { + if (!dbs) + return 0; /* Shortcut to allow for easier cleanup code. */ + + /* If we are releasing the batch transaction, then we better not + be in a normal transaction. */ + if (only_batch) + log_assert (dbs->in_transaction == 0); + + if (/* Batch mode disabled? */ + (!ctrl->tofu.batch_updated_wanted || only_batch == 2) + /* But, we still have an open batch transaction? */ + && dbs->in_batch_transaction) + { + /* The batch transaction is still in open, but we've left + * batch mode. */ + dbs->in_batch_transaction = 0; + dbs->in_transaction = 0; + + rc = gpgsql_stepx (dbs->db, &dbs->s.savepoint_batch_commit, + NULL, NULL, &err, + "commit transaction;", GPGSQL_ARG_END); + if (rc) + { + log_error (_("error committing transaction on TOFU database: %s\n"), + err); + sqlite3_free (err); + return gpg_error (GPG_ERR_GENERAL); + } + + return 0; + } + + if (only_batch) + return 0; + } + + log_assert (dbs); + log_assert (dbs->in_transaction > 0); + + rc = gpgsql_exec_printf (dbs->db, NULL, NULL, &err, + "release inner%d;", dbs->in_transaction); + + dbs->in_transaction --; + + if (rc) + { + log_error (_("error committing transaction on TOFU database: %s\n"), + err); + sqlite3_free (err); + return gpg_error (GPG_ERR_GENERAL); + } + + return 0; +} + + +static gpg_error_t +rollback_transaction (ctrl_t ctrl) +{ + tofu_dbs_t dbs = ctrl->tofu.dbs; + int rc; + char *err = NULL; + + log_assert (dbs); + log_assert (dbs->in_transaction > 0); + + /* Be careful to not undo any progress made by closed transactions in + batch mode. */ + rc = gpgsql_exec_printf (dbs->db, NULL, NULL, &err, + "rollback to inner%d;", + dbs->in_transaction); + + dbs->in_transaction --; + + if (rc) + { + log_error (_("error rolling back transaction on TOFU database: %s\n"), + err); + sqlite3_free (err); + return gpg_error (GPG_ERR_GENERAL); + } + + return 0; +} + +void +tofu_begin_batch_update (ctrl_t ctrl) +{ + ctrl->tofu.batch_updated_wanted ++; +} + +void +tofu_end_batch_update (ctrl_t ctrl) +{ + log_assert (ctrl->tofu.batch_updated_wanted > 0); + ctrl->tofu.batch_updated_wanted --; + end_transaction (ctrl, 1); +} + +/* Suspend any extant batch transaction (it is safe to call this even + no batch transaction has been started). Note: you cannot suspend a + batch transaction if you are in a normal transaction. The batch + transaction can be resumed explicitly by calling + tofu_resume_batch_transaction or implicitly by starting a normal + transaction. */ +static void +tofu_suspend_batch_transaction (ctrl_t ctrl) +{ + end_transaction (ctrl, 2); +} + +/* Resume a batch transaction if there is no extant batch transaction + and one has been requested using tofu_begin_batch_transaction. */ +static void +tofu_resume_batch_transaction (ctrl_t ctrl) +{ + begin_transaction (ctrl, 1); +} + + + +/* Wrapper around strtol which prints a warning in case of a + * conversion error. On success the converted value is stored at + * R_VALUE and 0 is returned; on error FALLBACK is stored at R_VALUE + * and an error code is returned. */ +static gpg_error_t +string_to_long (long *r_value, const char *string, long fallback, int line) +{ + gpg_error_t err; + char *tail = NULL; + + gpg_err_set_errno (0); + *r_value = strtol (string, &tail, 0); + if (errno || !(!strcmp (tail, ".0") || !*tail)) + { + err = errno? gpg_error_from_errno (errno) : gpg_error (GPG_ERR_BAD_DATA); + log_debug ("%s:%d: strtol failed for TOFU DB data; returned string" + " (string='%.10s%s'; tail='%.10s%s'): %s\n", + __FILE__, line, + string, string && strlen(string) > 10 ? "..." : "", + tail, tail && strlen(tail) > 10 ? "..." : "", + gpg_strerror (err)); + *r_value = fallback; + } + else + err = 0; + + return err; +} + + +/* Wrapper around strtoul which prints a warning in case of a + * conversion error. On success the converted value is stored at + * R_VALUE and 0 is returned; on error FALLBACK is stored at R_VALUE + * and an error code is returned. */ +static gpg_error_t +string_to_ulong (unsigned long *r_value, const char *string, + unsigned long fallback, int line) +{ + gpg_error_t err; + char *tail = NULL; + + gpg_err_set_errno (0); + *r_value = strtoul (string, &tail, 0); + if (errno || !(!strcmp (tail, ".0") || !*tail)) + { + err = errno? gpg_error_from_errno (errno) : gpg_error (GPG_ERR_BAD_DATA); + log_debug ("%s:%d: strtoul failed for TOFU DB data; returned string" + " (string='%.10s%s'; tail='%.10s%s'): %s\n", + __FILE__, line, + string, string && strlen(string) > 10 ? "..." : "", + tail, tail && strlen(tail) > 10 ? "..." : "", + gpg_strerror (err)); + *r_value = fallback; + } + else + err = 0; + + return err; +} + + + +/* Collect results of a select count (*) ...; style query. Aborts if + the argument is not a valid integer (or real of the form X.0). */ +static int +get_single_unsigned_long_cb (void *cookie, int argc, char **argv, + char **azColName) +{ + unsigned long int *count = cookie; + + (void) azColName; + + log_assert (argc == 1); + + if (string_to_ulong (count, argv[0], 0, __LINE__)) + return 1; /* Abort. */ + return 0; +} + +static int +get_single_unsigned_long_cb2 (void *cookie, int argc, char **argv, + char **azColName, sqlite3_stmt *stmt) +{ + (void) stmt; + return get_single_unsigned_long_cb (cookie, argc, argv, azColName); +} + +/* We expect a single integer column whose name is "version". COOKIE + must point to an int. This function always aborts. On error or a + if the version is bad, sets *VERSION to -1. */ +static int +version_check_cb (void *cookie, int argc, char **argv, char **azColName) +{ + int *version = cookie; + + if (argc != 1 || strcmp (azColName[0], "version") != 0) + { + *version = -1; + return 1; + } + + if (strcmp (argv[0], "1") == 0) + *version = 1; + else + { + log_error (_("unsupported TOFU database version: %s\n"), argv[0]); + *version = -1; + } + + /* Don't run again. */ + return 1; +} + +static int +check_utks (sqlite3 *db) +{ + int rc; + char *err = NULL; + struct key_item *utks; + struct key_item *ki; + int utk_count; + char *utks_string = NULL; + char keyid_str[16+1]; + long utks_unchanged = 0; + + /* An early version of the v1 format did not include the list of + * known ultimately trusted keys. + * + * This list is used to detect when the set of ultimately trusted + * keys changes. We need to detect this to invalidate the effective + * policy, which can change if an ultimately trusted key is added or + * removed. */ + rc = sqlite3_exec (db, + "create table if not exists ultimately_trusted_keys" + " (keyid);\n", + NULL, NULL, &err); + if (rc) + { + log_error ("error creating 'ultimately_trusted_keys' TOFU table: %s\n", + err); + sqlite3_free (err); + goto out; + } + + + utks = tdb_utks (); + for (ki = utks, utk_count = 0; ki; ki = ki->next, utk_count ++) + ; + + if (utk_count) + { + /* Build a list of keyids of the form "XXX","YYY","ZZZ". */ + int len = (1 + 16 + 1 + 1) * utk_count; + int o = 0; + + utks_string = xmalloc (len); + *utks_string = 0; + for (ki = utks, utk_count = 0; ki; ki = ki->next, utk_count ++) + { + utks_string[o ++] = '\''; + format_keyid (ki->kid, KF_LONG, + keyid_str, sizeof (keyid_str)); + memcpy (&utks_string[o], keyid_str, 16); + o += 16; + utks_string[o ++] = '\''; + utks_string[o ++] = ','; + } + utks_string[o - 1] = 0; + log_assert (o == len); + } + + rc = gpgsql_exec_printf + (db, get_single_unsigned_long_cb, &utks_unchanged, &err, + "select" + /* Removed UTKs? (Known UTKs in current UTKs.) */ + " ((select count(*) from ultimately_trusted_keys" + " where (keyid in (%s))) == %d)" + " and" + /* New UTKs? */ + " ((select count(*) from ultimately_trusted_keys" + " where keyid not in (%s)) == 0);", + utks_string ? utks_string : "", + utk_count, + utks_string ? utks_string : ""); + xfree (utks_string); + if (rc) + { + log_error (_("TOFU DB error")); + print_further_info ("checking if ultimately trusted keys changed: %s", + err); + sqlite3_free (err); + goto out; + } + + if (utks_unchanged) + goto out; + + if (DBG_TRUST) + log_debug ("TOFU: ultimately trusted keys changed.\n"); + + /* Given that the set of ultimately trusted keys + * changed, clear any cached policies. */ + rc = gpgsql_exec_printf + (db, NULL, NULL, &err, + "update bindings set effective_policy = %d;", + TOFU_POLICY_NONE); + if (rc) + { + log_error (_("TOFU DB error")); + print_further_info ("clearing cached policies: %s", err); + sqlite3_free (err); + goto out; + } + + /* Now, update the UTK table. */ + rc = sqlite3_exec (db, + "drop table ultimately_trusted_keys;", + NULL, NULL, &err); + if (rc) + { + log_error (_("TOFU DB error")); + print_further_info ("dropping ultimately_trusted_keys: %s", err); + sqlite3_free (err); + goto out; + } + + rc = sqlite3_exec (db, + "create table if not exists" + " ultimately_trusted_keys (keyid);\n", + NULL, NULL, &err); + if (rc) + { + log_error (_("TOFU DB error")); + print_further_info ("creating ultimately_trusted_keys: %s", err); + sqlite3_free (err); + goto out; + } + + for (ki = utks; ki; ki = ki->next) + { + format_keyid (ki->kid, KF_LONG, + keyid_str, sizeof (keyid_str)); + rc = gpgsql_exec_printf + (db, NULL, NULL, &err, + "insert into ultimately_trusted_keys values ('%s');", + keyid_str); + if (rc) + { + log_error (_("TOFU DB error")); + print_further_info ("updating ultimately_trusted_keys: %s", + err); + sqlite3_free (err); + goto out; + } + } + + out: + return rc; +} + +/* If the DB is new, initialize it. Otherwise, check the DB's + version. + + Return 0 if the database is okay and 1 otherwise. */ +static int +initdb (sqlite3 *db) +{ + char *err = NULL; + int rc; + unsigned long int count; + int version = -1; + + rc = sqlite3_exec (db, "begin transaction;", NULL, NULL, &err); + if (rc) + { + log_error (_("error beginning transaction on TOFU database: %s\n"), + err); + sqlite3_free (err); + return 1; + } + + /* If the DB has no tables, then assume this is a new DB that needs + to be initialized. */ + rc = sqlite3_exec (db, + "select count(*) from sqlite_master where type='table';", + get_single_unsigned_long_cb, &count, &err); + if (rc) + { + log_error (_("error reading TOFU database: %s\n"), err); + print_further_info ("query available tables"); + sqlite3_free (err); + goto out; + } + else if (count != 0) + /* Assume that the DB is already initialized. Make sure the + version is okay. */ + { + rc = sqlite3_exec (db, "select version from version;", version_check_cb, + &version, &err); + if (rc == SQLITE_ABORT && version == 1) + /* Happy, happy, joy, joy. */ + { + sqlite3_free (err); + rc = 0; + goto out; + } + else if (rc == SQLITE_ABORT && version == -1) + /* Unsupported version. */ + { + /* An error message was already displayed. */ + sqlite3_free (err); + goto out; + } + else if (rc) + /* Some error. */ + { + log_error (_("error determining TOFU database's version: %s\n"), err); + sqlite3_free (err); + goto out; + } + else + { + /* Unexpected success. This can only happen if there are no + rows. (select returned 0, but expected ABORT.) */ + log_error (_("error determining TOFU database's version: %s\n"), + gpg_strerror (GPG_ERR_NO_DATA)); + rc = 1; + goto out; + } + } + + /* Create the version table. */ + rc = sqlite3_exec (db, + "create table version (version INTEGER);", + NULL, NULL, &err); + if (rc) + { + log_error (_("error initializing TOFU database: %s\n"), err); + print_further_info ("create version"); + sqlite3_free (err); + goto out; + } + + /* Initialize the version table, which contains a single integer + value. */ + rc = sqlite3_exec (db, + "insert into version values (1);", + NULL, NULL, &err); + if (rc) + { + log_error (_("error initializing TOFU database: %s\n"), err); + print_further_info ("insert version"); + sqlite3_free (err); + goto out; + } + + /* The list of bindings and auxiliary data. + * + * OID is a unique ID identifying this binding (and used by the + * signatures table, see below). Note: OIDs will never be + * reused. + * + * FINGERPRINT: The key's fingerprint. + * + * EMAIL: The normalized email address. + * + * USER_ID: The unmodified user id from which EMAIL was extracted. + * + * TIME: The time this binding was first observed. + * + * POLICY: The trust policy (TOFU_POLICY_BAD, etc. as an integer). + * + * CONFLICT is either NULL or a fingerprint. Assume that we have + * a binding <0xdeadbeef, foo@example.com> and then we observe + * <0xbaddecaf, foo@example.com>. There two bindings conflict + * (they have the same email address). When we observe the + * latter binding, we warn the user about the conflict and ask + * for a policy decision about the new binding. We also change + * the old binding's policy to ask if it was auto. So that we + * know why this occurred, we also set conflict to 0xbaddecaf. + */ + rc = gpgsql_exec_printf + (db, NULL, NULL, &err, + "create table bindings\n" + " (oid INTEGER PRIMARY KEY AUTOINCREMENT,\n" + " fingerprint TEXT, email TEXT, user_id TEXT, time INTEGER,\n" + " policy INTEGER CHECK (policy in (%d, %d, %d, %d, %d)),\n" + " conflict STRING,\n" + " unique (fingerprint, email));\n" + "create index bindings_fingerprint_email\n" + " on bindings (fingerprint, email);\n" + "create index bindings_email on bindings (email);\n", + TOFU_POLICY_AUTO, TOFU_POLICY_GOOD, TOFU_POLICY_UNKNOWN, + TOFU_POLICY_BAD, TOFU_POLICY_ASK); + if (rc) + { + log_error (_("error initializing TOFU database: %s\n"), err); + print_further_info ("create bindings"); + sqlite3_free (err); + goto out; + } + + /* The signatures that we have observed. + * + * BINDING refers to a record in the bindings table, which + * describes the binding (i.e., this is a foreign key that + * references bindings.oid). + * + * SIG_DIGEST is the digest stored in the signature. + * + * SIG_TIME is the timestamp stored in the signature. + * + * ORIGIN is a free-form string that describes who fed this + * signature to GnuPG (e.g., email:claws). + * + * TIME is the time this signature was registered. */ + rc = sqlite3_exec (db, + "create table signatures " + " (binding INTEGER NOT NULL, sig_digest TEXT," + " origin TEXT, sig_time INTEGER, time INTEGER," + " primary key (binding, sig_digest, origin));", + NULL, NULL, &err); + if (rc) + { + log_error (_("error initializing TOFU database: %s\n"), err); + print_further_info ("create signatures"); + sqlite3_free (err); + goto out; + } + + out: + if (! rc) + { + /* Early version of the v1 format did not include the encryption + table. Add it. */ + rc = sqlite3_exec (db, + "create table if not exists encryptions" + " (binding INTEGER NOT NULL," + " time INTEGER);" + "create index if not exists encryptions_binding" + " on encryptions (binding);\n", + NULL, NULL, &err); + if (rc) + { + log_error ("error creating 'encryptions' TOFU table: %s\n", + err); + sqlite3_free (err); + } + } + if (! rc) + { + /* The effective policy for a binding. If a key is ultimately + * trusted, then the effective policy of all of its bindings is + * good. Likewise if a key is signed by an ultimately trusted + * key, etc. If the effective policy is NONE, then we need to + * recompute the effective policy. Otherwise, the effective + * policy is considered to be up to date, i.e., effective_policy + * is a cache of the computed policy. */ + rc = gpgsql_exec_printf + (db, NULL, NULL, &err, + "alter table bindings" + " add column effective_policy INTEGER" + " DEFAULT %d" + " CHECK (effective_policy in (%d, %d, %d, %d, %d, %d));", + TOFU_POLICY_NONE, + TOFU_POLICY_NONE, TOFU_POLICY_AUTO, TOFU_POLICY_GOOD, + TOFU_POLICY_UNKNOWN, TOFU_POLICY_BAD, TOFU_POLICY_ASK); + if (rc) + { + if (rc == SQLITE_ERROR) + /* Almost certainly "duplicate column name", which we can + * safely ignore. */ + rc = 0; + else + log_error ("adding column effective_policy to bindings DB: %s\n", + err); + sqlite3_free (err); + } + } + + if (! rc) + rc = check_utks (db); + + if (rc) + { + rc = sqlite3_exec (db, "rollback;", NULL, NULL, &err); + if (rc) + { + log_error (_("error rolling back transaction on TOFU database: %s\n"), + err); + sqlite3_free (err); + } + return 1; + } + else + { + rc = sqlite3_exec (db, "end transaction;", NULL, NULL, &err); + if (rc) + { + log_error (_("error committing transaction on TOFU database: %s\n"), + err); + sqlite3_free (err); + return 1; + } + return 0; + } +} + +static int +busy_handler (void *cookie, int call_count) +{ + ctrl_t ctrl = cookie; + tofu_dbs_t dbs = ctrl->tofu.dbs; + + (void) call_count; + + /* Update the want-lock-file time stamp (specifically, the ctime) so + * that the current owner knows that we (well, someone) want the + * lock. */ + if (dbs) + { + /* Note: we don't fail if we can't create the lock file: this + * process will have to wait a bit longer, but otherwise nothing + * horrible should happen. */ + + estream_t fp; + + fp = es_fopen (dbs->want_lock_file, "w"); + if (! fp) + log_debug ("TOFU: Error opening '%s': %s\n", + dbs->want_lock_file, strerror (errno)); + else + es_fclose (fp); + } + + /* Call again. */ + return 1; +} + +/* Create a new DB handle. Returns NULL on error. */ +/* FIXME: Change to return an error code for better reporting by the + caller. */ +static tofu_dbs_t +opendbs (ctrl_t ctrl) +{ + char *filename; + sqlite3 *db; + int rc; + + if (!ctrl->tofu.dbs) + { + filename = make_filename (gnupg_homedir (), "tofu.db", NULL); + + rc = sqlite3_open (filename, &db); + if (rc) + { + log_error (_("error opening TOFU database '%s': %s\n"), + filename, sqlite3_errmsg (db)); + /* Even if an error occurs, DB is guaranteed to be valid. */ + sqlite3_close (db); + db = NULL; + } + + /* If a DB is locked wait up to 5 seconds for the lock to be cleared + before failing. */ + if (db) + { + sqlite3_busy_timeout (db, 5 * 1000); + sqlite3_busy_handler (db, busy_handler, ctrl); + } + + if (db && initdb (db)) + { + sqlite3_close (db); + db = NULL; + } + + if (db) + { + ctrl->tofu.dbs = xmalloc_clear (sizeof *ctrl->tofu.dbs); + ctrl->tofu.dbs->db = db; + ctrl->tofu.dbs->want_lock_file = xasprintf ("%s-want-lock", filename); + } + + xfree (filename); + } + else + log_assert (ctrl->tofu.dbs->db); + + return ctrl->tofu.dbs; +} + + +/* Release all of the resources associated with the DB handle. */ +void +tofu_closedbs (ctrl_t ctrl) +{ + tofu_dbs_t dbs; + sqlite3_stmt **statements; + + dbs = ctrl->tofu.dbs; + if (!dbs) + return; /* Not initialized. */ + + log_assert (dbs->in_transaction == 0); + + end_transaction (ctrl, 2); + + /* Arghh, that is a surprising use of the struct. */ + for (statements = (void *) &dbs->s; + (void *) statements < (void *) &(&dbs->s)[1]; + statements ++) + sqlite3_finalize (*statements); + + sqlite3_close (dbs->db); + xfree (dbs->want_lock_file); + xfree (dbs); + ctrl->tofu.dbs = NULL; +} + + +/* Collect results of a select min (foo) ...; style query. Aborts if + the argument is not a valid integer (or real of the form X.0). */ +static int +get_single_long_cb (void *cookie, int argc, char **argv, char **azColName) +{ + long *count = cookie; + + (void) azColName; + + log_assert (argc == 1); + + if (string_to_long (count, argv[0], 0, __LINE__)) + return 1; /* Abort. */ + + return 0; +} + +static int +get_single_long_cb2 (void *cookie, int argc, char **argv, char **azColName, + sqlite3_stmt *stmt) +{ + (void) stmt; + return get_single_long_cb (cookie, argc, argv, azColName); +} + +/* Record (or update) a trust policy about a (possibly new) + binding. + + If SHOW_OLD is set, the binding's old policy is displayed. */ +static gpg_error_t +record_binding (tofu_dbs_t dbs, const char *fingerprint, const char *email, + const char *user_id, + enum tofu_policy policy, enum tofu_policy effective_policy, + const char *conflict, int set_conflict, + int show_old, time_t now) +{ + char *fingerprint_pp = format_hexfingerprint (fingerprint, NULL, 0); + gpg_error_t rc; + char *err = NULL; + + if (! (policy == TOFU_POLICY_AUTO + || policy == TOFU_POLICY_GOOD + || policy == TOFU_POLICY_UNKNOWN + || policy == TOFU_POLICY_BAD + || policy == TOFU_POLICY_ASK)) + log_bug ("%s: Bad value for policy (%d)!\n", __func__, policy); + + + if (DBG_TRUST || show_old) + { + /* Get the old policy. Since this is just for informational + * purposes, there is no need to start a transaction or to die + * if there is a failure. */ + + /* policy_old needs to be a long and not an enum tofu_policy, + because we pass it by reference to get_single_long_cb2, which + expects a long. */ + long policy_old = TOFU_POLICY_NONE; + + rc = gpgsql_stepx + (dbs->db, &dbs->s.record_binding_get_old_policy, + get_single_long_cb2, &policy_old, &err, + "select policy from bindings where fingerprint = ? and email = ?", + GPGSQL_ARG_STRING, fingerprint, GPGSQL_ARG_STRING, email, + GPGSQL_ARG_END); + if (rc) + { + log_debug ("TOFU: Error reading from binding database" + " (reading policy for ): %s\n", + fingerprint, email, err); + sqlite3_free (err); + } + + if (policy_old != TOFU_POLICY_NONE) + (show_old ? log_info : log_debug) + ("Changing TOFU trust policy for binding" + " from %s to %s.\n", + fingerprint, show_old ? user_id : email, + tofu_policy_str (policy_old), + tofu_policy_str (policy)); + else + (show_old ? log_info : log_debug) + ("Setting TOFU trust policy for new binding" + " to %s.\n", + fingerprint, show_old ? user_id : email, + tofu_policy_str (policy)); + } + + if (opt.dry_run) + { + log_info ("TOFU database update skipped due to --dry-run\n"); + rc = 0; + goto leave; + } + + rc = gpgsql_stepx + (dbs->db, &dbs->s.record_binding_update, NULL, NULL, &err, + "insert or replace into bindings\n" + " (oid, fingerprint, email, user_id, time," + " policy, conflict, effective_policy)\n" + " values (\n" + /* If we don't explicitly reuse the OID, then SQLite will + * reallocate a new one. We just need to search for the OID + * based on the fingerprint and email since they are unique. */ + " (select oid from bindings where fingerprint = ? and email = ?),\n" + " ?, ?, ?, ?, ?," + /* If SET_CONFLICT is 0, then preserve conflict's current value. */ + " case ?" + " when 0 then" + " (select conflict from bindings where fingerprint = ? and email = ?)" + " else ?" + " end," + " ?);", + /* oid subquery. */ + GPGSQL_ARG_STRING, fingerprint, GPGSQL_ARG_STRING, email, + /* values 2 through 6. */ + GPGSQL_ARG_STRING, fingerprint, GPGSQL_ARG_STRING, email, + GPGSQL_ARG_STRING, user_id, + GPGSQL_ARG_LONG_LONG, (long long) now, + GPGSQL_ARG_INT, (int) policy, + /* conflict subquery. */ + GPGSQL_ARG_INT, set_conflict ? 1 : 0, + GPGSQL_ARG_STRING, fingerprint, GPGSQL_ARG_STRING, email, + GPGSQL_ARG_STRING, conflict ? conflict : "", + GPGSQL_ARG_INT, (int) effective_policy, + GPGSQL_ARG_END); + if (rc) + { + log_error (_("error updating TOFU database: %s\n"), err); + print_further_info (" insert bindings = %s", + fingerprint, email, tofu_policy_str (policy)); + sqlite3_free (err); + goto leave; + } + + leave: + xfree (fingerprint_pp); + return rc; +} + + +/* Collect the strings returned by a query in a simple string list. + Any NULL values are converted to the empty string. + + If a result has 3 rows and each row contains two columns, then the + results are added to the list as follows (the value is parentheses + is the 1-based index in the final list): + + row 1, col 2 (6) + row 1, col 1 (5) + row 2, col 2 (4) + row 2, col 1 (3) + row 3, col 2 (2) + row 3, col 1 (1) + + This is because add_to_strlist pushes the results onto the front of + the list. The end result is that the rows are backwards, but the + columns are in the expected order. */ +static int +strings_collect_cb (void *cookie, int argc, char **argv, char **azColName) +{ + int i; + strlist_t *strlist = cookie; + + (void) azColName; + + for (i = argc - 1; i >= 0; i --) + add_to_strlist (strlist, argv[i] ? argv[i] : ""); + + return 0; +} + +static int +strings_collect_cb2 (void *cookie, int argc, char **argv, char **azColName, + sqlite3_stmt *stmt) +{ + (void) stmt; + return strings_collect_cb (cookie, argc, argv, azColName); + +} + +/* Auxiliary data structure to collect statistics about + signatures. */ +struct signature_stats +{ + struct signature_stats *next; + + /* The user-assigned policy for this binding. */ + enum tofu_policy policy; + + /* How long ago the signature was created (rounded to a multiple of + TIME_AGO_UNIT_SMALL, etc.). */ + long time_ago; + /* Number of signatures during this time. */ + unsigned long count; + + /* If the corresponding key/user id has been expired / revoked. */ + int is_expired; + int is_revoked; + + /* The key that generated this signature. */ + char fingerprint[1]; +}; + +static void +signature_stats_free (struct signature_stats *stats) +{ + while (stats) + { + struct signature_stats *next = stats->next; + xfree (stats); + stats = next; + } +} + +static void +signature_stats_prepend (struct signature_stats **statsp, + const char *fingerprint, + enum tofu_policy policy, + long time_ago, + unsigned long count) +{ + struct signature_stats *stats = + xmalloc_clear (sizeof (*stats) + strlen (fingerprint)); + + stats->next = *statsp; + *statsp = stats; + + strcpy (stats->fingerprint, fingerprint); + stats->policy = policy; + stats->time_ago = time_ago; + stats->count = count; +} + + +/* Process rows that contain the four columns: + + . */ +static int +signature_stats_collect_cb (void *cookie, int argc, char **argv, + char **azColName, sqlite3_stmt *stmt) +{ + struct signature_stats **statsp = cookie; + int i = 0; + enum tofu_policy policy; + long time_ago; + unsigned long count; + long along; + + (void) azColName; + (void) stmt; + + i ++; + + if (string_to_long (&along, argv[i], 0, __LINE__)) + return 1; /* Abort */ + policy = along; + i ++; + + if (! argv[i]) + time_ago = 0; + else + { + if (string_to_long (&time_ago, argv[i], 0, __LINE__)) + return 1; /* Abort. */ + } + i ++; + + /* If time_ago is NULL, then we had no messages, but we still have a + single row, which count(*) turns into 1. */ + if (! argv[i - 1]) + count = 0; + else + { + if (string_to_ulong (&count, argv[i], 0, __LINE__)) + return 1; /* Abort */ + } + i ++; + + log_assert (argc == i); + + signature_stats_prepend (statsp, argv[0], policy, time_ago, count); + + return 0; +} + +/* Format the first part of a conflict message and return that as a + * malloced string. Returns NULL on error. */ +static char * +format_conflict_msg_part1 (int policy, strlist_t conflict_set, + const char *email) +{ + estream_t fp; + char *fingerprint; + char *tmpstr, *text; + + log_assert (conflict_set); + fingerprint = conflict_set->d; + + fp = es_fopenmem (0, "rw,samethread"); + if (!fp) + log_fatal ("error creating memory stream: %s\n", + gpg_strerror (gpg_error_from_syserror())); + + if (policy == TOFU_POLICY_NONE) + { + es_fprintf (fp, + _("This is the first time the email address \"%s\" is " + "being used with key %s."), + email, fingerprint); + es_fputs (" ", fp); + } + else if (policy == TOFU_POLICY_ASK && conflict_set->next) + { + int conflicts = strlist_length (conflict_set); + es_fprintf + (fp, ngettext("The email address \"%s\" is associated with %d key!", + "The email address \"%s\" is associated with %d keys!", + conflicts), + email, conflicts); + if (opt.verbose) + es_fprintf (fp, + _(" Since this binding's policy was 'auto', it has been " + "changed to 'ask'.")); + es_fputs (" ", fp); + } + + es_fprintf (fp, + _("Please indicate whether this email address should" + " be associated with key %s or whether you think someone" + " is impersonating \"%s\"."), + fingerprint, email); + es_fputc ('\n', fp); + + es_fputc (0, fp); + if (es_fclose_snatch (fp, (void **)&tmpstr, NULL)) + log_fatal ("error snatching memory stream\n"); + text = format_text (tmpstr, 72, 80); + es_free (tmpstr); + + return text; +} + + +/* Return 1 if A signed B and B signed A. */ +static int +cross_sigs (const char *email, kbnode_t a, kbnode_t b) +{ + int i; + + PKT_public_key *a_pk = a->pkt->pkt.public_key; + PKT_public_key *b_pk = b->pkt->pkt.public_key; + + char a_keyid[33]; + char b_keyid[33]; + + if (DBG_TRUST) + { + format_keyid (pk_main_keyid (a_pk), + KF_LONG, a_keyid, sizeof (a_keyid)); + format_keyid (pk_main_keyid (b_pk), + KF_LONG, b_keyid, sizeof (b_keyid)); + } + + for (i = 0; i < 2; i ++) + { + /* See if SIGNER signed SIGNEE. */ + + kbnode_t signer = i == 0 ? a : b; + kbnode_t signee = i == 0 ? b : a; + + PKT_public_key *signer_pk = signer->pkt->pkt.public_key; + u32 *signer_kid = pk_main_keyid (signer_pk); + kbnode_t n; + + int saw_email = 0; + + /* Iterate over SIGNEE's keyblock and see if there is a valid + signature from SIGNER. */ + for (n = signee; n; n = n->next) + { + PKT_signature *sig; + + if (n->pkt->pkttype == PKT_USER_ID) + { + if (saw_email) + /* We're done: we've processed all signatures on the + user id. */ + break; + else + { + /* See if this is the matching user id. */ + PKT_user_id *user_id = n->pkt->pkt.user_id; + char *email2 = email_from_user_id (user_id->name); + + if (strcmp (email, email2) == 0) + saw_email = 1; + + xfree (email2); + } + } + + if (! saw_email) + continue; + + if (n->pkt->pkttype != PKT_SIGNATURE) + continue; + + sig = n->pkt->pkt.signature; + + if (! (sig->sig_class == 0x10 + || sig->sig_class == 0x11 + || sig->sig_class == 0x12 + || sig->sig_class == 0x13)) + /* Not a signature over a user id. */ + continue; + + /* SIG is on SIGNEE's keyblock. If SIG was generated by the + signer, then it's a match. */ + if (keyid_cmp (sig->keyid, signer_kid) == 0) + /* Match! */ + break; + } + if (! n) + /* We didn't find a signature from signer over signee. */ + { + if (DBG_TRUST) + log_debug ("No cross sig between %s and %s\n", + a_keyid, b_keyid); + return 0; + } + } + + /* A signed B and B signed A. */ + if (DBG_TRUST) + log_debug ("Cross sig between %s and %s\n", + a_keyid, b_keyid); + + return 1; +} + +/* Return whether the key was signed by an ultimately trusted key. */ +static int +signed_by_utk (const char *email, kbnode_t a) +{ + kbnode_t n; + int saw_email = 0; + + for (n = a; n; n = n->next) + { + PKT_signature *sig; + + if (n->pkt->pkttype == PKT_USER_ID) + { + if (saw_email) + /* We're done: we've processed all signatures on the + user id. */ + break; + else + { + /* See if this is the matching user id. */ + PKT_user_id *user_id = n->pkt->pkt.user_id; + char *email2 = email_from_user_id (user_id->name); + + if (strcmp (email, email2) == 0) + saw_email = 1; + + xfree (email2); + } + } + + if (! saw_email) + continue; + + if (n->pkt->pkttype != PKT_SIGNATURE) + continue; + + sig = n->pkt->pkt.signature; + + if (! (sig->sig_class == 0x10 + || sig->sig_class == 0x11 + || sig->sig_class == 0x12 + || sig->sig_class == 0x13)) + /* Not a signature over a user id. */ + continue; + + /* SIG is on SIGNEE's keyblock. If SIG was generated by the + signer, then it's a match. */ + if (tdb_keyid_is_utk (sig->keyid)) + { + /* Match! */ + if (DBG_TRUST) + log_debug ("TOFU: %s is signed by an ultimately trusted key.\n", + pk_keyid_str (a->pkt->pkt.public_key)); + + return 1; + } + } + + if (DBG_TRUST) + log_debug ("TOFU: %s is NOT signed by an ultimately trusted key.\n", + pk_keyid_str (a->pkt->pkt.public_key)); + + return 0; +} + + +enum + { + BINDING_NEW = 1 << 0, + BINDING_CONFLICT = 1 << 1, + BINDING_EXPIRED = 1 << 2, + BINDING_REVOKED = 1 << 3 + }; + + +/* Ask the user about the binding. There are three ways we could end + * up here: + * + * - This is a new binding and there is a conflict + * (policy == TOFU_POLICY_NONE && conflict_set_count > 1), + * + * - This is a new binding and opt.tofu_default_policy is set to + * ask. (policy == TOFU_POLICY_NONE && opt.tofu_default_policy == + * TOFU_POLICY_ASK), or, + * + * - The policy is ask (the user deferred last time) (policy == + * TOFU_POLICY_ASK). + * + * Note: this function must not be called while in a transaction! + * + * CONFLICT_SET includes all of the conflicting bindings + * with FINGERPRINT first. FLAGS is a bit-wise or of + * BINDING_NEW, etc. + */ +static void +ask_about_binding (ctrl_t ctrl, + enum tofu_policy *policy, + int *trust_level, + strlist_t conflict_set, + const char *fingerprint, + const char *email, + const char *user_id, + time_t now) +{ + tofu_dbs_t dbs; + strlist_t iter; + int conflict_set_count = strlist_length (conflict_set); + char *sqerr = NULL; + int rc; + estream_t fp; + strlist_t other_user_ids = NULL; + struct signature_stats *stats = NULL; + struct signature_stats *stats_iter = NULL; + char *prompt = NULL; + const char *choices; + + dbs = ctrl->tofu.dbs; + log_assert (dbs); + log_assert (dbs->in_transaction == 0); + + fp = es_fopenmem (0, "rw,samethread"); + if (!fp) + log_fatal ("error creating memory stream: %s\n", + gpg_strerror (gpg_error_from_syserror())); + + { + char *text = format_conflict_msg_part1 (*policy, conflict_set, email); + if (!text) /* FIXME: Return the error all the way up. */ + log_fatal ("format failed: %s\n", + gpg_strerror (gpg_error_from_syserror())); + + es_fputs (text, fp); + es_fputc ('\n', fp); + xfree (text); + } + + begin_transaction (ctrl, 0); + + /* Find other user ids associated with this key and whether the + * bindings are marked as good or bad. */ + rc = gpgsql_stepx + (dbs->db, &dbs->s.get_trust_gather_other_user_ids, + strings_collect_cb2, &other_user_ids, &sqerr, + "select user_id, policy from bindings where fingerprint = ?;", + GPGSQL_ARG_STRING, fingerprint, GPGSQL_ARG_END); + if (rc) + { + log_error (_("error gathering other user IDs: %s\n"), sqerr); + sqlite3_free (sqerr); + sqerr = NULL; + rc = gpg_error (GPG_ERR_GENERAL); + } + + if (other_user_ids) + { + strlist_t strlist_iter; + + es_fprintf (fp, _("This key's user IDs:\n")); + for (strlist_iter = other_user_ids; + strlist_iter; + strlist_iter = strlist_iter->next) + { + char *other_user_id = strlist_iter->d; + char *other_thing; + enum tofu_policy other_policy; + + log_assert (strlist_iter->next); + strlist_iter = strlist_iter->next; + other_thing = strlist_iter->d; + + other_policy = atoi (other_thing); + + es_fprintf (fp, " %s (", other_user_id); + es_fprintf (fp, _("policy: %s"), tofu_policy_str (other_policy)); + es_fprintf (fp, ")\n"); + } + es_fprintf (fp, "\n"); + + free_strlist (other_user_ids); + } + + /* Get the stats for all the keys in CONFLICT_SET. */ + strlist_rev (&conflict_set); + for (iter = conflict_set; iter && ! rc; iter = iter->next) + { +#define STATS_SQL(table, time, sign) \ + "select fingerprint, policy, time_ago, count(*)\n" \ + " from\n" \ + " (select bindings.*,\n" \ + " "sign" case\n" \ + " when delta ISNULL then 1\n" \ + /* From the future (but if its just a couple of hours in the \ + * future don't turn it into a warning)? Or should we use \ + * small, medium or large units? (Note: whatever we do, we \ + * keep the value in seconds. Then when we group, everything \ + * that rounds to the same number of seconds is grouped.) */ \ + " when delta < -("STRINGIFY (TIME_AGO_FUTURE_IGNORE)") then 2\n" \ + " when delta < ("STRINGIFY (TIME_AGO_SMALL_THRESHOLD)")\n" \ + " then 3\n" \ + " when delta < ("STRINGIFY (TIME_AGO_MEDIUM_THRESHOLD)")\n" \ + " then 4\n" \ + " when delta < ("STRINGIFY (TIME_AGO_LARGE_THRESHOLD)")\n" \ + " then 5\n" \ + " else 6\n" \ + " end time_ago,\n" \ + " delta time_ago_raw\n" \ + " from bindings\n" \ + " left join\n" \ + " (select *,\n" \ + " cast(? - " time " as real) delta\n" \ + " from " table ") ss\n" \ + " on ss.binding = bindings.oid)\n" \ + " where email = ? and fingerprint = ?\n" \ + " group by time_ago\n" \ + /* Make sure the current key is first. */ \ + " order by time_ago desc;\n" + + /* Use the time when we saw the signature, not when the + signature was created as that can be forged. */ + rc = gpgsql_stepx + (dbs->db, &dbs->s.get_trust_gather_signature_stats, + signature_stats_collect_cb, &stats, &sqerr, + STATS_SQL ("signatures", "time", ""), + GPGSQL_ARG_LONG_LONG, (long long) now, + GPGSQL_ARG_STRING, email, + GPGSQL_ARG_STRING, iter->d, + GPGSQL_ARG_END); + if (rc) + { + rc = gpg_error (GPG_ERR_GENERAL); + break; + } + + if (!stats || strcmp (iter->d, stats->fingerprint) != 0) + /* No stats for this binding. Add a dummy entry. */ + signature_stats_prepend (&stats, iter->d, TOFU_POLICY_AUTO, 1, 1); + + rc = gpgsql_stepx + (dbs->db, &dbs->s.get_trust_gather_encryption_stats, + signature_stats_collect_cb, &stats, &sqerr, + STATS_SQL ("encryptions", "time", "-"), + GPGSQL_ARG_LONG_LONG, (long long) now, + GPGSQL_ARG_STRING, email, + GPGSQL_ARG_STRING, iter->d, + GPGSQL_ARG_END); + if (rc) + { + rc = gpg_error (GPG_ERR_GENERAL); + break; + } + +#undef STATS_SQL + + if (!stats || strcmp (iter->d, stats->fingerprint) != 0 + || stats->time_ago > 0) + /* No stats for this binding. Add a dummy entry. */ + signature_stats_prepend (&stats, iter->d, TOFU_POLICY_AUTO, -1, 1); + } + end_transaction (ctrl, 0); + strlist_rev (&conflict_set); + if (rc) + { + strlist_t strlist_iter; + + log_error (_("error gathering signature stats: %s\n"), sqerr); + sqlite3_free (sqerr); + sqerr = NULL; + + es_fprintf (fp, ngettext("The email address \"%s\" is" + " associated with %d key:\n", + "The email address \"%s\" is" + " associated with %d keys:\n", + conflict_set_count), + email, conflict_set_count); + for (strlist_iter = conflict_set; + strlist_iter; + strlist_iter = strlist_iter->next) + es_fprintf (fp, " %s\n", strlist_iter->d); + } + else + { + char *key = NULL; + strlist_t binding; + int seen_in_past = 0; + int encrypted = 1; + + es_fprintf (fp, _("Statistics for keys" + " with the email address \"%s\":\n"), + email); + for (stats_iter = stats; stats_iter; stats_iter = stats_iter->next) + { +#if 0 + log_debug ("%s: time_ago: %ld; count: %ld\n", + stats_iter->fingerprint, + stats_iter->time_ago, + stats_iter->count); +#endif + + if (stats_iter->time_ago > 0 && encrypted) + { + /* We've change from the encrypted stats to the verified + * stats. Reset SEEN_IN_PAST. */ + encrypted = 0; + seen_in_past = 0; + } + + if (! key || strcmp (key, stats_iter->fingerprint)) + { + int this_key; + char *key_pp; + + key = stats_iter->fingerprint; + this_key = strcmp (key, fingerprint) == 0; + key_pp = format_hexfingerprint (key, NULL, 0); + es_fprintf (fp, " %s (", key_pp); + + /* Find the associated binding. */ + for (binding = conflict_set; + binding; + binding = binding->next) + if (strcmp (key, binding->d) == 0) + break; + log_assert (binding); + + if ((binding->flags & BINDING_REVOKED)) + { + es_fprintf (fp, _("revoked")); + es_fprintf (fp, ", "); + } + else if ((binding->flags & BINDING_EXPIRED)) + { + es_fprintf (fp, _("expired")); + es_fprintf (fp, ", "); + } + + if (this_key) + es_fprintf (fp, _("this key")); + else + es_fprintf (fp, _("policy: %s"), + tofu_policy_str (stats_iter->policy)); + es_fputs ("):\n", fp); + xfree (key_pp); + + seen_in_past = 0; + + show_statistics (dbs, stats_iter->fingerprint, email, + TOFU_POLICY_ASK, NULL, 1, now); + } + + if (labs(stats_iter->time_ago) == 1) + { + /* The 1 in this case is the NULL entry. */ + log_assert (stats_iter->count == 1); + stats_iter->count = 0; + } + seen_in_past += stats_iter->count; + + es_fputs (" ", fp); + + if (!stats_iter->count) + { + if (stats_iter->time_ago > 0) + es_fprintf (fp, ngettext("Verified %d message.", + "Verified %d messages.", + seen_in_past), seen_in_past); + else + es_fprintf (fp, ngettext("Encrypted %d message.", + "Encrypted %d messages.", + seen_in_past), seen_in_past); + } + else if (labs(stats_iter->time_ago) == 2) + { + if (stats_iter->time_ago > 0) + es_fprintf (fp, ngettext("Verified %d message in the future.", + "Verified %d messages in the future.", + seen_in_past), seen_in_past); + else + es_fprintf (fp, ngettext("Encrypted %d message in the future.", + "Encrypted %d messages in the future.", + seen_in_past), seen_in_past); + /* Reset it. */ + seen_in_past = 0; + } + else + { + if (labs(stats_iter->time_ago) == 3) + { + int days = 1 + stats_iter->time_ago / TIME_AGO_UNIT_SMALL; + if (stats_iter->time_ago > 0) + es_fprintf + (fp, + ngettext("Messages verified over the past %d day: %d.", + "Messages verified over the past %d days: %d.", + days), days, seen_in_past); + else + es_fprintf + (fp, + ngettext("Messages encrypted over the past %d day: %d.", + "Messages encrypted over the past %d days: %d.", + days), days, seen_in_past); + } + else if (labs(stats_iter->time_ago) == 4) + { + int months = 1 + stats_iter->time_ago / TIME_AGO_UNIT_MEDIUM; + if (stats_iter->time_ago > 0) + es_fprintf + (fp, + ngettext("Messages verified over the past %d month: %d.", + "Messages verified over the past %d months: %d.", + months), months, seen_in_past); + else + es_fprintf + (fp, + ngettext("Messages encrypted over the past %d month: %d.", + "Messages encrypted over the past %d months: %d.", + months), months, seen_in_past); + } + else if (labs(stats_iter->time_ago) == 5) + { + int years = 1 + stats_iter->time_ago / TIME_AGO_UNIT_LARGE; + if (stats_iter->time_ago > 0) + es_fprintf + (fp, + ngettext("Messages verified over the past %d year: %d.", + "Messages verified over the past %d years: %d.", + years), years, seen_in_past); + else + es_fprintf + (fp, + ngettext("Messages encrypted over the past %d year: %d.", + "Messages encrypted over the past %d years: %d.", + years), years, seen_in_past); + } + else if (labs(stats_iter->time_ago) == 6) + { + if (stats_iter->time_ago > 0) + es_fprintf + (fp, _("Messages verified in the past: %d."), + seen_in_past); + else + es_fprintf + (fp, _("Messages encrypted in the past: %d."), + seen_in_past); + } + else + log_assert (! "Broken SQL.\n"); + } + es_fputs ("\n", fp); + } + } + + if (conflict_set_count > 1 || (conflict_set->flags & BINDING_CONFLICT)) + { + /* This is a conflict. */ + + /* TRANSLATORS: Please translate the text found in the source + * file below. We don't directly internationalize that text so + * that we can tweak it without breaking translations. */ + const char *text = _("TOFU detected a binding conflict"); + char *textbuf; + if (!strcmp (text, "TOFU detected a binding conflict")) + { + /* No translation. Use the English text. */ + text = + "Normally, an email address is associated with a single key. " + "However, people sometimes generate a new key if " + "their key is too old or they think it might be compromised. " + "Alternatively, a new key may indicate a man-in-the-middle " + "attack! Before accepting this association, you should talk to or " + "call the person to make sure this new key is legitimate."; + } + textbuf = format_text (text, 72, 80); + es_fprintf (fp, "\n%s\n", textbuf? textbuf : "[OUT OF CORE!]"); + xfree (textbuf); + } + + es_fputc ('\n', fp); + + /* Add a NUL terminator. */ + es_fputc (0, fp); + if (es_fclose_snatch (fp, (void **) &prompt, NULL)) + log_fatal ("error snatching memory stream\n"); + + /* I think showing the large message once is sufficient. If we + * would move it right before the cpr_get many lines will scroll + * away and the user might not realize that he merely entered a + * wrong choice (because he does not see that either). As a small + * benefit we allow C-L to redisplay everything. */ + tty_printf ("%s", prompt); + + /* Suspend any transaction: it could take a while until the user + responds. */ + tofu_suspend_batch_transaction (ctrl); + while (1) + { + char *response; + + /* TRANSLATORS: Two letters (normally the lower and upper case + * version of the hotkey) for each of the five choices. If + * there is only one choice in your language, repeat it. */ + choices = _("gG" "aA" "uU" "rR" "bB"); + if (strlen (choices) != 10) + log_bug ("Bad TOFU conflict translation! Please report."); + + response = cpr_get + ("tofu.conflict", + _("(G)ood, (A)ccept once, (U)nknown, (R)eject once, (B)ad? ")); + trim_spaces (response); + cpr_kill_prompt (); + if (*response == CONTROL_L) + tty_printf ("%s", prompt); + else if (!response[0]) + /* Default to unknown. Don't save it. */ + { + tty_printf (_("Defaulting to unknown.\n")); + *policy = TOFU_POLICY_UNKNOWN; + break; + } + else if (!response[1]) + { + char *choice = strchr (choices, *response); + + if (choice) + { + int c = ((size_t) choice - (size_t) choices) / 2; + + switch (c) + { + case 0: /* Good. */ + *policy = TOFU_POLICY_GOOD; + *trust_level = tofu_policy_to_trust_level (*policy); + break; + case 1: /* Accept once. */ + *policy = TOFU_POLICY_ASK; + *trust_level = tofu_policy_to_trust_level (TOFU_POLICY_GOOD); + break; + case 2: /* Unknown. */ + *policy = TOFU_POLICY_UNKNOWN; + *trust_level = tofu_policy_to_trust_level (*policy); + break; + case 3: /* Reject once. */ + *policy = TOFU_POLICY_ASK; + *trust_level = tofu_policy_to_trust_level (TOFU_POLICY_BAD); + break; + case 4: /* Bad. */ + *policy = TOFU_POLICY_BAD; + *trust_level = tofu_policy_to_trust_level (*policy); + break; + default: + log_bug ("c should be between 0 and 4 but it is %d!", c); + } + + if (record_binding (dbs, fingerprint, email, user_id, + *policy, TOFU_POLICY_NONE, NULL, 0, 0, now)) + { + /* If there's an error registering the + * binding, don't save the signature. */ + *trust_level = _tofu_GET_TRUST_ERROR; + } + break; + } + } + xfree (response); + } + + tofu_resume_batch_transaction (ctrl); + + xfree (prompt); + + signature_stats_free (stats); +} + +/* Return the set of keys that conflict with the binding (including the binding itself, which will be first in the + list). For each returned key also sets BINDING_NEW, etc. */ +static strlist_t +build_conflict_set (ctrl_t ctrl, tofu_dbs_t dbs, + PKT_public_key *pk, const char *fingerprint, + const char *email) +{ + gpg_error_t rc; + char *sqerr; + strlist_t conflict_set = NULL; + int conflict_set_count; + strlist_t iter; + kbnode_t *kb_all; + KEYDB_HANDLE hd; + int i; + + /* Get the fingerprints of any bindings that share the email address + * and whether the bindings have a known conflict. + * + * Note: if the binding in question is in the DB, it will also be + * returned. Thus, if the result set is empty, then is a new binding. */ + rc = gpgsql_stepx + (dbs->db, &dbs->s.get_trust_bindings_with_this_email, + strings_collect_cb2, &conflict_set, &sqerr, + "select" + /* A binding should only appear once, but try not to break in the + * case of corruption. */ + " fingerprint || case sum(conflict NOTNULL) when 0 then '' else '!' end" + " from bindings where email = ?" + " group by fingerprint" + /* Make sure the current key comes first in the result list (if + it is present). */ + " order by fingerprint = ? asc, fingerprint desc;", + GPGSQL_ARG_STRING, email, + GPGSQL_ARG_STRING, fingerprint, + GPGSQL_ARG_END); + if (rc) + { + log_error (_("error reading TOFU database: %s\n"), sqerr); + print_further_info ("listing fingerprints"); + sqlite3_free (sqerr); + rc = gpg_error (GPG_ERR_GENERAL); + return NULL; + } + + /* Set BINDING_CONFLICT if the binding has a known conflict. This + * allows us to distinguish between bindings where the user + * explicitly set the policy to ask and bindings where we set the + * policy to ask due to a conflict. */ + for (iter = conflict_set; iter; iter = iter->next) + { + int l = strlen (iter->d); + if (!(l == 2 * MAX_FINGERPRINT_LEN + || l == 2 * MAX_FINGERPRINT_LEN + 1)) + { + log_error (_("TOFU db corruption detected.\n")); + print_further_info ("fingerprint '%s' is not %d characters long", + iter->d, 2 * MAX_FINGERPRINT_LEN); + } + + if (l >= 1 && iter->d[l - 1] == '!') + { + iter->flags |= BINDING_CONFLICT; + /* Remove the !. */ + iter->d[l - 1] = 0; + } + } + + /* If the current binding has not yet been recorded, add it to the + * list. (The order by above ensures that if it is present, it will + * be first.) */ + if (! (conflict_set && strcmp (conflict_set->d, fingerprint) == 0)) + { + add_to_strlist (&conflict_set, fingerprint); + conflict_set->flags |= BINDING_NEW; + } + + conflict_set_count = strlist_length (conflict_set); + + /* Eliminate false conflicts. */ + + if (conflict_set_count == 1) + /* We only have a single key. There are no false conflicts to + eliminate. But, we do need to set the flags. */ + { + if (pk->has_expired) + conflict_set->flags |= BINDING_EXPIRED; + if (pk->flags.revoked) + conflict_set->flags |= BINDING_REVOKED; + + return conflict_set; + } + + /* If two keys have cross signatures, then they are controlled by + * the same person and thus are not in conflict. */ + kb_all = xcalloc (sizeof (kb_all[0]), conflict_set_count); + hd = keydb_new (); + for (i = 0, iter = conflict_set; + i < conflict_set_count; + i ++, iter = iter->next) + { + char *fp = iter->d; + KEYDB_SEARCH_DESC desc; + kbnode_t kb; + PKT_public_key *binding_pk; + kbnode_t n; + int found_user_id; + + rc = keydb_search_reset (hd); + if (rc) + { + log_error ("resetting keydb failed: %s\n", gpg_strerror (rc)); + continue; + } + + rc = classify_user_id (fp, &desc, 0); + if (rc) + { + log_error (_("error parsing key specification '%s': %s\n"), + fp, gpg_strerror (rc)); + continue; + } + + rc = keydb_search (hd, &desc, 1, NULL); + if (rc) + { + /* Note: it is entirely possible that we don't have the key + corresponding to an entry in the TOFU DB. This can + happen if we merge two TOFU DBs, but not the key + rings. */ + log_info (_("key \"%s\" not found: %s\n"), + fp, gpg_strerror (rc)); + continue; + } + + rc = keydb_get_keyblock (hd, &kb); + if (rc) + { + log_error (_("error reading keyblock: %s\n"), + gpg_strerror (rc)); + print_further_info ("fingerprint: %s", fp); + continue; + } + + merge_keys_and_selfsig (ctrl, kb); + + log_assert (kb->pkt->pkttype == PKT_PUBLIC_KEY); + + kb_all[i] = kb; + + /* Since we have the key block, use this opportunity to figure + * out if the binding is expired or revoked. */ + binding_pk = kb->pkt->pkt.public_key; + + /* The binding is always expired/revoked if the key is + * expired/revoked. */ + if (binding_pk->has_expired) + iter->flags |= BINDING_EXPIRED; + if (binding_pk->flags.revoked) + iter->flags |= BINDING_REVOKED; + + /* The binding is also expired/revoked if the user id is + * expired/revoked. */ + n = kb; + found_user_id = 0; + while ((n = find_next_kbnode (n, PKT_USER_ID)) && ! found_user_id) + { + PKT_user_id *user_id2 = n->pkt->pkt.user_id; + char *email2; + + if (user_id2->attrib_data) + continue; + + email2 = email_from_user_id (user_id2->name); + + if (strcmp (email, email2) == 0) + { + found_user_id = 1; + + if (user_id2->flags.revoked) + iter->flags |= BINDING_REVOKED; + if (user_id2->flags.expired) + iter->flags |= BINDING_EXPIRED; + } + + xfree (email2); + } + + if (! found_user_id) + { + log_info (_("TOFU db corruption detected.\n")); + print_further_info ("user id '%s' not on key block '%s'", + email, fingerprint); + } + } + keydb_release (hd); + + /* Now that we have the key blocks, check for cross sigs. */ + { + int j; + strlist_t *prevp; + strlist_t iter_next; + int *die; + + log_assert (conflict_set_count > 0); + die = xtrycalloc (conflict_set_count, sizeof *die); + if (!die) + { + /*err = gpg_error_from_syserror ();*/ + xoutofcore (); /* Fixme: Let the function return an error. */ + } + + for (i = 0; i < conflict_set_count; i ++) + { + /* Look for cross sigs between this key (i == 0) or a key + * that has cross sigs with i == 0 (i.e., transitively) */ + if (! (i == 0 || die[i])) + continue; + + for (j = i + 1; j < conflict_set_count; j ++) + /* Be careful: we might not have a key block for a key. */ + if (kb_all[i] && kb_all[j] && cross_sigs (email, kb_all[i], kb_all[j])) + die[j] = 1; + } + + /* Free unconflicting bindings (and all of the key blocks). */ + for (iter = conflict_set, prevp = &conflict_set, i = 0; + iter; + iter = iter_next, i ++) + { + iter_next = iter->next; + + release_kbnode (kb_all[i]); + + if (die[i]) + { + *prevp = iter_next; + iter->next = NULL; + free_strlist (iter); + conflict_set_count --; + } + else + { + prevp = &iter->next; + } + } + + /* We shouldn't have removed the head. */ + log_assert (conflict_set); + log_assert (conflict_set_count >= 1); + xfree (die); + } + xfree (kb_all); + + if (DBG_TRUST) + { + log_debug ("binding conflicts:\n", + fingerprint, email); + for (iter = conflict_set; iter; iter = iter->next) + { + log_debug (" %s:%s%s%s%s\n", + iter->d, + (iter->flags & BINDING_NEW) ? " new" : "", + (iter->flags & BINDING_CONFLICT) ? " known_conflict" : "", + (iter->flags & BINDING_EXPIRED) ? " expired" : "", + (iter->flags & BINDING_REVOKED) ? " revoked" : ""); + } + } + + return conflict_set; +} + + +/* Return the effective policy for the binding + * (email has already been normalized). Returns + * _tofu_GET_POLICY_ERROR if an error occurs. Returns any conflict + * information in *CONFLICT_SETP if CONFLICT_SETP is not NULL and the + * returned policy is TOFU_POLICY_ASK (consequently, if there is a + * conflict, but the user set the policy to good *CONFLICT_SETP will + * empty). Note: as per build_conflict_set, which is used to build + * the conflict information, the conflict information includes the + * current user id as the first element of the linked list. + * + * This function registers the binding in the bindings table if it has + * not yet been registered. + */ +static enum tofu_policy +get_policy (ctrl_t ctrl, tofu_dbs_t dbs, PKT_public_key *pk, + const char *fingerprint, const char *user_id, const char *email, + strlist_t *conflict_setp, time_t now) +{ + int rc; + char *err = NULL; + strlist_t results = NULL; + enum tofu_policy policy = _tofu_GET_POLICY_ERROR; + enum tofu_policy effective_policy_orig = TOFU_POLICY_NONE; + enum tofu_policy effective_policy = _tofu_GET_POLICY_ERROR; + long along; + char *conflict_orig = NULL; + char *conflict = NULL; + strlist_t conflict_set = NULL; + int conflict_set_count; + + /* Check if the binding is known + (TOFU_POLICY_NONE cannot appear in the DB. Thus, if POLICY is + still TOFU_POLICY_NONE after executing the query, then the + result set was empty.) */ + rc = gpgsql_stepx (dbs->db, &dbs->s.get_policy_select_policy_and_conflict, + strings_collect_cb2, &results, &err, + "select policy, conflict, effective_policy from bindings\n" + " where fingerprint = ? and email = ?", + GPGSQL_ARG_STRING, fingerprint, + GPGSQL_ARG_STRING, email, + GPGSQL_ARG_END); + if (rc) + { + log_error (_("error reading TOFU database: %s\n"), err); + print_further_info ("reading the policy"); + sqlite3_free (err); + rc = gpg_error (GPG_ERR_GENERAL); + goto out; + } + + if (strlist_length (results) == 0) + { + /* No results. Use the defaults. */ + policy = TOFU_POLICY_NONE; + effective_policy = TOFU_POLICY_NONE; + } + else if (strlist_length (results) == 3) + { + /* Parse and sanity check the results. */ + + if (string_to_long (&along, results->d, 0, __LINE__)) + { + log_error (_("error reading TOFU database: %s\n"), + gpg_strerror (GPG_ERR_BAD_DATA)); + print_further_info ("bad value for policy: %s", results->d); + goto out; + } + policy = along; + + if (! (policy == TOFU_POLICY_AUTO + || policy == TOFU_POLICY_GOOD + || policy == TOFU_POLICY_UNKNOWN + || policy == TOFU_POLICY_BAD + || policy == TOFU_POLICY_ASK)) + { + log_error (_("error reading TOFU database: %s\n"), + gpg_strerror (GPG_ERR_DB_CORRUPTED)); + print_further_info ("invalid value for policy (%d)", policy); + effective_policy = _tofu_GET_POLICY_ERROR; + goto out; + } + + if (*results->next->d) + conflict = xstrdup (results->next->d); + + if (string_to_long (&along, results->next->next->d, 0, __LINE__)) + { + log_error (_("error reading TOFU database: %s\n"), + gpg_strerror (GPG_ERR_BAD_DATA)); + print_further_info ("bad value for effective policy: %s", + results->next->next->d); + goto out; + } + effective_policy = along; + + if (! (effective_policy == TOFU_POLICY_NONE + || effective_policy == TOFU_POLICY_AUTO + || effective_policy == TOFU_POLICY_GOOD + || effective_policy == TOFU_POLICY_UNKNOWN + || effective_policy == TOFU_POLICY_BAD + || effective_policy == TOFU_POLICY_ASK)) + { + log_error (_("error reading TOFU database: %s\n"), + gpg_strerror (GPG_ERR_DB_CORRUPTED)); + print_further_info ("invalid value for effective_policy (%d)", + effective_policy); + effective_policy = _tofu_GET_POLICY_ERROR; + goto out; + } + } + else + { + /* The result has the wrong form. */ + + log_error (_("error reading TOFU database: %s\n"), + gpg_strerror (GPG_ERR_BAD_DATA)); + print_further_info ("reading policy: expected 3 columns, got %d\n", + strlist_length (results)); + goto out; + } + + /* Save the effective policy and conflict so we know if we changed + * them. */ + effective_policy_orig = effective_policy; + conflict_orig = conflict; + + /* Unless there is a conflict, if the effective policy is cached, + * just return it. The reason we don't do this when there is a + * conflict is because of the following scenario: assume A and B + * conflict and B has signed A's key. Now, later we import A's + * signature on B. We need to recheck A, but the signature was on + * B, i.e., when B changes, we invalidate B's effective policy, but + * we also need to invalidate A's effective policy. Instead, we + * assume that conflicts are rare and don't optimize for them, which + * would complicate the code. */ + if (effective_policy != TOFU_POLICY_NONE && !conflict) + goto out; + + /* If the user explicitly set the policy, then respect that. */ + if (policy != TOFU_POLICY_AUTO && policy != TOFU_POLICY_NONE) + { + effective_policy = policy; + goto out; + } + + /* Unless proven wrong, assume the effective policy is 'auto'. */ + effective_policy = TOFU_POLICY_AUTO; + + /* See if the key is ultimately trusted. */ + { + u32 kid[2]; + + keyid_from_pk (pk, kid); + if (tdb_keyid_is_utk (kid)) + { + effective_policy = TOFU_POLICY_GOOD; + goto out; + } + } + + /* See if the key is signed by an ultimately trusted key. */ + { + int fingerprint_raw_len = strlen (fingerprint) / 2; + char fingerprint_raw[20]; + int len = 0; + + if (fingerprint_raw_len != sizeof fingerprint_raw + || ((len = hex2bin (fingerprint, + fingerprint_raw, fingerprint_raw_len)) + != strlen (fingerprint))) + { + if (DBG_TRUST) + log_debug ("TOFU: Bad fingerprint: %s (len: %zu, parsed: %d)\n", + fingerprint, strlen (fingerprint), len); + } + else + { + int lookup_err; + kbnode_t kb; + + lookup_err = get_pubkey_byfprint (ctrl, NULL, &kb, + fingerprint_raw, + fingerprint_raw_len); + if (lookup_err) + { + if (DBG_TRUST) + log_debug ("TOFU: Looking up %s: %s\n", + fingerprint, gpg_strerror (lookup_err)); + } + else + { + int is_signed_by_utk = signed_by_utk (email, kb); + release_kbnode (kb); + if (is_signed_by_utk) + { + effective_policy = TOFU_POLICY_GOOD; + goto out; + } + } + } + } + + /* Check for any conflicts / see if a previously discovered conflict + * disappeared. The latter can happen if the conflicting bindings + * are now cross signed, for instance. */ + + conflict_set = build_conflict_set (ctrl, dbs, pk, fingerprint, email); + conflict_set_count = strlist_length (conflict_set); + if (conflict_set_count == 0) + { + /* build_conflict_set should always at least return the current + binding. Something went wrong. */ + effective_policy = _tofu_GET_POLICY_ERROR; + goto out; + } + + if (conflict_set_count == 1 + && (conflict_set->flags & BINDING_NEW)) + { + /* We've never observed a binding with this email address and we + * have a default policy, which is not to ask the user. */ + + /* If we've seen this binding, then we've seen this email and + * policy couldn't possibly be TOFU_POLICY_NONE. */ + log_assert (policy == TOFU_POLICY_NONE); + + if (DBG_TRUST) + log_debug ("TOFU: New binding , no conflict.\n", + fingerprint, email); + + effective_policy = TOFU_POLICY_AUTO; + goto out; + } + + if (conflict_set_count == 1 + && (conflict_set->flags & BINDING_CONFLICT)) + { + /* No known conflicts now, but there was a conflict. This means + * at some point, there was a conflict and we changed this + * binding's policy to ask and set the conflicting key. The + * conflict can go away if there is not a cross sig between the + * two keys. In this case, just silently clear the conflict and + * reset the policy to auto. */ + + if (DBG_TRUST) + log_debug ("TOFU: binding had a conflict, but it's been resolved (probably via cross sig).\n", + fingerprint, email); + + effective_policy = TOFU_POLICY_AUTO; + conflict = NULL; + + goto out; + } + + if (conflict_set_count == 1) + { + /* No conflicts and never marked as conflicting. */ + + log_assert (!conflict); + + effective_policy = TOFU_POLICY_AUTO; + + goto out; + } + + /* There is a conflicting key. */ + log_assert (conflict_set_count > 1); + effective_policy = TOFU_POLICY_ASK; + conflict = xstrdup (conflict_set->next->d); + + out: + log_assert (policy == _tofu_GET_POLICY_ERROR + || policy == TOFU_POLICY_NONE + || policy == TOFU_POLICY_AUTO + || policy == TOFU_POLICY_GOOD + || policy == TOFU_POLICY_UNKNOWN + || policy == TOFU_POLICY_BAD + || policy == TOFU_POLICY_ASK); + /* Everything but NONE. */ + log_assert (effective_policy == _tofu_GET_POLICY_ERROR + || effective_policy == TOFU_POLICY_AUTO + || effective_policy == TOFU_POLICY_GOOD + || effective_policy == TOFU_POLICY_UNKNOWN + || effective_policy == TOFU_POLICY_BAD + || effective_policy == TOFU_POLICY_ASK); + + if (effective_policy != TOFU_POLICY_ASK && conflict) + conflict = NULL; + + /* If we don't have a record of this binding, its effective policy + * changed, or conflict changed, update the DB. */ + if (effective_policy != _tofu_GET_POLICY_ERROR + && (/* New binding. */ + policy == TOFU_POLICY_NONE + /* effective_policy changed. */ + || effective_policy != effective_policy_orig + /* conflict changed. */ + || (conflict != conflict_orig + && (!conflict || !conflict_orig + || strcmp (conflict, conflict_orig) != 0)))) + { + if (record_binding (dbs, fingerprint, email, user_id, + policy == TOFU_POLICY_NONE ? TOFU_POLICY_AUTO : policy, + effective_policy, conflict, 1, 0, now) != 0) + log_error ("error setting TOFU binding's policy" + " to %s\n", tofu_policy_str (policy)); + } + + /* If the caller wants the set of conflicts, return it. */ + if (effective_policy == TOFU_POLICY_ASK && conflict_setp) + { + if (! conflict_set) + conflict_set = build_conflict_set (ctrl, dbs, pk, fingerprint, email); + *conflict_setp = conflict_set; + } + else + { + free_strlist (conflict_set); + + if (conflict_setp) + *conflict_setp = NULL; + } + + xfree (conflict_orig); + if (conflict != conflict_orig) + xfree (conflict); + free_strlist (results); + + return effective_policy; +} + + +/* Return the trust level (TRUST_NEVER, etc.) for the binding + * (email is already normalized). If no policy + * is registered, returns TOFU_POLICY_NONE. If an error occurs, + * returns _tofu_GET_TRUST_ERROR. + * + * PK is the public key object for FINGERPRINT. + * + * USER_ID is the unadulterated user id. + * + * If MAY_ASK is set, then we may interact with the user. This is + * necessary if there is a conflict or the binding's policy is + * TOFU_POLICY_ASK. In the case of a conflict, we set the new + * conflicting binding's policy to TOFU_POLICY_ASK. In either case, + * we return TRUST_UNDEFINED. Note: if MAY_ASK is set, then this + * function must not be called while in a transaction! */ +static enum tofu_policy +get_trust (ctrl_t ctrl, PKT_public_key *pk, + const char *fingerprint, const char *email, + const char *user_id, int may_ask, + enum tofu_policy *policyp, strlist_t *conflict_setp, + time_t now) +{ + tofu_dbs_t dbs = ctrl->tofu.dbs; + int in_transaction = 0; + enum tofu_policy policy; + int rc; + char *sqerr = NULL; + strlist_t conflict_set = NULL; + int trust_level = TRUST_UNKNOWN; + strlist_t iter; + + log_assert (dbs); + + if (may_ask) + log_assert (dbs->in_transaction == 0); + + if (opt.batch) + may_ask = 0; + + log_assert (pk_is_primary (pk)); + + /* Make sure _tofu_GET_TRUST_ERROR isn't equal to any of the trust + levels. */ + log_assert (_tofu_GET_TRUST_ERROR != TRUST_UNKNOWN + && _tofu_GET_TRUST_ERROR != TRUST_EXPIRED + && _tofu_GET_TRUST_ERROR != TRUST_UNDEFINED + && _tofu_GET_TRUST_ERROR != TRUST_NEVER + && _tofu_GET_TRUST_ERROR != TRUST_MARGINAL + && _tofu_GET_TRUST_ERROR != TRUST_FULLY + && _tofu_GET_TRUST_ERROR != TRUST_ULTIMATE); + + begin_transaction (ctrl, 0); + in_transaction = 1; + + /* We need to call get_policy even if the key is ultimately trusted + * to make sure the binding has been registered. */ + policy = get_policy (ctrl, dbs, pk, fingerprint, user_id, email, + &conflict_set, now); + + if (policy == TOFU_POLICY_ASK) + /* The conflict set should always contain at least one element: + * the current key. */ + log_assert (conflict_set); + else + /* If the policy is not TOFU_POLICY_ASK, then conflict_set will be + * NULL. */ + log_assert (! conflict_set); + + /* If the key is ultimately trusted, there is nothing to do. */ + { + u32 kid[2]; + + keyid_from_pk (pk, kid); + if (tdb_keyid_is_utk (kid)) + { + trust_level = TRUST_ULTIMATE; + policy = TOFU_POLICY_GOOD; + goto out; + } + } + + if (policy == TOFU_POLICY_AUTO) + { + policy = opt.tofu_default_policy; + if (DBG_TRUST) + log_debug ("TOFU: binding 's policy is" + " auto (default: %s).\n", + fingerprint, email, + tofu_policy_str (opt.tofu_default_policy)); + + if (policy == TOFU_POLICY_ASK) + /* The default policy is ASK, but there is no conflict (policy + * was 'auto'). In this case, we need to make sure the + * conflict set includes at least the current user id. */ + { + add_to_strlist (&conflict_set, fingerprint); + } + } + switch (policy) + { + case TOFU_POLICY_AUTO: + case TOFU_POLICY_GOOD: + case TOFU_POLICY_UNKNOWN: + case TOFU_POLICY_BAD: + /* The saved judgement is auto -> auto, good, unknown or bad. + * We don't need to ask the user anything. */ + if (DBG_TRUST) + log_debug ("TOFU: Known binding 's policy: %s\n", + fingerprint, email, tofu_policy_str (policy)); + trust_level = tofu_policy_to_trust_level (policy); + goto out; + + case TOFU_POLICY_ASK: + /* We need to ask the user what to do. */ + break; + + case _tofu_GET_POLICY_ERROR: + trust_level = _tofu_GET_TRUST_ERROR; + goto out; + + default: + log_bug ("%s: Impossible value for policy (%d)\n", __func__, policy); + } + + + /* We get here if: + * + * 1. The saved policy is auto and the default policy is ask + * (get_policy() == TOFU_POLICY_AUTO + * && opt.tofu_default_policy == TOFU_POLICY_ASK) + * + * 2. The saved policy is ask (either last time the user selected + * accept once or reject once or there was a conflict and this + * binding's policy was changed from auto to ask) + * (policy == TOFU_POLICY_ASK). + */ + log_assert (policy == TOFU_POLICY_ASK); + + if (may_ask) + { + /* We can't be in a normal transaction in ask_about_binding. */ + end_transaction (ctrl, 0); + in_transaction = 0; + + /* If we get here, we need to ask the user about the binding. */ + ask_about_binding (ctrl, + &policy, + &trust_level, + conflict_set, + fingerprint, + email, + user_id, + now); + } + else + { + trust_level = TRUST_UNDEFINED; + } + + /* Mark any conflicting bindings that have an automatic policy as + * now requiring confirmation. Note: we do this after we ask for + * confirmation so that when the current policy is printed, it is + * correct. */ + if (! in_transaction) + { + begin_transaction (ctrl, 0); + in_transaction = 1; + } + + /* The conflict set should always contain at least one element: + * the current key. */ + log_assert (conflict_set); + + for (iter = conflict_set->next; iter; iter = iter->next) + { + /* We don't immediately set the effective policy to 'ask, + because */ + rc = gpgsql_exec_printf + (dbs->db, NULL, NULL, &sqerr, + "update bindings set effective_policy = %d, conflict = %Q" + " where email = %Q and fingerprint = %Q and effective_policy != %d;", + TOFU_POLICY_NONE, fingerprint, + email, iter->d, TOFU_POLICY_ASK); + if (rc) + { + log_error (_("error changing TOFU policy: %s\n"), sqerr); + print_further_info ("binding: ", + fingerprint, user_id); + sqlite3_free (sqerr); + sqerr = NULL; + rc = gpg_error (GPG_ERR_GENERAL); + } + else if (DBG_TRUST) + log_debug ("Set %s to conflict with %s\n", + iter->d, fingerprint); + } + + out: + if (in_transaction) + end_transaction (ctrl, 0); + + if (policyp) + *policyp = policy; + + if (conflict_setp) + *conflict_setp = conflict_set; + else + free_strlist (conflict_set); + + return trust_level; +} + + +/* Return a malloced string of the form + * "7~months" + * The caller should replace all '~' in the returned string by a space + * and also free the returned string. + * + * This is actually a bad hack which may not work correctly with all + * languages. + */ +static char * +time_ago_str (long long int t) +{ + /* It would be nice to use a macro to do this, but gettext + works on the unpreprocessed code. */ +#define MIN_SECS (60) +#define HOUR_SECS (60 * MIN_SECS) +#define DAY_SECS (24 * HOUR_SECS) +#define WEEK_SECS (7 * DAY_SECS) +#define MONTH_SECS (30 * DAY_SECS) +#define YEAR_SECS (365 * DAY_SECS) + + if (t > 2 * YEAR_SECS) + { + long long int c = t / YEAR_SECS; + return xtryasprintf (ngettext("%lld~year", "%lld~years", c), c); + } + if (t > 2 * MONTH_SECS) + { + long long int c = t / MONTH_SECS; + return xtryasprintf (ngettext("%lld~month", "%lld~months", c), c); + } + if (t > 2 * WEEK_SECS) + { + long long int c = t / WEEK_SECS; + return xtryasprintf (ngettext("%lld~week", "%lld~weeks", c), c); + } + if (t > 2 * DAY_SECS) + { + long long int c = t / DAY_SECS; + return xtryasprintf (ngettext("%lld~day", "%lld~days", c), c); + } + if (t > 2 * HOUR_SECS) + { + long long int c = t / HOUR_SECS; + return xtryasprintf (ngettext("%lld~hour", "%lld~hours", c), c); + } + if (t > 2 * MIN_SECS) + { + long long int c = t / MIN_SECS; + return xtryasprintf (ngettext("%lld~minute", "%lld~minutes", c), c); + } + return xtryasprintf (ngettext("%lld~second", "%lld~seconds", t), t); +} + + +/* If FP is NULL, write TOFU_STATS status line. If FP is not NULL + * write a "tfs" record to that stream. */ +static void +write_stats_status (estream_t fp, + enum tofu_policy policy, + unsigned long signature_count, + unsigned long signature_first_seen, + unsigned long signature_most_recent, + unsigned long signature_days, + unsigned long encryption_count, + unsigned long encryption_first_done, + unsigned long encryption_most_recent, + unsigned long encryption_days) +{ + int summary; + int validity; + unsigned long days_sq; + + /* Use the euclidean distance (m = sqrt(a^2 + b^2)) rather then the + sum of the magnitudes (m = a + b) to ensure a balance between + verified signatures and encrypted messages. */ + days_sq = signature_days * signature_days + encryption_days * encryption_days; + + if (days_sq < 1) + validity = 1; /* Key without history. */ + else if (days_sq < (2 * BASIC_TRUST_THRESHOLD) * (2 * BASIC_TRUST_THRESHOLD)) + validity = 2; /* Key with too little history. */ + else if (days_sq < (2 * FULL_TRUST_THRESHOLD) * (2 * FULL_TRUST_THRESHOLD)) + validity = 3; /* Key with enough history for basic trust. */ + else + validity = 4; /* Key with a lot of history. */ + + if (policy == TOFU_POLICY_ASK) + summary = 0; /* Key requires attention. */ + else + summary = validity; + + if (fp) + { + es_fprintf (fp, "tfs:1:%d:%lu:%lu:%s:%lu:%lu:%lu:%lu:%d:%lu:%lu:\n", + summary, signature_count, encryption_count, + tofu_policy_str (policy), + signature_first_seen, signature_most_recent, + encryption_first_done, encryption_most_recent, + validity, signature_days, encryption_days); + } + else + { + write_status_printf (STATUS_TOFU_STATS, + "%d %lu %lu %s %lu %lu %lu %lu %d %lu %lu", + summary, + signature_count, + encryption_count, + tofu_policy_str (policy), + signature_first_seen, + signature_most_recent, + encryption_first_done, + encryption_most_recent, + validity, + signature_days, encryption_days); + } +} + +/* Note: If OUTFP is not NULL, this function merely prints a "tfs" record + * to OUTFP. + * + * POLICY is the key's policy (as returned by get_policy). + * + * Returns 0 if ONLY_STATUS_FD is set. Otherwise, returns whether + * the caller should call show_warning after iterating over all user + * ids. + */ +static int +show_statistics (tofu_dbs_t dbs, + const char *fingerprint, const char *email, + enum tofu_policy policy, + estream_t outfp, int only_status_fd, time_t now) +{ + char *fingerprint_pp; + int rc; + strlist_t strlist = NULL; + char *err = NULL; + + unsigned long signature_first_seen = 0; + unsigned long signature_most_recent = 0; + unsigned long signature_count = 0; + unsigned long signature_days = 0; + unsigned long encryption_first_done = 0; + unsigned long encryption_most_recent = 0; + unsigned long encryption_count = 0; + unsigned long encryption_days = 0; + + int show_warning = 0; + + if (only_status_fd && ! is_status_enabled ()) + return 0; + + fingerprint_pp = format_hexfingerprint (fingerprint, NULL, 0); + + /* Get the signature stats. */ + rc = gpgsql_exec_printf + (dbs->db, strings_collect_cb, &strlist, &err, + "select count (*), coalesce (min (signatures.time), 0),\n" + " coalesce (max (signatures.time), 0)\n" + " from signatures\n" + " left join bindings on signatures.binding = bindings.oid\n" + " where fingerprint = %Q and email = %Q;", + fingerprint, email); + if (rc) + { + log_error (_("error reading TOFU database: %s\n"), err); + print_further_info ("getting signature statistics"); + sqlite3_free (err); + rc = gpg_error (GPG_ERR_GENERAL); + goto out; + } + rc = gpgsql_exec_printf + (dbs->db, strings_collect_cb, &strlist, &err, + "select count (*) from\n" + " (select round(signatures.time / (24 * 60 * 60)) day\n" + " from signatures\n" + " left join bindings on signatures.binding = bindings.oid\n" + " where fingerprint = %Q and email = %Q\n" + " group by day);", + fingerprint, email); + if (rc) + { + log_error (_("error reading TOFU database: %s\n"), err); + print_further_info ("getting signature statistics (by day)"); + sqlite3_free (err); + rc = gpg_error (GPG_ERR_GENERAL); + goto out; + } + + if (strlist) + { + /* We expect exactly 4 elements. */ + log_assert (strlist->next); + log_assert (strlist->next->next); + log_assert (strlist->next->next->next); + log_assert (! strlist->next->next->next->next); + + string_to_ulong (&signature_days, strlist->d, -1, __LINE__); + string_to_ulong (&signature_count, strlist->next->d, -1, __LINE__); + string_to_ulong (&signature_first_seen, + strlist->next->next->d, -1, __LINE__); + string_to_ulong (&signature_most_recent, + strlist->next->next->next->d, -1, __LINE__); + + free_strlist (strlist); + strlist = NULL; + } + + /* Get the encryption stats. */ + rc = gpgsql_exec_printf + (dbs->db, strings_collect_cb, &strlist, &err, + "select count (*), coalesce (min (encryptions.time), 0),\n" + " coalesce (max (encryptions.time), 0)\n" + " from encryptions\n" + " left join bindings on encryptions.binding = bindings.oid\n" + " where fingerprint = %Q and email = %Q;", + fingerprint, email); + if (rc) + { + log_error (_("error reading TOFU database: %s\n"), err); + print_further_info ("getting encryption statistics"); + sqlite3_free (err); + rc = gpg_error (GPG_ERR_GENERAL); + goto out; + } + rc = gpgsql_exec_printf + (dbs->db, strings_collect_cb, &strlist, &err, + "select count (*) from\n" + " (select round(encryptions.time / (24 * 60 * 60)) day\n" + " from encryptions\n" + " left join bindings on encryptions.binding = bindings.oid\n" + " where fingerprint = %Q and email = %Q\n" + " group by day);", + fingerprint, email); + if (rc) + { + log_error (_("error reading TOFU database: %s\n"), err); + print_further_info ("getting encryption statistics (by day)"); + sqlite3_free (err); + rc = gpg_error (GPG_ERR_GENERAL); + goto out; + } + + if (strlist) + { + /* We expect exactly 4 elements. */ + log_assert (strlist->next); + log_assert (strlist->next->next); + log_assert (strlist->next->next->next); + log_assert (! strlist->next->next->next->next); + + string_to_ulong (&encryption_days, strlist->d, -1, __LINE__); + string_to_ulong (&encryption_count, strlist->next->d, -1, __LINE__); + string_to_ulong (&encryption_first_done, + strlist->next->next->d, -1, __LINE__); + string_to_ulong (&encryption_most_recent, + strlist->next->next->next->d, -1, __LINE__); + + free_strlist (strlist); + strlist = NULL; + } + + if (!outfp) + write_status_text_and_buffer (STATUS_TOFU_USER, fingerprint, + email, strlen (email), 0); + + write_stats_status (outfp, policy, + signature_count, + signature_first_seen, + signature_most_recent, + signature_days, + encryption_count, + encryption_first_done, + encryption_most_recent, + encryption_days); + + if (!outfp && !only_status_fd) + { + estream_t fp; + char *msg; + + fp = es_fopenmem (0, "rw,samethread"); + if (! fp) + log_fatal ("error creating memory stream: %s\n", + gpg_strerror (gpg_error_from_syserror())); + + if (signature_count == 0 && encryption_count == 0) + { + es_fprintf (fp, + _("%s: Verified 0~signatures and encrypted 0~messages."), + email); + } + else + { + if (signature_count == 0) + es_fprintf (fp, _("%s: Verified 0 signatures."), email); + else + { + /* Note: Translation not possible with that wording. */ + char *ago_str = time_ago_str (now - signature_first_seen); + es_fprintf + (fp, "%s: Verified %ld~signatures in the past %s.", + email, signature_count, ago_str); + xfree (ago_str); + } + + es_fputs (" ", fp); + + if (encryption_count == 0) + es_fprintf (fp, _("Encrypted 0 messages.")); + else + { + char *ago_str = time_ago_str (now - encryption_first_done); + + /* Note: Translation not possible with this kind of + * composition. */ + es_fprintf (fp, "Encrypted %ld~messages in the past %s.", + encryption_count, ago_str); + xfree (ago_str); + } + } + + if (opt.verbose) + { + es_fputs (" ", fp); + es_fprintf (fp, _("(policy: %s)"), tofu_policy_str (policy)); + } + es_fputs ("\n", fp); + + + { + char *tmpmsg, *p; + es_fputc (0, fp); + if (es_fclose_snatch (fp, (void **) &tmpmsg, NULL)) + log_fatal ("error snatching memory stream\n"); + msg = format_text (tmpmsg, 72, 80); + if (!msg) /* FIXME: Return the error all the way up. */ + log_fatal ("format failed: %s\n", + gpg_strerror (gpg_error_from_syserror())); + es_free (tmpmsg); + + /* Print a status line but suppress the trailing LF. + * Spaces are not percent escaped. */ + if (*msg) + write_status_buffer (STATUS_TOFU_STATS_LONG, + msg, strlen (msg)-1, -1); + + /* Remove the non-breaking space markers. */ + for (p=msg; *p; p++) + if (*p == '~') + *p = ' '; + } + + log_string (GPGRT_LOG_INFO, msg); + xfree (msg); + + if (policy == TOFU_POLICY_AUTO) + { + if (signature_count == 0) + log_info (_("Warning: we have yet to see" + " a message signed using this key and user id!\n")); + else if (signature_count == 1) + log_info (_("Warning: we've only seen one message" + " signed using this key and user id!\n")); + + if (encryption_count == 0) + log_info (_("Warning: you have yet to encrypt" + " a message to this key!\n")); + else if (encryption_count == 1) + log_info (_("Warning: you have only encrypted" + " one message to this key!\n")); + + /* Cf. write_stats_status */ + if ((encryption_count * encryption_count + + signature_count * signature_count) + < ((2 * BASIC_TRUST_THRESHOLD) * (2 * BASIC_TRUST_THRESHOLD))) + show_warning = 1; + } + } + + out: + xfree (fingerprint_pp); + + return show_warning; +} + +static void +show_warning (const char *fingerprint, strlist_t user_id_list) +{ + char *set_policy_command; + char *text; + char *tmpmsg; + + set_policy_command = + xasprintf ("gpg --tofu-policy bad %s", fingerprint); + + tmpmsg = xasprintf + (ngettext + ("Warning: if you think you've seen more signatures " + "by this key and user id, then this key might be a " + "forgery! Carefully examine the email address for small " + "variations. If the key is suspect, then use\n" + " %s\n" + "to mark it as being bad.\n", + "Warning: if you think you've seen more signatures " + "by this key and these user ids, then this key might be a " + "forgery! Carefully examine the email addresses for small " + "variations. If the key is suspect, then use\n" + " %s\n" + "to mark it as being bad.\n", + strlist_length (user_id_list)), + set_policy_command); + + text = format_text (tmpmsg, 72, 80); + if (!text) /* FIXME: Return the error all the way up. */ + log_fatal ("format failed: %s\n", + gpg_strerror (gpg_error_from_syserror())); + xfree (tmpmsg); + log_string (GPGRT_LOG_INFO, text); + xfree (text); + + es_free (set_policy_command); +} + + +/* Extract the email address from a user id and normalize it. If the + user id doesn't contain an email address, then we use the whole + user_id and normalize that. The returned string must be freed. */ +static char * +email_from_user_id (const char *user_id) +{ + char *email = mailbox_from_userid (user_id); + if (! email) + { + /* Hmm, no email address was provided or we are out of core. Just + take the lower-case version of the whole user id. It could be + a hostname, for instance. */ + email = ascii_strlwr (xstrdup (user_id)); + } + + return email; +} + +/* Register the signature with the bindings , + for each USER_ID in USER_ID_LIST. The fingerprint is taken from + the primary key packet PK. + + SIG_DIGEST_BIN is the binary representation of the message's + digest. SIG_DIGEST_BIN_LEN is its length. + + SIG_TIME is the time that the signature was generated. + + ORIGIN is a free-formed string describing the origin of the + signature. If this was from an email and the Claws MUA was used, + then this should be something like: "email:claws". If this is + NULL, the default is simply "unknown". + + If MAY_ASK is 1, then this function may interact with the user. + This is necessary if there is a conflict or the binding's policy is + TOFU_POLICY_ASK. + + This function returns 0 on success and an error code if an error + occurred. */ +gpg_error_t +tofu_register_signature (ctrl_t ctrl, + PKT_public_key *pk, strlist_t user_id_list, + const byte *sig_digest_bin, int sig_digest_bin_len, + time_t sig_time, const char *origin) +{ + time_t now = gnupg_get_time (); + gpg_error_t rc; + tofu_dbs_t dbs; + char *fingerprint = NULL; + strlist_t user_id; + char *email = NULL; + char *sqlerr = NULL; + char *sig_digest = NULL; + unsigned long c; + + dbs = opendbs (ctrl); + if (! dbs) + { + rc = gpg_error (GPG_ERR_GENERAL); + log_error (_("error opening TOFU database: %s\n"), + gpg_strerror (rc)); + return rc; + } + + /* We do a query and then an insert. Make sure they are atomic + by wrapping them in a transaction. */ + rc = begin_transaction (ctrl, 0); + if (rc) + return rc; + + log_assert (pk_is_primary (pk)); + + sig_digest = make_radix64_string (sig_digest_bin, sig_digest_bin_len); + if (!sig_digest) + { + rc = gpg_error_from_syserror (); + goto leave; + } + fingerprint = hexfingerprint (pk, NULL, 0); + if (!fingerprint) + { + rc = gpg_error_from_syserror (); + goto leave; + } + + if (! origin) + origin = "unknown"; /* The default origin is simply "unknown". */ + + for (user_id = user_id_list; user_id; user_id = user_id->next) + { + email = email_from_user_id (user_id->d); + + if (DBG_TRUST) + log_debug ("TOFU: Registering signature %s with binding" + " \n", + sig_digest, fingerprint, email); + + /* Make sure the binding exists and record any TOFU + conflicts. */ + if (get_trust (ctrl, pk, fingerprint, email, user_id->d, + 0, NULL, NULL, now) + == _tofu_GET_TRUST_ERROR) + { + rc = gpg_error (GPG_ERR_GENERAL); + xfree (email); + break; + } + + /* If we've already seen this signature before, then don't add + it again. */ + rc = gpgsql_stepx + (dbs->db, &dbs->s.register_already_seen, + get_single_unsigned_long_cb2, &c, &sqlerr, + "select count (*)\n" + " from signatures left join bindings\n" + " on signatures.binding = bindings.oid\n" + " where fingerprint = ? and email = ? and sig_time = ?\n" + " and sig_digest = ?", + GPGSQL_ARG_STRING, fingerprint, GPGSQL_ARG_STRING, email, + GPGSQL_ARG_LONG_LONG, (long long) sig_time, + GPGSQL_ARG_STRING, sig_digest, + GPGSQL_ARG_END); + if (rc) + { + log_error (_("error reading TOFU database: %s\n"), sqlerr); + print_further_info ("checking existence"); + sqlite3_free (sqlerr); + rc = gpg_error (GPG_ERR_GENERAL); + } + else if (c > 1) + /* Duplicates! This should not happen. In particular, + because is the + primary key! */ + log_debug ("SIGNATURES DB contains duplicate records" + " ." + " Please report.\n", + fingerprint, email, (unsigned long) sig_time, + sig_digest, origin); + else if (c == 1) + { + if (DBG_TRUST) + log_debug ("Already observed the signature and binding" + " \n", + fingerprint, email, (unsigned long) sig_time, + sig_digest, origin); + } + else if (opt.dry_run) + { + log_info ("TOFU database update skipped due to --dry-run\n"); + } + else + /* This is the first time that we've seen this signature and + binding. Record it. */ + { + if (DBG_TRUST) + log_debug ("TOFU: Saving signature" + " \n", + fingerprint, email, sig_digest); + + log_assert (c == 0); + + rc = gpgsql_stepx + (dbs->db, &dbs->s.register_signature, NULL, NULL, &sqlerr, + "insert into signatures\n" + " (binding, sig_digest, origin, sig_time, time)\n" + " values\n" + " ((select oid from bindings\n" + " where fingerprint = ? and email = ?),\n" + " ?, ?, ?, ?);", + GPGSQL_ARG_STRING, fingerprint, GPGSQL_ARG_STRING, email, + GPGSQL_ARG_STRING, sig_digest, GPGSQL_ARG_STRING, origin, + GPGSQL_ARG_LONG_LONG, (long long) sig_time, + GPGSQL_ARG_LONG_LONG, (long long) now, + GPGSQL_ARG_END); + if (rc) + { + log_error (_("error updating TOFU database: %s\n"), sqlerr); + print_further_info ("insert signatures"); + sqlite3_free (sqlerr); + rc = gpg_error (GPG_ERR_GENERAL); + } + } + + xfree (email); + + if (rc) + break; + } + + leave: + if (rc) + rollback_transaction (ctrl); + else + rc = end_transaction (ctrl, 0); + + xfree (fingerprint); + xfree (sig_digest); + + return rc; +} + +gpg_error_t +tofu_register_encryption (ctrl_t ctrl, + PKT_public_key *pk, strlist_t user_id_list, + int may_ask) +{ + time_t now = gnupg_get_time (); + gpg_error_t rc = 0; + tofu_dbs_t dbs; + kbnode_t kb = NULL; + int free_user_id_list = 0; + char *fingerprint = NULL; + strlist_t user_id; + char *sqlerr = NULL; + int in_batch = 0; + + dbs = opendbs (ctrl); + if (! dbs) + { + rc = gpg_error (GPG_ERR_GENERAL); + log_error (_("error opening TOFU database: %s\n"), + gpg_strerror (rc)); + return rc; + } + + if (/* We need the key block to find the primary key. */ + ! pk_is_primary (pk) + /* We need the key block to find all user ids. */ + || ! user_id_list) + kb = get_pubkeyblock (ctrl, pk->keyid); + + /* Make sure PK is a primary key. */ + if (! pk_is_primary (pk)) + pk = kb->pkt->pkt.public_key; + + if (! user_id_list) + { + /* Use all non-revoked user ids. Do use expired user ids. */ + kbnode_t n = kb; + + while ((n = find_next_kbnode (n, PKT_USER_ID))) + { + PKT_user_id *uid = n->pkt->pkt.user_id; + + if (uid->flags.revoked) + continue; + + add_to_strlist (&user_id_list, uid->name); + } + + free_user_id_list = 1; + + if (! user_id_list) + log_info (_("WARNING: Encrypting to %s, which has no " + "non-revoked user ids\n"), + keystr (pk->keyid)); + } + + fingerprint = hexfingerprint (pk, NULL, 0); + if (!fingerprint) + { + rc = gpg_error_from_syserror (); + goto leave; + } + + tofu_begin_batch_update (ctrl); + in_batch = 1; + tofu_resume_batch_transaction (ctrl); + + for (user_id = user_id_list; user_id; user_id = user_id->next) + { + char *email = email_from_user_id (user_id->d); + strlist_t conflict_set = NULL; + enum tofu_policy policy; + + /* Make sure the binding exists and that we recognize any + conflicts. */ + int tl = get_trust (ctrl, pk, fingerprint, email, user_id->d, + may_ask, &policy, &conflict_set, now); + if (tl == _tofu_GET_TRUST_ERROR) + { + /* An error. */ + rc = gpg_error (GPG_ERR_GENERAL); + xfree (email); + goto leave; + } + + + /* If there is a conflict and MAY_ASK is true, we need to show + * the TOFU statistics for the current binding and the + * conflicting bindings. But, if we are not in batch mode, then + * they have already been printed (this is required to make sure + * the information is available to the caller before cpr_get is + * called). */ + if (policy == TOFU_POLICY_ASK && may_ask && opt.batch) + { + strlist_t iter; + + /* The conflict set should contain at least the current + * key. */ + log_assert (conflict_set); + + for (iter = conflict_set; iter; iter = iter->next) + show_statistics (dbs, iter->d, email, + TOFU_POLICY_ASK, NULL, 1, now); + } + + free_strlist (conflict_set); + + rc = gpgsql_stepx + (dbs->db, &dbs->s.register_encryption, NULL, NULL, &sqlerr, + "insert into encryptions\n" + " (binding, time)\n" + " values\n" + " ((select oid from bindings\n" + " where fingerprint = ? and email = ?),\n" + " ?);", + GPGSQL_ARG_STRING, fingerprint, GPGSQL_ARG_STRING, email, + GPGSQL_ARG_LONG_LONG, (long long) now, + GPGSQL_ARG_END); + if (rc) + { + log_error (_("error updating TOFU database: %s\n"), sqlerr); + print_further_info ("insert encryption"); + sqlite3_free (sqlerr); + rc = gpg_error (GPG_ERR_GENERAL); + } + + xfree (email); + } + + leave: + if (in_batch) + tofu_end_batch_update (ctrl); + + release_kbnode (kb); + if (free_user_id_list) + free_strlist (user_id_list); + xfree (fingerprint); + + return rc; +} + + +/* Combine a trust level returned from the TOFU trust model with a + trust level returned by the PGP trust model. This is primarily of + interest when the trust model is tofu+pgp (TM_TOFU_PGP). + + This function ors together the upper bits (the values not covered + by TRUST_MASK, i.e., TRUST_FLAG_REVOKED, etc.). */ +int +tofu_wot_trust_combine (int tofu_base, int wot_base) +{ + int tofu = tofu_base & TRUST_MASK; + int wot = wot_base & TRUST_MASK; + int upper = (tofu_base & ~TRUST_MASK) | (wot_base & ~TRUST_MASK); + + log_assert (tofu == TRUST_UNKNOWN + || tofu == TRUST_EXPIRED + || tofu == TRUST_UNDEFINED + || tofu == TRUST_NEVER + || tofu == TRUST_MARGINAL + || tofu == TRUST_FULLY + || tofu == TRUST_ULTIMATE); + log_assert (wot == TRUST_UNKNOWN + || wot == TRUST_EXPIRED + || wot == TRUST_UNDEFINED + || wot == TRUST_NEVER + || wot == TRUST_MARGINAL + || wot == TRUST_FULLY + || wot == TRUST_ULTIMATE); + + /* We first consider negative trust policys. These trump positive + trust policies. */ + if (tofu == TRUST_NEVER || wot == TRUST_NEVER) + /* TRUST_NEVER trumps everything else. */ + return upper | TRUST_NEVER; + if (tofu == TRUST_EXPIRED || wot == TRUST_EXPIRED) + /* TRUST_EXPIRED trumps everything but TRUST_NEVER. */ + return upper | TRUST_EXPIRED; + + /* Now we only have positive or neutral trust policies. We take + the max. */ + if (tofu == TRUST_ULTIMATE) + return upper | TRUST_ULTIMATE | TRUST_FLAG_TOFU_BASED; + if (wot == TRUST_ULTIMATE) + return upper | TRUST_ULTIMATE; + + if (tofu == TRUST_FULLY) + return upper | TRUST_FULLY | TRUST_FLAG_TOFU_BASED; + if (wot == TRUST_FULLY) + return upper | TRUST_FULLY; + + if (tofu == TRUST_MARGINAL) + return upper | TRUST_MARGINAL | TRUST_FLAG_TOFU_BASED; + if (wot == TRUST_MARGINAL) + return upper | TRUST_MARGINAL; + + if (tofu == TRUST_UNDEFINED) + return upper | TRUST_UNDEFINED | TRUST_FLAG_TOFU_BASED; + if (wot == TRUST_UNDEFINED) + return upper | TRUST_UNDEFINED; + + return upper | TRUST_UNKNOWN; +} + + +/* Write a "tfs" record for a --with-colons listing. */ +gpg_error_t +tofu_write_tfs_record (ctrl_t ctrl, estream_t fp, + PKT_public_key *pk, const char *user_id) +{ + time_t now = gnupg_get_time (); + gpg_error_t err = 0; + tofu_dbs_t dbs; + char *fingerprint; + char *email = NULL; + enum tofu_policy policy; + + if (!*user_id) + return 0; /* No TOFU stats possible for an empty ID. */ + + dbs = opendbs (ctrl); + if (!dbs) + { + err = gpg_error (GPG_ERR_GENERAL); + log_error (_("error opening TOFU database: %s\n"), gpg_strerror (err)); + return err; + } + + fingerprint = hexfingerprint (pk, NULL, 0); + if (!fingerprint) + { + err = gpg_error_from_syserror (); + goto leave; + } + email = email_from_user_id (user_id); + policy = get_policy (ctrl, dbs, pk, fingerprint, user_id, email, NULL, now); + + show_statistics (dbs, fingerprint, email, policy, fp, 0, now); + + leave: + xfree (email); + xfree (fingerprint); + return err; +} + + +/* Return the validity (TRUST_NEVER, etc.) of the bindings + , for each USER_ID in USER_ID_LIST. If + USER_ID_LIST->FLAG is set, then the id is considered to be expired. + + PK is the primary key packet. + + If MAY_ASK is 1 and the policy is TOFU_POLICY_ASK, then the user + will be prompted to choose a policy. If MAY_ASK is 0 and the + policy is TOFU_POLICY_ASK, then TRUST_UNKNOWN is returned. + + Returns TRUST_UNDEFINED if an error occurs. + + Fixme: eturn an error code + */ +int +tofu_get_validity (ctrl_t ctrl, PKT_public_key *pk, strlist_t user_id_list, + int may_ask) +{ + time_t now = gnupg_get_time (); + tofu_dbs_t dbs; + char *fingerprint = NULL; + strlist_t user_id; + int trust_level = TRUST_UNKNOWN; + int bindings = 0; + int bindings_valid = 0; + int need_warning = 0; + int had_conflict = 0; + + dbs = opendbs (ctrl); + if (! dbs) + { + log_error (_("error opening TOFU database: %s\n"), + gpg_strerror (GPG_ERR_GENERAL)); + return TRUST_UNDEFINED; + } + + fingerprint = hexfingerprint (pk, NULL, 0); + if (!fingerprint) + log_fatal ("%s: malloc failed\n", __func__); + + tofu_begin_batch_update (ctrl); + /* Start the batch transaction now. */ + tofu_resume_batch_transaction (ctrl); + + for (user_id = user_id_list; user_id; user_id = user_id->next, bindings ++) + { + char *email = email_from_user_id (user_id->d); + strlist_t conflict_set = NULL; + enum tofu_policy policy; + + /* Always call get_trust to make sure the binding is + registered. */ + int tl = get_trust (ctrl, pk, fingerprint, email, user_id->d, + may_ask, &policy, &conflict_set, now); + if (tl == _tofu_GET_TRUST_ERROR) + { + /* An error. */ + trust_level = TRUST_UNDEFINED; + xfree (email); + goto die; + } + + if (DBG_TRUST) + log_debug ("TOFU: validity for : %s%s.\n", + fingerprint, email, + trust_value_to_string (tl), + user_id->flags ? " (but expired)" : ""); + + if (user_id->flags) + tl = TRUST_EXPIRED; + + if (tl != TRUST_EXPIRED) + bindings_valid ++; + + if (may_ask && tl != TRUST_ULTIMATE && tl != TRUST_EXPIRED) + { + /* If policy is ask, then we already printed out the + * conflict information in ask_about_binding or will do so + * in a moment. */ + if (policy != TOFU_POLICY_ASK) + need_warning |= + show_statistics (dbs, fingerprint, email, policy, NULL, 0, now); + + /* If there is a conflict and MAY_ASK is true, we need to + * show the TOFU statistics for the current binding and the + * conflicting bindings. But, if we are not in batch mode, + * then they have already been printed (this is required to + * make sure the information is available to the caller + * before cpr_get is called). */ + if (policy == TOFU_POLICY_ASK && opt.batch) + { + strlist_t iter; + + /* The conflict set should contain at least the current + * key. */ + log_assert (conflict_set); + + had_conflict = 1; + for (iter = conflict_set; iter; iter = iter->next) + show_statistics (dbs, iter->d, email, + TOFU_POLICY_ASK, NULL, 1, now); + } + } + + free_strlist (conflict_set); + + if (tl == TRUST_NEVER) + trust_level = TRUST_NEVER; + else if (tl == TRUST_EXPIRED) + /* Ignore expired bindings in the trust calculation. */ + ; + else if (tl > trust_level) + { + /* The expected values: */ + log_assert (tl == TRUST_UNKNOWN || tl == TRUST_UNDEFINED + || tl == TRUST_MARGINAL || tl == TRUST_FULLY + || tl == TRUST_ULTIMATE); + + /* We assume the following ordering: */ + log_assert (TRUST_UNKNOWN < TRUST_UNDEFINED); + log_assert (TRUST_UNDEFINED < TRUST_MARGINAL); + log_assert (TRUST_MARGINAL < TRUST_FULLY); + log_assert (TRUST_FULLY < TRUST_ULTIMATE); + + trust_level = tl; + } + + xfree (email); + } + + if (need_warning && ! had_conflict) + show_warning (fingerprint, user_id_list); + + die: + tofu_end_batch_update (ctrl); + + xfree (fingerprint); + + if (bindings_valid == 0) + { + if (DBG_TRUST) + log_debug ("no (of %d) valid bindings." + " Can't get TOFU validity for this set of user ids.\n", + bindings); + return TRUST_NEVER; + } + + return trust_level; +} + +/* Set the policy for all non-revoked user ids in the keyblock KB to + POLICY. + + If no key is available with the specified key id, then this + function returns GPG_ERR_NO_PUBKEY. + + Returns 0 on success and an error code otherwise. */ +gpg_error_t +tofu_set_policy (ctrl_t ctrl, kbnode_t kb, enum tofu_policy policy) +{ + gpg_error_t err = 0; + time_t now = gnupg_get_time (); + tofu_dbs_t dbs; + PKT_public_key *pk; + char *fingerprint = NULL; + + log_assert (kb->pkt->pkttype == PKT_PUBLIC_KEY); + pk = kb->pkt->pkt.public_key; + + dbs = opendbs (ctrl); + if (! dbs) + { + log_error (_("error opening TOFU database: %s\n"), + gpg_strerror (GPG_ERR_GENERAL)); + return gpg_error (GPG_ERR_GENERAL); + } + + if (DBG_TRUST) + log_debug ("Setting TOFU policy for %s to %s\n", + keystr (pk->keyid), tofu_policy_str (policy)); + if (! pk_is_primary (pk)) + log_bug ("%s: Passed a subkey, but expecting a primary key.\n", __func__); + + fingerprint = hexfingerprint (pk, NULL, 0); + if (!fingerprint) + return gpg_error_from_syserror (); + + begin_transaction (ctrl, 0); + + for (; kb; kb = kb->next) + { + PKT_user_id *user_id; + char *email; + + if (kb->pkt->pkttype != PKT_USER_ID) + continue; + + user_id = kb->pkt->pkt.user_id; + if (user_id->flags.revoked) + /* Skip revoked user ids. (Don't skip expired user ids, the + expiry can be changed.) */ + continue; + + email = email_from_user_id (user_id->name); + + err = record_binding (dbs, fingerprint, email, user_id->name, + policy, TOFU_POLICY_NONE, NULL, 0, 1, now); + if (err) + { + log_error ("error setting policy for key %s, user id \"%s\": %s", + fingerprint, email, gpg_strerror (err)); + xfree (email); + break; + } + + xfree (email); + } + + if (err) + rollback_transaction (ctrl); + else + end_transaction (ctrl, 0); + + xfree (fingerprint); + return err; +} + +/* Return the TOFU policy for the specified binding in *POLICY. If no + policy has been set for the binding, sets *POLICY to + TOFU_POLICY_NONE. + + PK is a primary public key and USER_ID is a user id. + + Returns 0 on success and an error code otherwise. */ +gpg_error_t +tofu_get_policy (ctrl_t ctrl, PKT_public_key *pk, PKT_user_id *user_id, + enum tofu_policy *policy) +{ + time_t now = gnupg_get_time (); + tofu_dbs_t dbs; + char *fingerprint; + char *email; + + /* Make sure PK is a primary key. */ + log_assert (pk_is_primary (pk)); + + dbs = opendbs (ctrl); + if (! dbs) + { + log_error (_("error opening TOFU database: %s\n"), + gpg_strerror (GPG_ERR_GENERAL)); + return gpg_error (GPG_ERR_GENERAL); + } + + fingerprint = hexfingerprint (pk, NULL, 0); + if (!fingerprint) + return gpg_error_from_syserror (); + + email = email_from_user_id (user_id->name); + + *policy = get_policy (ctrl, dbs, pk, fingerprint, + user_id->name, email, NULL, now); + + xfree (email); + xfree (fingerprint); + if (*policy == _tofu_GET_POLICY_ERROR) + return gpg_error (GPG_ERR_GENERAL); + return 0; +} + +gpg_error_t +tofu_notice_key_changed (ctrl_t ctrl, kbnode_t kb) +{ + tofu_dbs_t dbs; + PKT_public_key *pk; + char *fingerprint; + char *sqlerr = NULL; + int rc; + + /* Make sure PK is a primary key. */ + setup_main_keyids (kb); + pk = kb->pkt->pkt.public_key; + log_assert (pk_is_primary (pk)); + + dbs = opendbs (ctrl); + if (! dbs) + { + log_error (_("error opening TOFU database: %s\n"), + gpg_strerror (GPG_ERR_GENERAL)); + return gpg_error (GPG_ERR_GENERAL); + } + + fingerprint = hexfingerprint (pk, NULL, 0); + if (!fingerprint) + return gpg_error_from_syserror (); + + rc = gpgsql_stepx (dbs->db, NULL, NULL, NULL, &sqlerr, + "update bindings set effective_policy = ?" + " where fingerprint = ?;", + GPGSQL_ARG_INT, (int) TOFU_POLICY_NONE, + GPGSQL_ARG_STRING, fingerprint, + GPGSQL_ARG_END); + xfree (fingerprint); + + if (rc == _tofu_GET_POLICY_ERROR) + return gpg_error (GPG_ERR_GENERAL); + return 0; +} diff --git a/g10/tofu.h b/g10/tofu.h new file mode 100644 index 0000000..7b1beea --- /dev/null +++ b/g10/tofu.h @@ -0,0 +1,142 @@ +/* tofu.h - TOFU trust model. + * Copyright (C) 2015 g10 Code GmbH + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#ifndef G10_TOFU_H +#define G10_TOFU_H + +#include + +/* For each binding, we have a trust policy. */ +enum tofu_policy + { + /* This value can be returned by tofu_get_policy to indicate that + there is no policy set for the specified binding. */ + TOFU_POLICY_NONE = 0, + + /* We made a default policy decision. This is only done if there + is no conflict with another binding (that is, the email address + is not part of another known key). The default policy is + configurable (and specified using: --tofu-default-policy). + + Note: when using the default policy, we save TOFU_POLICY_AUTO + with the binding, not the policy that was in effect. This way, + if the user invokes gpg again, but with a different value for + --tofu-default-policy, a different decision is made. */ + TOFU_POLICY_AUTO = 1, + + /* The user explicitly marked the binding as good. In this case, + we return TRUST_FULLY. */ + TOFU_POLICY_GOOD = 2, + + /* The user explicitly marked the binding as unknown. In this + case, we return TRUST_UNKNOWN. */ + TOFU_POLICY_UNKNOWN = 3, + + /* The user explicitly marked the binding as bad. In this case, + we always return TRUST_NEVER. */ + TOFU_POLICY_BAD = 4, + + /* The user deferred a definitive policy decision about the + binding (by selecting accept once or reject once). The next + time we see this binding, we should ask the user what to + do. */ + TOFU_POLICY_ASK = 5, + + + /* Private value used only within tofu.c. */ + _tofu_GET_POLICY_ERROR = 100 + }; + + +/* Return a string representation of a trust policy. Returns "???" if + POLICY is not valid. */ +const char *tofu_policy_str (enum tofu_policy policy); + +/* Convert a binding policy (e.g., TOFU_POLICY_BAD) to a trust level + (e.g., TRUST_BAD) in light of the current configuration. */ +int tofu_policy_to_trust_level (enum tofu_policy policy); + +/* Register the bindings , for each USER_ID in + USER_ID_LIST, and the signature described by SIGS_DIGEST and + SIG_TIME, which it generated. Origin describes where the signed + data came from, e.g., "email:claws" (default: "unknown"). Note: + this function does not interact with the user, If there is a + conflict, or if the binding's policy is ask, the actual interaction + is deferred until tofu_get_validity is called. Set the string + list FLAG to indicate that a specified user id is expired. This + function returns 0 on success and an error code on failure. */ +gpg_error_t tofu_register_signature (ctrl_t ctrl, PKT_public_key *pk, + strlist_t user_id_list, + const byte *sigs_digest, + int sigs_digest_len, + time_t sig_time, const char *origin); + +/* Note that an encrypted mail was sent to , for each + USER_ID in USER_ID_LIST. (If USER_ID_LIST is NULL, then all + non-revoked user ids associated with PK are used.) If MAY_ASK is + set, then may interact with the user to resolve a TOFU + conflict. */ +gpg_error_t tofu_register_encryption (ctrl_t ctrl, + PKT_public_key *pk, + strlist_t user_id_list, + int may_ask); + +/* Combine a trust level returned from the TOFU trust model with a + trust level returned by the PGP trust model. This is primarily of + interest when the trust model is tofu+pgp (TM_TOFU_PGP). */ +int tofu_wot_trust_combine (int tofu, int wot); + +/* Write a "tfs" record for a --with-colons listing. */ +gpg_error_t tofu_write_tfs_record (ctrl_t ctrl, estream_t fp, + PKT_public_key *pk, const char *user_id); + +/* Determine the validity (TRUST_NEVER, etc.) of the binding . If MAY_ASK is 1, then this function may interact with + the user. If not, TRUST_UNKNOWN is returned if an interaction is + required. Set the string list FLAGS to indicate that a specified + user id is expired. If an error occurs, TRUST_UNDEFINED is + returned. */ +int tofu_get_validity (ctrl_t ctrl, + PKT_public_key *pk, strlist_t user_id_list, + int may_ask); + +/* Set the policy for all non-revoked user ids in the keyblock KB to + POLICY. */ +gpg_error_t tofu_set_policy (ctrl_t ctrl, kbnode_t kb, enum tofu_policy policy); + +/* Return the TOFU policy for the specified binding in *POLICY. */ +gpg_error_t tofu_get_policy (ctrl_t ctrl, + PKT_public_key *pk, PKT_user_id *user_id, + enum tofu_policy *policy); + +/* When doing a lot of DB activities (in particular, when listing + keys), this causes the DB to enter batch mode, which can + significantly speed up operations. */ +void tofu_begin_batch_update (ctrl_t ctrl); +void tofu_end_batch_update (ctrl_t ctrl); + +/* Release all of the resources associated with a DB meta-handle. */ +void tofu_closedbs (ctrl_t ctrl); + +/* Whenever a key is modified (e.g., a user id is added or revoked, a + * new signature, etc.), this function should be called to cause TOFU + * to update its world view. */ +gpg_error_t tofu_notice_key_changed (ctrl_t ctrl, kbnode_t kb); + +#endif /*G10_TOFU_H*/ diff --git a/g10/trust.c b/g10/trust.c new file mode 100644 index 0000000..9749bd7 --- /dev/null +++ b/g10/trust.c @@ -0,0 +1,432 @@ +/* trust.c - High level trust functions + * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, + * 2008, 2012 Free Software Foundation, Inc. + * Copyright (C) 2014 Werner Koch + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include +#include +#include +#include + +#include "gpg.h" +#include "keydb.h" +#include "../common/util.h" +#include "options.h" +#include "packet.h" +#include "main.h" +#include "../common/i18n.h" +#include "trustdb.h" +#include "../common/host2net.h" + + +/* Return true if key is disabled. Note that this is usually used via + the pk_is_disabled macro. */ +int +cache_disabled_value (ctrl_t ctrl, PKT_public_key *pk) +{ +#ifdef NO_TRUST_MODELS + (void)pk; + return 0; +#else + return tdb_cache_disabled_value (ctrl, pk); +#endif +} + + +void +register_trusted_key (const char *string) +{ +#ifdef NO_TRUST_MODELS + (void)string; +#else + + /* Some users have conf files with entries like + * trusted-key 0x1234567812345678 # foo + * That is obviously wrong. Before fixing bug#1206 trailing garbage + * on a key specification if was ignored. We detect the above use case + * here and cut off the junk-looking-like-a comment. */ + if (strchr (string, '#')) + { + char *buf; + + buf = xtrystrdup (string); + if (buf) + { + *strchr (buf, '#') = 0; + tdb_register_trusted_key (buf); + xfree (buf); + return; + } + } + + tdb_register_trusted_key (string); +#endif +} + + + +/* + * This function returns a letter for a trust value. Trust flags + * are ignored. + */ +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 '?'; + } +} + + +/* 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")) + return TRUST_UNDEFINED; + else if (!ascii_strcasecmp (str, "never")) + return TRUST_NEVER; + else if (!ascii_strcasecmp (str, "marginal")) + return TRUST_MARGINAL; + else if (!ascii_strcasecmp (str, "full")) + return TRUST_FULLY; + else if (!ascii_strcasecmp(str, "ultimate")) + return TRUST_ULTIMATE; + else + return -1; +} + + +const char * +uid_trust_string_fixed (ctrl_t ctrl, PKT_public_key *key, PKT_user_id *uid) +{ + if (!key && !uid) + { + /* TRANSLATORS: 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. */ + return _("10 translator see trust.c:uid_trust_string_fixed"); + } + else if(uid->flags.revoked || (key && key->flags.revoked)) + return _("[ revoked]"); + else if(uid->flags.expired) + return _("[ expired]"); + else if(key) + { + switch (get_validity (ctrl, NULL, key, uid, NULL, 0) & TRUST_MASK) + { + case TRUST_UNKNOWN: return _("[ unknown]"); + case TRUST_EXPIRED: return _("[ expired]"); + case TRUST_UNDEFINED: return _("[ undef ]"); + case TRUST_NEVER: return _("[ never ]"); + case TRUST_MARGINAL: return _("[marginal]"); + case TRUST_FULLY: return _("[ full ]"); + case TRUST_ULTIMATE: return _("[ultimate]"); + } + } + + return "err"; +} + + + +/* + * Return the assigned ownertrust value for the given public key. + * The key should be the primary key. + */ +unsigned int +get_ownertrust (ctrl_t ctrl, PKT_public_key *pk) +{ +#ifdef NO_TRUST_MODELS + (void)pk; + return TRUST_UNKNOWN; +#else + return tdb_get_ownertrust (ctrl, pk, 0); +#endif +} + + +/* + * Same as get_ownertrust but this takes the minimum ownertrust value + * into account, and will bump up the value as needed. NO_CREATE + * inhibits creation of a trustdb it that does not yet exists. + */ +static int +get_ownertrust_with_min (ctrl_t ctrl, PKT_public_key *pk, int no_create) +{ +#ifdef NO_TRUST_MODELS + (void)pk; + return TRUST_UNKNOWN; +#else + unsigned int otrust, otrust_min; + + /* Shortcut instead of doing the same twice in the two tdb_get + * functions: If the caller asked not to create a trustdb we call + * init_trustdb directly and allow it to fail with an error code for + * a non-existing trustdb. */ + if (no_create && init_trustdb (ctrl, 1)) + return TRUST_UNKNOWN; + + otrust = (tdb_get_ownertrust (ctrl, pk, no_create) & TRUST_MASK); + otrust_min = tdb_get_min_ownertrust (ctrl, pk, no_create); + 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; +#endif +} + + +/* + * Same as get_ownertrust but return a trust letter instead of an + * value. This takes the minimum ownertrust value into account. If + * NO_CREATE is set, no efforts for creating a trustdb will be taken. + */ +int +get_ownertrust_info (ctrl_t ctrl, PKT_public_key *pk, int no_create) +{ + return trust_letter (get_ownertrust_with_min (ctrl, pk, no_create)); +} + + +/* + * Same as get_ownertrust but return a trust string instead of an + * value. This takes the minimum ownertrust value into account. If + * NO_CREATE is set, no efforts for creating a trustdb will be taken. + */ +const char * +get_ownertrust_string (ctrl_t ctrl, PKT_public_key *pk, int no_create) +{ + return trust_value_to_string (get_ownertrust_with_min (ctrl, pk, no_create)); +} + + +/* + * Set the trust value of the given public key to the new value. + * The key should be a primary one. + */ +void +update_ownertrust (ctrl_t ctrl, PKT_public_key *pk, unsigned int new_trust) +{ +#ifdef NO_TRUST_MODELS + (void)pk; + (void)new_trust; +#else + u32 keyid[2]; + + tdb_update_ownertrust (ctrl, pk, new_trust, 0); + keyid_from_pk (pk, keyid); + tdb_update_utk (keyid, (new_trust & TRUST_ULTIMATE)); +#endif +} + + +int +clear_ownertrusts (ctrl_t ctrl, PKT_public_key *pk) +{ +#ifdef NO_TRUST_MODELS + (void)pk; + return 0; +#else + return tdb_clear_ownertrusts (ctrl, pk); +#endif +} + + +void +revalidation_mark (ctrl_t ctrl) +{ +#ifndef NO_TRUST_MODELS + tdb_revalidation_mark (ctrl); +#endif +} + + +void +check_trustdb_stale (ctrl_t ctrl) +{ +#ifndef NO_TRUST_MODELS + tdb_check_trustdb_stale (ctrl); +#else + (void)ctrl; +#endif +} + + +void +check_or_update_trustdb (ctrl_t ctrl) +{ +#ifndef NO_TRUST_MODELS + tdb_check_or_update (ctrl); +#else + (void)ctrl; +#endif +} + + +/* + * Return the validity information for KB/PK (at least one must be + * non-NULL). If the namehash is not NULL, the validity of the + * corresponding user ID is returned, otherwise, a reasonable value + * for the entire key is returned. + */ +unsigned int +get_validity (ctrl_t ctrl, kbnode_t kb, PKT_public_key *pk, PKT_user_id *uid, + PKT_signature *sig, int may_ask) +{ + int rc; + unsigned int validity; + u32 kid[2]; + PKT_public_key *main_pk; + + if (kb && pk) + log_assert (keyid_cmp (pk_main_keyid (pk), + pk_main_keyid (kb->pkt->pkt.public_key)) == 0); + + if (! pk) + { + log_assert (kb); + pk = kb->pkt->pkt.public_key; + } + + if (uid) + namehash_from_uid (uid); + + 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. */ + if (kb) + main_pk = kb->pkt->pkt.public_key; + else + { + main_pk = xmalloc_clear (sizeof *main_pk); + rc = get_pubkey (ctrl, 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), gpg_strerror (rc)); + xfree (tempkeystr); + validity = TRUST_UNKNOWN; + goto leave; + } + } + } + else + main_pk = pk; + +#ifdef NO_TRUST_MODELS + validity = TRUST_UNKNOWN; +#else + validity = tdb_get_validity_core (ctrl, kb, pk, uid, main_pk, sig, may_ask); +#endif + + leave: + /* Set some flags direct from the key */ + if (main_pk->flags.revoked) + validity |= TRUST_FLAG_REVOKED; + if (main_pk != pk && pk->flags.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_FLAG_PENDING_CHECK)) + | TRUST_EXPIRED); + + if (main_pk != pk && !kb) + free_public_key (main_pk); + return validity; +} + + +int +get_validity_info (ctrl_t ctrl, kbnode_t kb, PKT_public_key *pk, + PKT_user_id *uid) +{ + int trustlevel; + + if (kb && pk) + log_assert (keyid_cmp (pk_main_keyid (pk), + pk_main_keyid (kb->pkt->pkt.public_key)) == 0); + + if (! pk && kb) + pk = kb->pkt->pkt.public_key; + if (!pk) + return '?'; /* Just in case a NULL PK is passed. */ + + trustlevel = get_validity (ctrl, kb, pk, uid, NULL, 0); + if ((trustlevel & TRUST_FLAG_REVOKED)) + return 'r'; + return trust_letter (trustlevel); +} + + +const char * +get_validity_string (ctrl_t ctrl, PKT_public_key *pk, PKT_user_id *uid) +{ + int trustlevel; + + if (!pk) + return "err"; /* Just in case a NULL PK is passed. */ + + trustlevel = get_validity (ctrl, NULL, pk, uid, NULL, 0); + if ((trustlevel & TRUST_FLAG_REVOKED)) + return _("revoked"); + return trust_value_to_string (trustlevel); +} diff --git a/g10/trustdb.c b/g10/trustdb.c new file mode 100644 index 0000000..1b6da96 --- /dev/null +++ b/g10/trustdb.c @@ -0,0 +1,2343 @@ +/* trustdb.c + * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, + * 2008, 2012 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include +#include +#include +#include + +#include "gpg.h" +#include "../common/status.h" +#include "../common/iobuf.h" +#include "../regexp/jimregexp.h" +#include "keydb.h" +#include "../common/util.h" +#include "options.h" +#include "packet.h" +#include "main.h" +#include "../common/mbox-util.h" +#include "../common/i18n.h" +#include "tdbio.h" +#include "trustdb.h" +#include "tofu.h" +#include "key-clean.h" + +static void write_record (ctrl_t ctrl, TRUSTREC *rec); +static void do_sync(void); + + +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; + int no_trustdb; +} 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 (ctrl_t ctrl, 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); + } +} + +#define KEY_HASH_TABLE_SIZE 1024 + +/* + * For fast keylook up we need a hash table. Each byte of a KeyID + * 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 KEY_HASH_TABLE_SIZE key items. + * Possible optimization: Do not use key_items but other hash_table when the + * duplicates lists get too large. + */ +static KeyHashTable +new_key_hash_table (void) +{ + struct key_item **tbl; + + tbl = xmalloc_clear (KEY_HASH_TABLE_SIZE * sizeof *tbl); + return tbl; +} + +static void +release_key_hash_table (KeyHashTable tbl) +{ + int i; + + if (!tbl) + return; + for (i=0; i < KEY_HASH_TABLE_SIZE; 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] % KEY_HASH_TABLE_SIZE)]; 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) +{ + int i = kid[1] % KEY_HASH_TABLE_SIZE; + struct key_item *k, *kk; + + for (k = tbl[i]; 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[i]; + tbl[i] = 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. + */ +static void +tdb_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 +tdb_register_trusted_key (const char *string) +{ + gpg_error_t err; + KEYDB_SEARCH_DESC desc; + u32 kid[2]; + + err = classify_user_id (string, &desc, 1); + if (!err) + { + if (desc.mode == KEYDB_SEARCH_MODE_LONG_KID) + { + tdb_register_trusted_keyid (desc.u.kid); + return; + } + if (desc.mode == KEYDB_SEARCH_MODE_FPR + || desc.mode == KEYDB_SEARCH_MODE_FPR20) + { + kid[0] = buf32_to_u32 (desc.u.fpr+12); + kid[1] = buf32_to_u32 (desc.u.fpr+16); + tdb_register_trusted_keyid (kid); + return; + } + } + log_error (_("'%s' is not a valid long keyID\n"), string ); +} + + +/* + * Helper to add a key to the global list of ultimately trusted keys. + * Returns: true = inserted, false = already in list. + */ +static int +add_utk (u32 *kid) +{ + struct key_item *k; + + if (tdb_keyid_is_utk (kid)) + 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; +} + + +/* Add/remove KID to/from the list of ultimately trusted keys. */ +void +tdb_update_utk (u32 *kid, int add) +{ + struct key_item *k, *k_prev; + + k_prev = NULL; + for (k = utk_list; k; k = k->next) + if (k->kid[0] == kid[0] && k->kid[1] == kid[1]) + break; + else + k_prev = k; + + if (add) + { + if (!k) + { + 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)); + } + } + else + { + if (k) + { + if (k_prev) + k_prev->next = k->next; + else + utk_list = NULL; + + xfree (k->trust_regexp); + xfree (k); + } + } +} + + +/**************** + * Verify that all our secret keys are usable and put them into the utk_list. + */ +static void +verify_own_keys (ctrl_t ctrl) +{ + TRUSTREC rec; + ulong recnum; + int rc; + struct key_item *k, *k2; + int need_revalidation = 0; + + if (utk_list) + return; /* Has already been run. */ + + /* 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 bytes + * 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 (ctrl, fpr, fprlen, kid); + if (!add_utk (kid)) + log_info(_("key %s occurs more than once in the trustdb\n"), + keystr(kid)); + else if ((rec.r.trust.flags & 1)) + { + /* Record marked as inserted via --trusted-key. Is this + * still the case? */ + for (k2 = user_utk_list; k2; k2 = k2->next) + if (k2->kid[0] == kid[0] && k2->kid[1] == kid[1]) + break; + if (!k2) /* No - clear the flag. */ + { + if (DBG_TRUST) + log_debug ("clearing former --trusted-key %s\n", + keystr (kid)); + rec.r.trust.ownertrust = TRUST_UNKNOWN; + rec.r.trust.flags &= ~(rec.r.trust.flags & 1); + write_record (ctrl, &rec); + need_revalidation = 1; + } + } + } + } + + if (need_revalidation) + { + tdb_revalidation_mark (ctrl); + do_sync (); + } + + /* 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_with_ldap_fallback (ctrl, &pk, k->kid); + if (rc) + log_info(_("key %s: no public key for trusted key - skipped\n"), + keystr(k->kid)); + else + { + tdb_update_ownertrust + (ctrl, &pk, ((tdb_get_ownertrust (ctrl, &pk, 0) & ~TRUST_MASK) + | TRUST_ULTIMATE ), 1); + release_public_key_parts (&pk); + } + + if (!opt.quiet) + 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; +} + +/* Returns whether KID is on the list of ultimately trusted keys. */ +int +tdb_keyid_is_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 1; + + return 0; +} + +/* Return the list of ultimately trusted keys. */ +struct key_item * +tdb_utks (void) +{ + return utk_list; +} + +/********************************************* + *********** 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, gpg_strerror (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 (ctrl_t ctrl, TRUSTREC *rec) +{ + int rc = tdbio_write_record (ctrl, rec); + if (rc) + { + log_error(_("trust record %lu, type %d: write failed: %s\n"), + rec->recnum, rec->rectype, gpg_strerror (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"), gpg_strerror (rc) ); + g10_exit(2); + } +} + +const char * +trust_model_string (int model) +{ + switch (model) + { + case TM_CLASSIC: return "classic"; + case TM_PGP: return "pgp"; + case TM_EXTERNAL: return "external"; + case TM_TOFU: return "tofu"; + case TM_TOFU_PGP: return "tofu+pgp"; + 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 +how_to_fix_the_trustdb () +{ + const char *name = trustdb_args.dbname; + + if (!name) + name = "trustdb.gpg"; + + log_info (_("You may try to re-create the trustdb using the commands:\n")); + log_info (" cd %s\n", default_homedir ()); + log_info (" %s --export-ownertrust > otrust.tmp\n", GPG_NAME); +#ifdef HAVE_W32_SYSTEM + log_info (" del %s\n", name); +#else + log_info (" rm %s\n", name); +#endif + log_info (" %s --import-ownertrust < otrust.tmp\n", GPG_NAME); + log_info (_("If that does not work, please consult the manual\n")); +} + + +/* Initialize the trustdb. With NO_CREATE set a missing trustdb is + * not an error and the function won't terminate the process on error; + * in that case 0 is returned if there is a trustdb or an error code + * if no trustdb is available. */ +gpg_error_t +init_trustdb (ctrl_t ctrl, int no_create) +{ + int level = trustdb_args.level; + const char* dbname = trustdb_args.dbname; + + if( trustdb_args.init ) + return 0; + + trustdb_args.init = 1; + + if(level==0 || level==1) + { + int rc = tdbio_set_dbname (ctrl, dbname, (!no_create && level), + &trustdb_args.no_trustdb); + if (no_create && trustdb_args.no_trustdb) + { + /* No trustdb found and the caller asked us not to create + * it. Return an error and set the initialization state + * back so that we always test for an existing trustdb. */ + trustdb_args.init = 0; + return gpg_error (GPG_ERR_ENOENT); + } + if (rc) + log_fatal("can't init trustdb: %s\n", gpg_strerror (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_TOFU_PGP + && opt.trust_model != TM_TOFU + && 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 (opt.trust_model)); + } + + if (opt.trust_model==TM_PGP || opt.trust_model==TM_CLASSIC + || opt.trust_model == TM_TOFU || opt.trust_model == TM_TOFU_PGP) + { + /* Verify the list of ultimately trusted keys and move the + --trusted-keys list there as well. */ + if(level==1) + verify_own_keys (ctrl); + + if(!tdbio_db_matches_options()) + pending_check_trustdb=1; + } + + return 0; +} + + +/* Check whether we have a trust database, initializing it if + necessary if the trust model is not 'always trust'. Returns true + if we do have a usable trust database. */ +int +have_trustdb (ctrl_t ctrl) +{ + return !init_trustdb (ctrl, opt.trust_model == TM_ALWAYS); +} + + +/**************** + * 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 (ctrl_t ctrl) +{ + init_trustdb (ctrl, 0); + if (opt.trust_model == TM_PGP || opt.trust_model == TM_CLASSIC + || opt.trust_model == TM_TOFU_PGP || opt.trust_model == TM_TOFU) + { + 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 (ctrl, 0); + } + else + log_info (_("no need for a trustdb check with '%s' trust model\n"), + trust_model_string(opt.trust_model)); +} + + +/* + * Recreate the WoT. + */ +void +update_trustdb (ctrl_t ctrl) +{ + init_trustdb (ctrl, 0); + if (opt.trust_model == TM_PGP || opt.trust_model == TM_CLASSIC + || opt.trust_model == TM_TOFU_PGP || opt.trust_model == TM_TOFU) + validate_keys (ctrl, 1); + else + log_info (_("no need for a trustdb update with '%s' trust model\n"), + trust_model_string(opt.trust_model)); +} + +void +tdb_revalidation_mark (ctrl_t ctrl) +{ + init_trustdb (ctrl, 0); + if (trustdb_args.no_trustdb && opt.trust_model == TM_ALWAYS) + return; + + /* 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 (ctrl, 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 +tdb_check_or_update (ctrl_t ctrl) +{ + if (trustdb_pending_check ()) + { + if (opt.interactive) + update_trustdb (ctrl); + else if (!opt.no_auto_check_trustdb) + check_trustdb (ctrl); + } +} + +void +read_trust_options (ctrl_t ctrl, + byte *trust_model, ulong *created, ulong *nextcheck, + byte *marginals, byte *completes, byte *cert_depth, + byte *min_cert_level) +{ + TRUSTREC opts; + + init_trustdb (ctrl, 0); + if (trustdb_args.no_trustdb && opt.trust_model == TM_ALWAYS) + memset (&opts, 0, sizeof opts); + else + 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; + if(min_cert_level) + *min_cert_level=opts.r.ver.min_cert_level; +} + +/*********************************************** + *********** Ownertrust et al. **************** + ***********************************************/ + +static int +read_trust_record (ctrl_t ctrl, PKT_public_key *pk, TRUSTREC *rec) +{ + int rc; + + init_trustdb (ctrl, 0); + rc = tdbio_search_trust_bypk (ctrl, pk, rec); + if (rc) + { + if (gpg_err_code (rc) != GPG_ERR_NOT_FOUND) + log_error ("trustdb: searching trust record failed: %s\n", + gpg_strerror (rc)); + return rc; + } + + if (rec->rectype != RECTYPE_TRUST) + { + log_error ("trustdb: record %lu is not a trust record\n", + rec->recnum); + return GPG_ERR_TRUSTDB; + } + + return 0; +} + + +/* + * Return the assigned ownertrust value for the given public key. The + * key should be the primary key. If NO_CREATE is set a missing + * trustdb will not be created. This comes for example handy when we + * want to print status lines (DECRYPTION_KEY) which carry ownertrust + * values but we usually use --always-trust. + */ +unsigned int +tdb_get_ownertrust (ctrl_t ctrl, PKT_public_key *pk, int no_create) +{ + TRUSTREC rec; + gpg_error_t err; + + if (trustdb_args.no_trustdb && opt.trust_model == TM_ALWAYS) + return TRUST_UNKNOWN; + + /* If the caller asked not to create a trustdb we call init_trustdb + * directly and allow it to fail with an error code for a + * non-existing trustdb. */ + if (no_create && init_trustdb (ctrl, 1)) + return TRUST_UNKNOWN; + + err = read_trust_record (ctrl, pk, &rec); + if (gpg_err_code (err) == GPG_ERR_NOT_FOUND) + return TRUST_UNKNOWN; /* no record yet */ + if (err) + { + tdbio_invalid (); + return TRUST_UNKNOWN; /* actually never reached */ + } + + return rec.r.trust.ownertrust; +} + + +unsigned int +tdb_get_min_ownertrust (ctrl_t ctrl, PKT_public_key *pk, int no_create) +{ + TRUSTREC rec; + gpg_error_t err; + + if (trustdb_args.no_trustdb && opt.trust_model == TM_ALWAYS) + return TRUST_UNKNOWN; + + /* If the caller asked not to create a trustdb we call init_trustdb + * directly and allow it to fail with an error code for a + * non-existing trustdb. */ + if (no_create && init_trustdb (ctrl, 1)) + return TRUST_UNKNOWN; + + err = read_trust_record (ctrl, pk, &rec); + if (gpg_err_code (err) == GPG_ERR_NOT_FOUND) + return TRUST_UNKNOWN; /* no record yet */ + if (err) + { + tdbio_invalid (); + return TRUST_UNKNOWN; /* actually never reached */ + } + + return rec.r.trust.min_ownertrust; +} + + +/* + * Set the trust value of the given public key to the new value. + * The key should be a primary one. + */ +void +tdb_update_ownertrust (ctrl_t ctrl, PKT_public_key *pk, unsigned int new_trust, + int as_trusted_key) +{ + TRUSTREC rec; + gpg_error_t err; + + if (trustdb_args.no_trustdb && opt.trust_model == TM_ALWAYS) + return; + + err = read_trust_record (ctrl, pk, &rec); + if (!err) + { + if (DBG_TRUST) + log_debug ("update ownertrust from %u to %u%s\n", + (unsigned int)rec.r.trust.ownertrust, new_trust, + as_trusted_key? " via --trusted-key":""); + if (rec.r.trust.ownertrust != new_trust) + { + rec.r.trust.ownertrust = new_trust; + /* Clear or set the trusted key flag if the new value is + * ultimate. This is required so that we know which keys + * have been added by --trusted-keys. */ + if ((rec.r.trust.ownertrust & TRUST_MASK) == TRUST_ULTIMATE) + { + if (as_trusted_key) + rec.r.trust.flags |= 1; + else + rec.r.trust.flags &= ~(rec.r.trust.flags & 1); + } + else + rec.r.trust.flags &= ~(rec.r.trust.flags & 1); + write_record (ctrl, &rec); + tdb_revalidation_mark (ctrl); + do_sync (); + } + } + else if (gpg_err_code (err) == GPG_ERR_NOT_FOUND) + { /* no record yet - create a new one */ + size_t dummy; + + if (DBG_TRUST) + log_debug ("insert ownertrust %u%s\n", new_trust, + as_trusted_key? " via --trusted-key":""); + + memset (&rec, 0, sizeof rec); + rec.recnum = tdbio_new_recnum (ctrl); + rec.rectype = RECTYPE_TRUST; + fingerprint_from_pk (pk, rec.r.trust.fingerprint, &dummy); + rec.r.trust.ownertrust = new_trust; + if ((rec.r.trust.ownertrust & TRUST_MASK) == TRUST_ULTIMATE + && as_trusted_key) + rec.r.trust.flags = 1; + write_record (ctrl, &rec); + tdb_revalidation_mark (ctrl); + do_sync (); + } + else + { + tdbio_invalid (); + } +} + +static void +update_min_ownertrust (ctrl_t ctrl, u32 *kid, unsigned int new_trust) +{ + PKT_public_key *pk; + TRUSTREC rec; + gpg_error_t err; + + if (trustdb_args.no_trustdb && opt.trust_model == TM_ALWAYS) + return; + + pk = xmalloc_clear (sizeof *pk); + err = get_pubkey (ctrl, pk, kid); + if (err) + { + log_error (_("public key %s not found: %s\n"), + keystr (kid), gpg_strerror (err)); + xfree (pk); + return; + } + + err = read_trust_record (ctrl, pk, &rec); + if (!err) + { + 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 (ctrl, &rec); + tdb_revalidation_mark (ctrl); + do_sync (); + } + } + else if (gpg_err_code (err) == GPG_ERR_NOT_FOUND) + { /* 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 (ctrl); + rec.rectype = RECTYPE_TRUST; + fingerprint_from_pk (pk, rec.r.trust.fingerprint, &dummy); + rec.r.trust.min_ownertrust = new_trust; + write_record (ctrl, &rec); + tdb_revalidation_mark (ctrl); + do_sync (); + } + else + { + tdbio_invalid (); + } + + free_public_key (pk); +} + + +/* + * Clear the ownertrust and min_ownertrust values. + * + * Return: True if a change actually happened. + */ +int +tdb_clear_ownertrusts (ctrl_t ctrl, PKT_public_key *pk) +{ + TRUSTREC rec; + gpg_error_t err; + + init_trustdb (ctrl, 0); + + if (trustdb_args.no_trustdb && opt.trust_model == TM_ALWAYS) + return 0; + + err = read_trust_record (ctrl, pk, &rec); + if (!err) + { + 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 (ctrl, &rec); + tdb_revalidation_mark (ctrl); + do_sync (); + return 1; + } + } + else if (gpg_err_code (err) != GPG_ERR_NOT_FOUND) + { + tdbio_invalid (); + } + return 0; +} + +/* + * Note: Caller has to do a sync + */ +static void +update_validity (ctrl_t ctrl, PKT_public_key *pk, PKT_user_id *uid, + int depth, int validity) +{ + TRUSTREC trec, vrec; + gpg_error_t err; + ulong recno; + + namehash_from_uid(uid); + + err = read_trust_record (ctrl, pk, &trec); + if (err && gpg_err_code (err) != GPG_ERR_NOT_FOUND) + { + tdbio_invalid (); + return; + } + if (gpg_err_code (err) == GPG_ERR_NOT_FOUND) + { + /* No record yet - create a new one. */ + size_t dummy; + + memset (&trec, 0, sizeof trec); + trec.recnum = tdbio_new_recnum (ctrl); + 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 (ctrl); + 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 (ctrl, &vrec); + trec.r.trust.depth = depth; + write_record (ctrl, &trec); +} + + +/*********************************************** + ********* Query trustdb values ************** + ***********************************************/ + +/* Return true if key is disabled. Note that this is usually used via + the pk_is_disabled macro. */ +int +tdb_cache_disabled_value (ctrl_t ctrl, PKT_public_key *pk) +{ + gpg_error_t err; + TRUSTREC trec; + int disabled = 0; + + if (pk->flags.disabled_valid) + return pk->flags.disabled; + + init_trustdb (ctrl, 0); + + if (trustdb_args.no_trustdb) + return 0; /* No trustdb => not disabled. */ + + err = read_trust_record (ctrl, pk, &trec); + if (err && gpg_err_code (err) != GPG_ERR_NOT_FOUND) + { + tdbio_invalid (); + goto leave; + } + if (gpg_err_code (err) == GPG_ERR_NOT_FOUND) + { + /* 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 */ + pk->flags.disabled = disabled; + pk->flags.disabled_valid = 1; + + leave: + return disabled; +} + + +void +tdb_check_trustdb_stale (ctrl_t ctrl) +{ + static int did_nextcheck=0; + + init_trustdb (ctrl, 0); + + if (trustdb_args.no_trustdb) + return; /* No trustdb => can't be stale. */ + + if (!did_nextcheck + && (opt.trust_model == TM_PGP || opt.trust_model == TM_CLASSIC + || opt.trust_model == TM_TOFU_PGP || opt.trust_model == TM_TOFU)) + { + ulong scheduled; + + did_nextcheck = 1; + scheduled = tdbio_read_nextcheck (); + if ((scheduled && scheduled <= make_timestamp ()) + || pending_check_trustdb) + { + if (opt.no_auto_check_trustdb) + { + pending_check_trustdb = 1; + if (!opt.quiet) + log_info (_("please do a --check-trustdb\n")); + } + else + { + if (!opt.quiet) + log_info (_("checking the trustdb\n")); + validate_keys (ctrl, 0); + } + } + } +} + +/* + * Return the validity information for KB/PK (at least one of them + * must be non-NULL). This is the core of get_validity. If SIG is + * not NULL, then the trust is being evaluated in the context of the + * provided signature. This is used by the TOFU code to record + * statistics. + */ +unsigned int +tdb_get_validity_core (ctrl_t ctrl, + kbnode_t kb, + PKT_public_key *pk, PKT_user_id *uid, + PKT_public_key *main_pk, + PKT_signature *sig, + int may_ask) +{ + TRUSTREC trec, vrec; + gpg_error_t err = 0; + ulong recno; +#ifdef USE_TOFU + unsigned int tofu_validity = TRUST_UNKNOWN; + int free_kb = 0; +#endif + unsigned int validity = TRUST_UNKNOWN; + + if (kb && pk) + log_assert (keyid_cmp (pk_main_keyid (pk), + pk_main_keyid (kb->pkt->pkt.public_key)) == 0); + + if (! pk) + { + log_assert (kb); + pk = kb->pkt->pkt.public_key; + } + +#ifndef USE_TOFU + (void)sig; + (void)may_ask; +#endif + + init_trustdb (ctrl, 0); + + /* If we have no trustdb (which also means it has not been created) + and the trust-model is always, we don't know the validity - + return immediately. If we won't do that the tdbio code would try + to open the trustdb and run into a fatal error. */ + if (trustdb_args.no_trustdb && opt.trust_model == TM_ALWAYS) + return TRUST_UNKNOWN; + + check_trustdb_stale (ctrl); + + 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 = tdb_get_ownertrust (ctrl, main_pk, 0); + goto leave; + } + +#ifdef USE_TOFU + if (opt.trust_model == TM_TOFU || opt.trust_model == TM_TOFU_PGP) + { + kbnode_t n = NULL; + strlist_t user_id_list = NULL; + int done = 0; + + /* If the caller didn't supply a user id then use all uids. */ + if (! uid) + { + if (! kb) + { + kb = get_pubkeyblock (ctrl, main_pk->keyid); + free_kb = 1; + } + n = kb; + } + + if (DBG_TRUST && sig && sig->signers_uid) + log_debug ("TOFU: only considering user id: '%s'\n", + sig->signers_uid); + + while (!done && (uid || (n = find_next_kbnode (n, PKT_USER_ID)))) + { + PKT_user_id *user_id; + int expired = 0; + + if (uid) + { + user_id = uid; + /* If the caller specified a user id, then we only + process the specified user id and are done after the + first iteration. */ + done = 1; + } + else + user_id = n->pkt->pkt.user_id; + + if (user_id->attrib_data) + /* Skip user attributes. */ + continue; + + if (sig && sig->signers_uid) + /* Make sure the UID matches. */ + { + char *email = mailbox_from_userid (user_id->name); + if (!email || !*email || strcmp (sig->signers_uid, email) != 0) + { + if (DBG_TRUST) + log_debug ("TOFU: skipping user id '%s', which does" + " not match the signer's email ('%s')\n", + email, sig->signers_uid); + xfree (email); + continue; + } + xfree (email); + } + + /* If the user id is revoked or expired, then skip it. */ + if (user_id->flags.revoked || user_id->flags.expired) + { + if (DBG_TRUST) + { + char *s; + if (user_id->flags.revoked && user_id->flags.expired) + s = "revoked and expired"; + else if (user_id->flags.revoked) + s = "revoked"; + else + s = "expire"; + + log_debug ("TOFU: Ignoring %s user id (%s)\n", + s, user_id->name); + } + + if (user_id->flags.revoked) + continue; + + expired = 1; + } + + add_to_strlist (&user_id_list, user_id->name); + user_id_list->flags = expired; + } + + /* Process the user ids in the order they appear in the key + block. */ + strlist_rev (&user_id_list); + + /* It only makes sense to observe any signature before getting + the validity. This is because if the current signature + results in a conflict, then we damn well want to take that + into account. */ + if (sig) + { + err = tofu_register_signature (ctrl, main_pk, user_id_list, + sig->digest, sig->digest_len, + sig->timestamp, "unknown"); + if (err) + { + log_error ("TOFU: error registering signature: %s\n", + gpg_strerror (err)); + + tofu_validity = TRUST_UNKNOWN; + } + } + if (! err) + tofu_validity = tofu_get_validity (ctrl, main_pk, user_id_list, + may_ask); + + free_strlist (user_id_list); + if (free_kb) + release_kbnode (kb); + } +#endif /*USE_TOFU*/ + + if (opt.trust_model == TM_TOFU_PGP + || opt.trust_model == TM_CLASSIC + || opt.trust_model == TM_PGP) + { + err = read_trust_record (ctrl, main_pk, &trec); + if (err && gpg_err_code (err) != GPG_ERR_NOT_FOUND) + { + tdbio_invalid (); + return 0; + } + if (gpg_err_code (err) == GPG_ERR_NOT_FOUND) + { + /* 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 user ID 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->flags.disabled = 1; + } + else + pk->flags.disabled = 0; + pk->flags.disabled_valid = 1; + } + + leave: +#ifdef USE_TOFU + validity = tofu_wot_trust_combine (tofu_validity, validity); +#else /*!USE_TOFU*/ + validity &= TRUST_MASK; + + if (validity == TRUST_NEVER) + /* TRUST_NEVER trumps everything else. */ + validity |= TRUST_NEVER; + if (validity == TRUST_EXPIRED) + /* TRUST_EXPIRED trumps everything but TRUST_NEVER. */ + validity |= TRUST_EXPIRED; +#endif /*!USE_TOFU*/ + + if (opt.trust_model != TM_TOFU + && pending_check_trustdb) + validity |= TRUST_FLAG_PENDING_CHECK; + + return validity; +} + + +static void +get_validity_counts (ctrl_t ctrl, 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 (ctrl, 0); + + if(read_trust_record (ctrl, pk, &trec)) + 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; + /* es_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 ) +{ + (void)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 ) +{ + (void)context; + (void)lid; + (void)ownertrust; + (void)validity; + return -1; +} + + +/**************** + * Print the current path + */ +void +enum_cert_paths_print (void **context, FILE *fp, + int refresh, ulong selected_lid) +{ + (void)context; + (void)fp; + (void)refresh; + (void)selected_lid; +} + + + +/**************************************** + *********** NEW NEW NEW **************** + ****************************************/ + +static int +ask_ownertrust (ctrl_t ctrl, u32 *kid, int minimum) +{ + PKT_public_key *pk; + int rc; + int ot; + + pk = xmalloc_clear (sizeof *pk); + rc = get_pubkey (ctrl, pk, kid); + if (rc) + { + log_error (_("public key %s not found: %s\n"), + keystr(kid), gpg_strerror (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)); + tdb_update_ownertrust (ctrl, pk, opt.force_ownertrust, 0); + ot=opt.force_ownertrust; + } + else + { + ot=edit_ownertrust (ctrl, pk, 0); + if(ot>0) + ot = tdb_get_ownertrust (ctrl, pk, 0); + 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); + es_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; + es_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':'-'); + es_write_sanitized (es_stdout, node->pkt->pkt.user_id->name, + len, ":", NULL); + es_putc (':', es_stdout); + es_putc ('\n', es_stdout); + } + } + } +} + + +static void +store_validation_status (ctrl_t ctrl, int depth, + kbnode_t 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 (ctrl, keyblock->pkt->pkt.public_key, + uid, depth, status); + + mark_keyblock_seen(stored,keyblock); + + any = 1; + } + } + } + + if (any) + do_sync (); +} + + +/* Returns a sanitized copy of the regexp (which might be "", but not + NULL). */ +/* Operator characters except '.' and backslash. + See regex(7) on BSD. */ +#define REGEXP_OPERATOR_CHARS "^[$()|*+?{" + +static char * +sanitize_regexp(const char *old) +{ + size_t start=0,len=strlen(old),idx=0; + int escaped=0,standard_bracket=0; + char *new=xmalloc((len*2)+1); /* enough to \-escape everything if we + have to */ + + /* There are basically two commonly-used regexps here. GPG and most + versions of PGP use "<[^>]+[@.]example\.com>$" and PGP (9) + command line uses "example.com" (i.e. whatever the user specifies, + and we can't expect users know to use "\." instead of "."). So + here are the rules: we're allowed to start with "<[^>]+[@.]" and + end with ">$" or start and end with nothing. In between, the + only legal regex character is ".", and everything else gets + escaped. Part of the gotcha here is that some regex packages + allow more than RFC-4880 requires. For example, 4880 has no "{}" + operator, but GNU regex does. Commenting removes these operators + from consideration. A possible future enhancement is to use + commenting to effectively back off a given regex to the Henry + Spencer syntax in 4880. -dshaw */ + + /* Are we bracketed between "<[^>]+[@.]" and ">$" ? */ + if(len>=12 && strncmp(old,"<[^>]+[@.]",10)==0 + && old[len-2]=='>' && old[len-1]=='$') + { + strcpy(new,"<[^>]+[@.]"); + idx=strlen(new); + standard_bracket=1; + start+=10; + len-=2; + } + + /* Walk the remaining characters and ensure that everything that is + left is not an operational regex character. */ + for(;start$", then it was escaping the ">" and is fine. If the regexp + actually ended with the bare "\", then it's an illegal regexp and + regcomp should kick it out. */ + + if(standard_bracket) + strcat(new,">$"); + + return new; +} + +/* 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) +{ + int ret; + char *regexp; + + regexp=sanitize_regexp(expr); + + { + regex_t pat; + + ret=regcomp(&pat,regexp,REG_ICASE|REG_EXTENDED); + if(ret==0) + { + ret=regexec(&pat,string,0,NULL,0); + regfree(&pat); + } + ret=(ret==0); + } + + if(DBG_TRUST) + log_debug("regexp '%s' ('%s') on '%s': %s\n", + regexp,expr,string,ret?"YES":"NO"); + + xfree(regexp); + + return ret; +} + +/* + * 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 (ctrl_t ctrl, kbnode_t 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->flags.revoked + && !node->pkt->pkt.user_id->flags.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 (ctrl, pk, uid); + mark_usable_uid_certs (ctrl, 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 + || !(opt.trust_model == TM_PGP + || opt.trust_model == TM_TOFU_PGP) + || (uidnode + && check_regexp(kr->trust_regexp, + uidnode->pkt->pkt.user_id->name)))) + { + /* 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 + || opt.trust_model == TM_TOFU_PGP) + && sig->trust_depth + && pk->trust_timestamp <= sig->timestamp) + { + unsigned char depth; + + /* If the depth on the signature is less than the + chain currently has, then use the signature depth + so we don't increase the depth beyond what the + signer wanted. If the depth on the signature is + more than the chain currently has, then use the + chain depth so we use as much of the signature + depth as the chain will permit. An ultimately + trusted signature can restart the depth to + whatever level it likes. */ + + if (sig->trust_depth < kr->trust_depth + || kr->ownertrust == TRUST_ULTIMATE) + depth = sig->trust_depth; + else + depth = kr->trust_depth; + + if (depth) + { + if(DBG_TRUST) + 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); + + /* 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,depth); + + pk->trust_value = sig->trust_value; + pk->trust_depth = 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, int dummy_uid_no) +{ + (void)dummy_uid_no; + 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 (ctrl_t ctrl, 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", gpg_strerror (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, NULL); + if (gpg_err_code (rc) == GPG_ERR_NOT_FOUND) + { + keys[nkeys].keyblock = NULL; + return keys; + } + if (rc) + { + log_error ("keydb_search(first) failed: %s\n", gpg_strerror (rc)); + goto die; + } + + 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", gpg_strerror (rc)); + goto die; + } + + 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 (ctrl, keyblock); + clear_kbnode_flags (keyblock); + pk = keyblock->pkt->pkt.public_key; + if (pk->has_expired || pk->flags.revoked) + { + /* it does not make sense to look further at those keys */ + mark_keyblock_seen (full_trust, keyblock); + } + else if (validate_one_keyblock (ctrl, 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, NULL))); + + if (rc && gpg_err_code (rc) != GPG_ERR_NOT_FOUND) + { + log_error ("keydb_search_next failed: %s\n", gpg_strerror (rc)); + goto die; + } + + keys[nkeys].keyblock = NULL; + return keys; + + die: + keys[nkeys].keyblock = NULL; + release_key_array (keys); + return NULL; +} + +/* Caller must sync */ +static void +reset_trust_records (ctrl_t ctrl) +{ + 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 (ctrl, &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 (ctrl, &rec); + } + + } + + if (opt.verbose) + { + log_info (ngettext("%d key processed", + "%d keys processed", + count), count); + log_printf (ngettext(" (%d validity count cleared)\n", + " (%d validity counts cleared)\n", + nreset), 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 (ctrl_t ctrl, 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 architectural 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 (ctrl, 0); + + kdb = keydb_new (); + if (!kdb) + return gpg_error_from_syserror (); + + 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 (); + + reset_trust_records (ctrl); + + /* 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 (ctrl, 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 (ctrl, 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 (); + } + + if (opt.trust_model == TM_TOFU) + /* In the TOFU trust model, we only need to save the ultimately + trusted keys. */ + goto leave; + + klist = utk_list; + + if (!opt.quiet) + log_info ("marginals needed: %d completes needed: %d trust model: %s\n", + opt.marginals_needed, opt.completes_needed, + trust_model_string (opt.trust_model)); + + 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 (ctrl, k->kid,min); + + if (interactive && k->ownertrust == TRUST_UNKNOWN) + { + k->ownertrust = ask_ownertrust (ctrl, k->kid,min); + + if (k->ownertrust == (unsigned int)(-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->ownertrustkid[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 (ctrl, kdb, full_trust, klist, + start_time, &next_expire); + if (!keys) + { + log_error ("validate_key_list failed\n"); + rc = GPG_ERR_GENERAL; + goto leave; + } + + for (key_count=0, kar=keys; kar->keyblock; kar++, key_count++) + ; + + /* Store the calculated valididation status somewhere */ + if (opt.verbose > 1 && DBG_TRUST) + dump_key_array (depth, keys); + + for (kar=keys; kar->keyblock; kar++) + store_validation_status (ctrl, depth, kar->keyblock, stored); + + if (!opt.quiet) + 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 = + (tdb_get_ownertrust + (ctrl, kar->keyblock->pkt->pkt.public_key, 0) + & TRUST_MASK); + k->min_ownertrust = tdb_get_min_ownertrust + (ctrl, kar->keyblock->pkt->pkt.public_key, 0); + 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); + if (klist != utk_list) + 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 */ + { + int rc2; + + if (next_expire == 0xffffffff || next_expire < start_time ) + tdbio_write_nextcheck (ctrl, 0); + else + { + tdbio_write_nextcheck (ctrl, next_expire); + if (!opt.quiet) + log_info (_("next trustdb check due at %s\n"), + strtimestamp (next_expire)); + } + + rc2 = tdbio_update_version_record (ctrl); + if (rc2) + { + log_error (_("unable to update trustdb version record: " + "write failed: %s\n"), gpg_strerror (rc2)); + 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..595f8b2 --- /dev/null +++ b/g10/trustdb.h @@ -0,0 +1,135 @@ +/* trustdb.h - Trust database + * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, + * 2005, 2012 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#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 TRUST_FLAG_TOFU_BASED 512 /* The trust value is based on + * the TOFU information. */ + +/* Private value used in tofu.c - must be different from the trust + values. */ +#define _tofu_GET_TRUST_ERROR 100 + +/* Length of the hash used to select UIDs in keyedit.c. */ +#define NAMEHASH_LEN 20 + + +/*-- trust.c --*/ +int cache_disabled_value (ctrl_t ctrl, PKT_public_key *pk); +void register_trusted_keyid (u32 *keyid); +void register_trusted_key (const char *string); + +const char *trust_value_to_string (unsigned int value); +int string_to_trust_value (const char *str); +const char *uid_trust_string_fixed (ctrl_t ctrl, + PKT_public_key *key, PKT_user_id *uid); + +unsigned int get_ownertrust (ctrl_t ctrl, PKT_public_key *pk); +void update_ownertrust (ctrl_t ctrl, + PKT_public_key *pk, unsigned int new_trust); +int clear_ownertrusts (ctrl_t ctrl, PKT_public_key *pk); + +void revalidation_mark (ctrl_t ctrl); +void check_trustdb_stale (ctrl_t ctrl); +void check_or_update_trustdb (ctrl_t ctrl); + +unsigned int get_validity (ctrl_t ctrl, kbnode_t kb, PKT_public_key *pk, + PKT_user_id *uid, + PKT_signature *sig, int may_ask); +int get_validity_info (ctrl_t ctrl, kbnode_t kb, PKT_public_key *pk, + PKT_user_id *uid); +const char *get_validity_string (ctrl_t ctrl, + PKT_public_key *pk, PKT_user_id *uid); + + +/*-- trustdb.c --*/ +void tdb_register_trusted_key (const char *string); +/* Returns whether KID is on the list of ultimately trusted keys. */ +int tdb_keyid_is_utk (u32 *kid); +/* Return the list of ultimately trusted keys. The caller must not + * modify this list nor must it free the list. */ +struct key_item *tdb_utks (void); +void tdb_update_utk (u32 *kid, int add); +void check_trustdb (ctrl_t ctrl); +void update_trustdb (ctrl_t ctrl); +int setup_trustdb( int level, const char *dbname ); +void how_to_fix_the_trustdb (void); +const char *trust_model_string (int model); +gpg_error_t init_trustdb (ctrl_t ctrl, int no_create); +int have_trustdb (ctrl_t ctrl); +void tdb_check_trustdb_stale (ctrl_t ctrl); +void tdb_revalidation_mark (ctrl_t ctrl); +int trustdb_pending_check(void); +void tdb_check_or_update (ctrl_t ctrl); + +int tdb_cache_disabled_value (ctrl_t ctrl, PKT_public_key *pk); + +unsigned int tdb_get_validity_core (ctrl_t ctrl, kbnode_t kb, + PKT_public_key *pk, PKT_user_id *uid, + PKT_public_key *main_pk, + PKT_signature *sig, int may_ask); + +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 (ctrl_t ctrl, byte *trust_model, + ulong *created, ulong *nextcheck, + byte *marginals, byte *completes, byte *cert_depth, + byte *min_cert_level); + +unsigned int tdb_get_ownertrust (ctrl_t ctrl, PKT_public_key *pk, + int no_create); +unsigned int tdb_get_min_ownertrust (ctrl_t ctrl, PKT_public_key *pk, + int no_create); +int get_ownertrust_info (ctrl_t ctrl, PKT_public_key *pk, int no_create); +const char *get_ownertrust_string (ctrl_t ctrl, + PKT_public_key *pk, int no_create); + +void tdb_update_ownertrust (ctrl_t ctrl, PKT_public_key *pk, + unsigned int new_trust, int as_trusted_key); +int tdb_clear_ownertrusts (ctrl_t ctrl, PKT_public_key *pk); + +/*-- tdbdump.c --*/ +void list_trustdb (ctrl_t ctrl, estream_t fp, const char *username); +void export_ownertrust (ctrl_t ctrl); +void import_ownertrust (ctrl_t ctrl, const char *fname); + +/*-- pkclist.c --*/ +int edit_ownertrust (ctrl_t ctrl, 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..a0f9d42 --- /dev/null +++ b/g10/verify.c @@ -0,0 +1,283 @@ +/* verify.c - Verify signed data + * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2004, 2005, 2006, + * 2007, 2010 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include +#include +#include +#include +#include + +#include "gpg.h" +#include "options.h" +#include "packet.h" +#include "../common/status.h" +#include "../common/iobuf.h" +#include "keydb.h" +#include "../common/util.h" +#include "main.h" +#include "filter.h" +#include "../common/ttyio.h" +#include "../common/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 (ctrl_t ctrl, int nfiles, char **files ) +{ + IOBUF fp; + armor_filter_context_t *afx = NULL; + progress_filter_context_t *pfx = new_progress_context (); + const char *sigfile; + int i, rc; + strlist_t sl; + + /* 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 + * intended signed material from the 2nd file or stdin. + * 1. gpg 0 ; i-- ) + add_to_strlist( &sl, files[i] ); + rc = proc_signature_packets (ctrl, NULL, fp, sl, sigfile ); + free_strlist(sl); + iobuf_close(fp); + if( (afx && afx->no_openpgp_data && rc == -1) + || gpg_err_code (rc) == GPG_ERR_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; + } + + leave: + release_armor_context (afx); + release_progress_context (pfx); + 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 (ctrl_t ctrl, const char *name ) +{ + IOBUF fp; + armor_filter_context_t *afx = NULL; + progress_filter_context_t *pfx = new_progress_context (); + int rc; + + print_file_status( STATUS_FILE_START, name, 1 ); + fp = iobuf_open(name); + if (fp) + iobuf_ioctl (fp, IOBUF_IOCTL_NO_CACHE, 1, NULL); + if (fp && is_secured_file (iobuf_get_fd (fp))) + { + iobuf_close (fp); + fp = NULL; + gpg_err_set_errno (EPERM); + } + if( !fp ) { + rc = gpg_error_from_syserror (); + log_error(_("can't open '%s': %s\n"), + print_fname_stdin(name), strerror (errno)); + print_file_status( STATUS_FILE_ERROR, name, 1 ); + goto leave; + } + handle_progress (pfx, fp, name); + + if( !opt.no_armor ) { + if( use_armor_filter( fp ) ) { + afx = new_armor_context (); + push_armor_filter (afx, fp); + } + } + + rc = proc_signature_packets (ctrl, NULL, fp, NULL, name ); + iobuf_close(fp); + write_status( STATUS_FILE_DONE ); + + reset_literals_seen(); + + leave: + release_armor_context (afx); + release_progress_context (pfx); + 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 (ctrl_t ctrl, int nfiles, char **files ) +{ + int i, rc; + int first_rc = 0; + + 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 GPG_ERR_GENERAL; + } + /* This code does not work on MSDOS but hwo 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; + rc = verify_one_file (ctrl, line); + if (!first_rc) + first_rc = rc; + } + + } + else { /* take filenames from the array */ + for(i=0; i < nfiles; i++ ) + { + rc = verify_one_file (ctrl, files[i]); + if (!first_rc) + first_rc = rc; + } + } + + return first_rc; +} + + + + +/* Perform a verify operation. To verify detached signatures, DATA_FD + shall be the descriptor of the signed data; for regular signatures + it needs to be -1. If OUT_FP is not NULL and DATA_FD is not -1 the + the signed material gets written that stream. + + FIXME: OUTFP is not yet implemented. +*/ +int +gpg_verify (ctrl_t ctrl, int sig_fd, int data_fd, estream_t out_fp) +{ + int rc; + iobuf_t fp; + armor_filter_context_t *afx = NULL; + progress_filter_context_t *pfx = new_progress_context (); + + (void)ctrl; + (void)out_fp; + + if (is_secured_file (sig_fd)) + { + fp = NULL; + gpg_err_set_errno (EPERM); + } + else + fp = iobuf_fdopen_nc (sig_fd, "rb"); + if (!fp) + { + rc = gpg_error_from_syserror (); + log_error (_("can't open fd %d: %s\n"), sig_fd, strerror (errno)); + goto leave; + } + + handle_progress (pfx, fp, NULL); + + if ( !opt.no_armor && use_armor_filter (fp) ) + { + afx = new_armor_context (); + push_armor_filter (afx, fp); + } + + rc = proc_signature_packets_by_fd (ctrl, NULL, fp, data_fd); + + if ( afx && afx->no_openpgp_data + && (rc == -1 || gpg_err_code (rc) == GPG_ERR_EOF) ) + rc = gpg_error (GPG_ERR_NO_DATA); + + leave: + iobuf_close (fp); + release_progress_context (pfx); + release_armor_context (afx); + return rc; +} -- cgit v1.2.3