summaryrefslogtreecommitdiffstats
path: root/g10
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 16:14:06 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 16:14:06 +0000
commiteee068778cb28ecf3c14e1bf843a95547d72c42d (patch)
tree0e07b30ddc5ea579d682d5dbe57998200d1c9ab7 /g10
parentInitial commit. (diff)
downloadgnupg2-eee068778cb28ecf3c14e1bf843a95547d72c42d.tar.xz
gnupg2-eee068778cb28ecf3c14e1bf843a95547d72c42d.zip
Adding upstream version 2.2.40.upstream/2.2.40upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'g10')
-rw-r--r--g10/ChangeLog-201112066
-rw-r--r--g10/Makefile.am267
-rw-r--r--g10/Makefile.in1453
-rw-r--r--g10/all-tests.scm35
-rw-r--r--g10/armor.c1570
-rw-r--r--g10/build-packet.c1808
-rw-r--r--g10/call-agent.c2702
-rw-r--r--g10/call-agent.h224
-rw-r--r--g10/call-dirmngr.c1401
-rw-r--r--g10/call-dirmngr.h48
-rw-r--r--g10/card-util.c2489
-rw-r--r--g10/cipher.c187
-rw-r--r--g10/compress-bz2.c254
-rw-r--r--g10/compress.c377
-rw-r--r--g10/cpr.c659
-rw-r--r--g10/dearmor.c131
-rw-r--r--g10/decrypt-data.c1030
-rw-r--r--g10/decrypt.c282
-rw-r--r--g10/dek.h51
-rw-r--r--g10/delkey.c382
-rw-r--r--g10/distsigkey.gpgbin0 -> 3902 bytes
-rw-r--r--g10/ecdh.c516
-rw-r--r--g10/encrypt.c1060
-rw-r--r--g10/exec.c705
-rw-r--r--g10/exec.h51
-rw-r--r--g10/export.c2475
-rw-r--r--g10/filter.h164
-rw-r--r--g10/free-packet.c569
-rw-r--r--g10/getkey.c4544
-rw-r--r--g10/gpg-w32info.rc52
-rw-r--r--g10/gpg.c5569
-rw-r--r--g10/gpg.h110
-rw-r--r--g10/gpg.w32-manifest.in18
-rw-r--r--g10/gpgcompose.c3089
-rw-r--r--g10/gpgsql.c251
-rw-r--r--g10/gpgsql.h61
-rw-r--r--g10/gpgv-w32info.rc52
-rw-r--r--g10/gpgv.c835
-rw-r--r--g10/gpgv.w32-manifest.in18
-rw-r--r--g10/helptext.c86
-rw-r--r--g10/import.c4528
-rw-r--r--g10/kbnode.c431
-rw-r--r--g10/key-check.c750
-rw-r--r--g10/key-check.h28
-rw-r--r--g10/key-clean.c614
-rw-r--r--g10/key-clean.h52
-rw-r--r--g10/keydb.c2116
-rw-r--r--g10/keydb.h570
-rw-r--r--g10/keyedit.c6577
-rw-r--r--g10/keyedit.h64
-rw-r--r--g10/keygen.c5766
-rw-r--r--g10/keyid.c987
-rw-r--r--g10/keylist.c2226
-rw-r--r--g10/keyring.c1747
-rw-r--r--g10/keyring.h45
-rw-r--r--g10/keyserver-internal.h66
-rw-r--r--g10/keyserver.c2001
-rw-r--r--g10/main.h518
-rw-r--r--g10/mainproc.c2914
-rw-r--r--g10/mdfilter.c73
-rw-r--r--g10/migrate.c118
-rw-r--r--g10/misc.c1905
-rw-r--r--g10/openfile.c403
-rw-r--r--g10/options.h423
-rw-r--r--g10/packet.h943
-rw-r--r--g10/parse-packet.c3663
-rw-r--r--g10/passphrase.c581
-rw-r--r--g10/photoid.c401
-rw-r--r--g10/photoid.h34
-rw-r--r--g10/pkclist.c1721
-rw-r--r--g10/pkglue.c453
-rw-r--r--g10/pkglue.h51
-rw-r--r--g10/plaintext.c814
-rw-r--r--g10/progress.c202
-rw-r--r--g10/pubkey-enc.c487
-rw-r--r--g10/revoke.c911
-rw-r--r--g10/rmd160.c425
-rw-r--r--g10/rmd160.h24
-rw-r--r--g10/server.c799
-rw-r--r--g10/seskey.c359
-rw-r--r--g10/sig-check.c1229
-rw-r--r--g10/sign.c1819
-rw-r--r--g10/skclist.c637
-rw-r--r--g10/t-keydb-get-keyblock.c65
-rw-r--r--g10/t-keydb-get-keyblock.gpgbin0 -> 138824 bytes
-rw-r--r--g10/t-keydb-keyring.kbxbin0 -> 5104 bytes
-rw-r--r--g10/t-keydb.c104
-rw-r--r--g10/t-rmd160.c91
-rw-r--r--g10/t-stutter-data.asc1
-rw-r--r--g10/t-stutter.c613
-rw-r--r--g10/tdbdump.c240
-rw-r--r--g10/tdbio.c1936
-rw-r--r--g10/tdbio.h122
-rw-r--r--g10/test-stubs.c591
-rw-r--r--g10/test.c192
-rw-r--r--g10/textfilter.c245
-rw-r--r--g10/tofu.c4033
-rw-r--r--g10/tofu.h142
-rw-r--r--g10/trust.c432
-rw-r--r--g10/trustdb.c2343
-rw-r--r--g10/trustdb.h135
-rw-r--r--g10/verify.c283
102 files changed, 109684 insertions, 0 deletions
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 <wk@g10code.com>
+
+ NB: ChangeLog files are no longer manually maintained. Starting
+ on December 1st, 2011 we put change information only in the GIT
+ commit log, and generate a top-level ChangeLog file from logs at
+ "make dist". See doc/HACKING for details.
+
+2011-11-30 Werner Koch <wk@g10code.com>
+
+ * keyserver.c (keyserver_import_cert): Adjust for changed
+ get_dns_cert.
+
+2011-11-28 Werner Koch <wk@g10code.com>
+
+ * keyserver.c (DEFAULT_MAX_CERT_SIZE): Increase from 16k to 64k.
+
+2011-11-22 Werner Koch <wk@g10code.com>
+
+ * pubkey-enc.c (get_session_key): Don't print anonymous recipient
+ messages in quiet mode. This is bug#1378.
+
+2011-11-06 Werner Koch <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * 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 <meyering@redhat.com>
+
+ 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 <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * tdbio.c (open_db): Do not print read-only warning in quiet mode.
+
+2011-07-18 Werner Koch <wk@g10code.com>
+
+ * parse-packet.c (parse_key): Print the decoded iteration count.
+ Fixes bug#1355.
+
+2011-07-07 Werner Koch <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * 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 <mb@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * 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 <marcus@g10code.com>
+
+ * 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 <marcus@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * export.c (transfer_format_to_openpgp): Do not apply
+ encode_s2k_iterations to S2K_COUNT.
+
+2011-04-25 Werner Koch <wk@g10code.com>
+
+ * 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 <mb@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * parse-packet.c (read_rest): Drop unsed PARTIAL arg. Rewrite to
+ detect premature EOF. Suggested by Timo Schulz.
+
+2011-03-10 Werner Koch <wk@g10code.com>
+
+ * passphrase.c (hash_passphrase): Remove.
+ (passphrase_to_dek_ext): Use gcry_kdf_derive.
+
+2011-03-03 Werner Koch <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * call-agent.c (agent_scd_pksign, agent_scd_pkdecrypt)
+ (hash_algo_option): Remove these unused functions.
+
+2011-02-10 Werner Koch <wk@g10code.com>
+
+ * seskey.c (encode_md_value): Change last fix to avoid a
+ regression for DSA with SHA-2 hashes.
+
+2011-02-09 Werner Koch <wk@g10code.com>
+
+ * keyserver.c: Replace all printf by es_printf.
+
+2011-02-08 Werner Koch <wk@g10code.com>
+
+ * call-dirmngr.c (gpg_dirmngr_ks_fetch): New.
+ * keyserver.c (keyserver_fetch): Rewrite to use dirmngr.
+
+2011-02-07 Werner Koch <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * sig-check.c (do_check_messages): Remove the long deprecated
+ SIGEXPIRED status line.
+
+2011-02-03 Werner Koch <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ Finished ECC integration.
+ Wrote change description for 2011-01-13.
+
+2011-02-02 Werner Koch <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * 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 <openpgp@brainhub.org> (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 <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * misc.c (openpgp_cipher_algo_name): Use gnupg_cipher_algo_name.
+
+2010-11-23 Werner Koch <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * 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 <dshaw@jabberwocky.com>
+
+ * 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 <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * import.c (transfer_secret_keys): Ignore missing key parameters.
+ Provide dummy IV. Ignore stub keys.
+
+2010-10-01 Werner Koch <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * keygen.c (key_from_sexp): Fix memory leak in the error case.
+
+ * call-agent.c (agent_export_key): New.
+
+2010-09-29 Werner Koch <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * call-agent.c (agent_probe_any_secret_key): New.
+
+2010-09-28 David Shaw <dshaw@jabberwocky.com>
+
+ * 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 <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * import.c (transfer_secret_keys, import_secret_one): Enable stats.
+ (import_secret_one): Enable secret key merging.
+
+2010-09-01 Werner Koch <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * misc.c (openpgp_pk_algo_name): New.
+ (openpgp_md_algo_name): New.
+
+2010-08-24 Werner Koch <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * mainproc.c (print_pkenc_list): Write a STATUS_ERROR. Fixes
+ bug#1255.
+
+2010-06-17 Werner Koch <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * cpr.c: Use estream for status output.
+
+2010-05-12 Werner Koch <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * passphrase.c (gpg_format_keydesc): New.
+ * pubkey-enc.c (get_it): Use it.
+ * sign.c (do_sign): Use it.
+
+2010-04-26 Werner Koch <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * pubkey-enc.c (get_it): Use the agent for decryption.
+ * call-agent.c (agent_pkdecrypt, inq_ciphertext_cb): New.
+
+2010-04-22 Werner Koch <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * openfile.c (mkdir): Remove.
+ (try_make_homedir): Use gnupg_mkdir.
+
+2010-04-01 Werner Koch <wk@g10code.com>
+
+ Use gpg_err_set_errno to set ERRNO.
+
+2010-03-26 Werner Koch <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * openfile.c (CMP_FILENAME): Depend on HAVE_DOSISH_SYSTEM instead
+ of HAVE_DRIVE_LETTERS.
+
+2010-03-15 Werner Koch <wk@g10code.com>
+
+ * card-util.c: Replace stdio by estream.
+ * keylist.c: Ditto.
+
+2010-03-12 Werner Koch <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * Makefile.am (needed_libs): Remove libjnlib.a.
+
+ * main.h: Remove "estream.h".
+
+2010-03-08 Werner Koch <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * keygen.c (ask_user_id): Avoid infinite loop in case of invalid
+ data. Fixes bug#1186.
+
+2010-02-02 Werner Koch <wk@g10code.com>
+
+ * keyedit.c (keyedit_menu): Change prompt to "gpg".
+ * card-util.c (card_edit): Change prompt to "gpg/card".
+
+2010-01-11 Werner Koch <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * getkey.c, keylist.c: Re-indent.
+
+2010-01-08 Werner Koch <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * gpg.c (set_debug): Allow for numerical debug leveles. Print
+ active debug flags.
+
+2009-11-27 Werner Koch <wk@g10code.com>
+
+ * keyedit.c (cmds, keyedit_menu): New command "checkbkupkey".
+
+2009-11-25 Marcus Brinkmann <marcus@g10code.de>
+
+ * server.c (gpg_server): Use assuan_fd_t and assuan_fdopen on fds.
+
+2009-11-23 Werner Koch <wk@g10code.com>
+
+ * gpg.c (gpgconf_list): Add key "default_pubkey_algo".
+
+2009-11-18 Werner Koch <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * server.c (register_commands): Add NULL arg to
+ assuan_register_command.
+
+2009-11-02 Marcus Brinkmann <marcus@g10code.de>
+
+ * server.c (reset_notify, input_notify, output_notify): Update to
+ new assuan interface.
+ (register_commands): Use assuan_handler_t.
+
+2009-10-19 Werner Koch <wk@g10code.com>
+
+ * options.h (glo_ctrl): Add field LASTERR.
+ * mainproc.c (proc_encrypted): Set LASTERR.
+ * server.c (cmd_decrypt): Check LASTERR.
+
+2009-10-02 Werner Koch <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * parse-packet.c (skip_packet, parse_gpg_control) <list_mode>: Take
+ care of premature EOFs.
+
+ * gpg.c (main): Remove obsolete GCRYCTL_DISABLE_INTERNAL_LOCKING.
+
+2009-09-29 Werner Koch <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * 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 <marcus@g10code.de>
+
+ * call-agent.c: Include "scdaemon.h" before <assuan.h> 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 <assuan.h> because of
+ GPG_ERR_SOURCE_DEFAULT check.
+ (main): Update to new Assuan API.
+ * server.c: Include "scdaemon.h" before <assuan.h> 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 <wk@g10code.com>
+
+ * keyedit.c (menu_select_uid): Use IDX ==-1 t select all.
+ (menu_select_key): Ditto.
+ (keyedit_menu) <cmdSELKEY, cmdSELUID>: Allow '*' to select all.
+
+2009-09-03 Werner Koch <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * keyedit.c (menu_revsig): Check for signature right away. Fix
+ Debian-bug#543530.
+
+2009-08-20 Daiki Ueno <ueno@unixuser.org>
+
+ * 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 <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * 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 <dshaw@jabberwocky.com>
+
+ * gpg.c (main): --pgp6 includes --disable-mdc.
+
+2009-07-23 David Shaw <dshaw@jabberwocky.com>
+
+ * 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 <wk@g10code.com>
+
+ * 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) <aFixTrustDB>: 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 <wk@g10code.com>
+
+ * keygen.c (generate_keypair): Allow Elgamal > 3072 in BOTH mode.
+ Reported by Jeroen Schot. Fixes bug#1091.
+
+2009-07-17 Werner Koch <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * keyserver.c (keyserver_search_prompt): No prompt in batch+colons
+ mode.
+
+2009-06-09 Werner Koch <wk@g10code.com>
+
+ * 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 <dshaw@jabberwocky.com>
+
+ * 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 <wk@g10code.com>
+
+ * card-util.c (get_manufacturer): Add new manufacturer.
+
+2009-05-26 Werner Koch <wk@g10code.com>
+
+ * parse-packet.c (mpi_read): Workaround for zero-length MPI bug in
+ libgcrypt<1.5.0.
+
+2009-05-22 Werner Koch <wk@g10code.com>
+
+ * signal.c (got_fatal_signal): Call new function
+ tty_cleanup_after_signal.
+
+2009-05-20 Werner Koch <wk@g10code.com>
+
+ * 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 <ueno@unixuser.org> (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 <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * 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 <marcus@g10code.de>
+
+ * gpg.c (gpgconf_list): Remove dead entry "allow-pka-lookup" (a
+ verify option for a couple of years now).
+
+2009-05-14 Werner Koch <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * keygen.c (parse_expire_string): Base ISO date string at noon.
+ Also allow full ISO timestamp.
+
+2009-05-11 Werner Koch <wk@g10code.com>
+
+ * 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 <dshaw@jabberwocky.com>
+
+ * keyserver.c (keyserver_typemap): gpgkeys_hkp handles hkps as
+ well. From 1.4.
+
+2009-05-06 Werner Koch <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * 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 <dshaw@jabberwocky.com> (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 <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * mainproc.c (print_pkenc_list): Use snprintf.
+
+2009-03-17 Werner Koch <wk@g10code.com>
+
+ * call-agent.c (my_percent_plus_escape): Remove.
+ (agent_get_passphrase): Rewrite using percent_plus_escape.
+
+2009-03-17 Daiki Ueno <ueno@unixuser.org>
+
+ * 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 <wk@g10code.com>
+
+ * 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 <dshaw@jabberwocky.com>
+
+ * 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 <wk@g10code.com>
+
+ * call-agent.c (agent_scd_pksign, agent_scd_pkdecrypt): First send
+ the SERIALNO command.
+
+2009-02-24 Werner Koch <wk@g10code.com>
+
+ * pkglue.c (pk_verify): Return an error for improper DATA instead
+ of calling BUG().
+
+2009-02-09 Werner Koch <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * card-util.c (card_status): Detect a Geldkarte.
+
+2009-01-13 Werner Koch <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * getkey.c (finish_lookup): Take care of keys with a zero
+ timestamp. Reported by Peter Gutmann.
+
+2009-01-08 Werner Koch <wk@g10code.com>
+
+ * misc.c (has_invalid_email_chars): Let non-ascii pass through.
+
+ * cpr.c [USE_SHM_COPROCESSING]: Remove this code.
+
+2008-12-12 Werner Koch <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * gpg.c (main) [IS_DEVELOPMENT_VERSION]: Fix strusage use.
+
+2008-12-09 Werner Koch <wk@g10code.com>
+
+ * keygen.c (proc_parameter_file): Check that key and subkey usages
+ are allowed.
+
+2008-12-09 David Shaw <dshaw@jabberwocky.com> (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 <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * gpgv.c: Use new ARGPARSE macros and re-indent.
+
+2008-11-11 Werner Koch <wk@g10code.com>
+
+ * gpg.c (opts): Use new ARGPARSE macros for clarity.
+
+2008-10-24 Werner Koch <wk@g10code.com>
+
+ * keyedit.c (change_passphrase): Clear passphrase cache.
+
+2008-10-20 Werner Koch <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * main.h (idea_cipher_warn): Use do while construct in place of an
+ empty definition.
+
+2008-10-03 David Shaw <dshaw@jabberwocky.com>
+
+ * 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 <wk@g10code.com>
+
+ * 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 <dshaw@jabberwocky.com>
+
+ * 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 <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * 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 <dshaw@jabberwocky.com>
+
+ * pkclist.c (select_algo_from_prefs): Redo function to rank prefs
+ and pick a consensus winner across all keys.
+
+2008-09-16 Werner Koch <wk@g10code.com>
+
+ * 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 <dshaw@jabberwocky.com>
+
+ * keyserver.c (keyserver_import_cert): Allow keyserver URLs in
+ addition to full URLs in CERT records.
+
+2008-08-11 Werner Koch <wk@g10code.com>
+
+ * keygen.c (ask_expire_interval): Check for time overflow of an
+ u32. Fixes bug #947.
+
+2008-08-01 Werner Koch <wk@g10code.com>
+
+ * tdbio.c (open_db) [!EROFS]: Move closing parens out of the
+ ifdef. Reported by Ken Takusagawa.
+
+2008-06-25 Marcus Brinkmann <marcus@g10code.de>
+
+ * 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 <wk@g10code.com>
+
+ * gpg.c (gpgconf_list): Add "group".
+
+2008-06-18 Marcus Brinkmann <marcus@g10code.de>
+
+ * 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 <wk@g10code.com>
+
+ * keygen.c (output_control_s): Add ASK_PASSPHRASE.
+ (read_parameter_file): Add commands %ask-passphrase and
+ %no-ask-passphrase.
+
+2008-06-11 Werner Koch <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * sig-check.c (do_check_messages): Print a revocation diagnostic
+ in verbose mode.
+
+2008-05-07 Werner Koch <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * 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 <dshaw@jabberwocky.com>
+
+ * getkey.c (merge_selfsigs_subkey): If there are multiple 0x19
+ backsigs, take the most recent one.
+
+2008-04-08 Werner Koch <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * keyserver.c (parse_keyserver_uri): Allow a default host name.
+
+ * getkey.c (get_pubkey_byname): Replace sprintf by bin2hex.
+
+2008-04-02 Werner Koch <wk@g10code.com>
+
+ * gpg.c (main): Do not allow DSA2 with a too old Libgcrypt.
+
+2008-03-26 Werner Koch <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * keyserver.c (parse_keyrec): Take care of char defaulting to
+ unsigned when using hextobyte.
+
+2008-03-25 David Shaw <dshaw@jabberwocky.com> (wk)
+
+ * import.c (collapse_uids): Fix bug 894: possible memory
+ corruption around deduplication of user IDs.
+
+2008-03-25 Werner Koch <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * seckey-cert.c (do_check): Use GCRYMPI_FMT_PGP for v3 keys.
+ Reported by Petr Cerny.
+
+2008-03-13 Werner Koch <wk@g10code.com>
+
+ * passphrase.c (PROMPTSTRING): Change string to me more similar to
+ the X.509 prompt.
+
+2008-02-26 Werner Koch <wk@g10code.com>
+
+ * getkey.c (get_pubkey_byname): Fix comment.
+
+2008-02-14 Werner Koch <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * server.c (cmd_getinfo): New.
+ (register_commands): Register GETINFO.
+
+2008-02-09 Marcus Brinkmann <marcus@g10code.de>
+
+ * 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 <wk@g10code.com>
+
+ * keydb.c (maybe_create_keyring): Fixed last change.
+ * tdbio.c (tdbio_set_dbname): Also test for forward slash.
+
+2008-01-29 Werner Koch <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * card-util.c (get_manufacturer): Add vendor 0004.
+
+2008-01-02 Werner Koch <wk@g10code.com>
+
+ * gpg.c: Add --logger-file as an alias for log-file.
+
+2007-12-14 Werner Koch <wk@g10code.com>
+
+ * gpg.c (main): Set opt.no_homedir_creation during the first option
+ parsing pass.
+
+2007-12-12 Werner Koch <wk@g10code.com>
+
+ * 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 <dshaw@jabberwocky.com> (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 <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * mainproc.c (proc_pubkey_enc): Allow type 20 Elgamal key for
+ decryption.
+
+2007-12-10 Werner Koch <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * misc.c (openpgp_pk_algo_usage): Allow Elgamal type 20 for
+ encryption.
+
+2007-12-04 Werner Koch <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * keyedit.c (keyedit_menu): String grammar fix.
+
+2007-11-15 Werner Koch <wk@g10code.com>
+
+ * gpg.c (main): New option --xauthority.
+ * call-agent.c (start_agent): Adjust changed start_new_gpg_agent.
+
+2007-11-12 Werner Koch <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ 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 <dshaw@jabberwocky.com> (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 <dshaw@jabberwocky.com> (wk)
+
+ From 1.4 (September):
+
+ * import.c (collapse_uids): Significant speedup for de-duping user
+ IDs.
+
+2007-10-25 David Shaw <dshaw@jabberwocky.com> (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 <wk@g10code.com>
+
+ * passphrase.c (passphrase_get): Use new utf8 switching fucntions.
+
+2007-09-14 Werner Koch <wk@g10code.com>
+
+ * gpg.c (build_lib_list): New.
+ (my_strusage): Print lib info.
+
+2007-08-27 Werner Koch <wk@g10code.com>
+
+ * trustdb.c (USE_INTERNAL_REGEX): Remove support.
+
+2007-08-24 Werner Koch <wk@g10code.com>
+
+ * keyring.c (keyring_register_filename): Use same_file_p().
+
+2007-08-21 Werner Koch <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * gpg.c: Include gc-opt-flags.h and remove their definition here.
+
+2007-07-17 Werner Koch <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * gpg.c (main): Use translate_sys2libc_fd_int when passing an int
+ value.
+ * gpgv.c (main): Ditto.
+
+2007-07-05 Werner Koch <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * openfile.c (try_make_homedir): Support W32; use standard_homedir.
+
+2007-06-25 Werner Koch <wk@g10code.com>
+
+ * gpg.c, gpgv.c: Include sysutils.h.
+ (main): Replace iobuf_translate_file_handle by
+ translate_sys2libc_fd.
+
+2007-06-21 Werner Koch <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * 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 <marcus@g10code.de>
+
+ * gpg.c (gpgconf_list): Percent escape output of --gpgconf-list.
+
+2007-06-14 Werner Koch <wk@g10code.com>
+
+ * call-agent.c (start_agent): Use gnupg_module_name.
+
+2007-06-12 Werner Koch <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * Makefile.am (gpg2_LDADD): Syntax fix.
+
+2007-06-06 Werner Koch <wk@g10code.com>
+
+ * 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 <marcus@g10code.de>
+
+ * passphrase.c (passphrase_get): Use PACKAGE_GT, not PACKAGE.
+
+ * passphrase.c (passphrase_get): Free ORIG_CODESET on error.
+
+2007-05-16 Werner Koch <wk@g10code.com>
+
+ * sig-check.c (check_backsig): Check the digest algorithm before
+ using it. Fixed bug 797.
+
+2007-05-09 Werner Koch <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * openfile.c (open_outfile, overwrite_filep) [W32]: Use "/dev/nul".
+
+2007-05-02 David Shaw <dshaw@jabberwocky.com>
+
+ * 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 <marcus@g10code.de>
+
+ * passphrase.c (passphrase_to_dek): Write missing passphrase
+ status message in case of cancellation.
+
+2007-04-16 Werner Koch <wk@g10code.com>
+
+ * build-packet.c (mpi_write): Made buffer a bit larger. Reported
+ by Alexander Feigl.
+
+2007-04-13 Werner Koch <wk@g10code.com>
+
+ * call-agent.c (start_agent): Don't use log_error when using the
+ fallback hack to start the agent. This is bug 782.
+
+2007-04-05 David Shaw <dshaw@jabberwocky.com>
+
+ 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 <dshaw@jabberwocky.com>
+
+ 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 <dshaw@jabberwocky.com>
+
+ 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 <wk@g10code.com>
+
+ * plaintext.c (handle_plaintext): Add two extra fflush for stdout.
+
+2007-03-08 David Shaw <dshaw@jabberwocky.com> (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 <wk@g10code.com>
+
+ 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 <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * export.c (do_export_stream): Allow reset-subkey-passwd along
+ with sexp-format.
+
+2007-02-04 Werner Koch <wk@g10code.com>
+
+ * parse-packet.c (parse_signature): Limit bytes read for an
+ unknown alogorithm. Fixes Debian bug#402592.
+
+2007-01-31 Werner Koch <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * pkclist.c (do_we_trust_pre): Issue a user-id-hint status code.
+
+2007-01-15 Werner Koch <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * 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 <dshaw@jabberwocky.com>
+
+ * Makefile.am: Link to iconv for jnlib dependency.
+
+2006-12-05 Werner Koch <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * 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 <dshaw@jabberwocky.com>
+
+ * keyedit.c (menu_clean): Show "already minimized" rather than
+ "already clean" when a minimized key is minimized again. From
+ Dirk Traulsen.
+
+2006-12-02 David Shaw <dshaw@jabberwocky.com>
+
+ * options.h, gpg.c (main), passphrase.c (passphrase_to_dek): Add
+ --passphrase-repeat option to control how many times gpg will
+ re-prompt for a passphrase to ensure the user has typed it
+ correctly. Defaults to 1.
+
+2006-12-02 Werner Koch <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * gpg.c (main): Remove the default --require-cross-certification.
+ * options.skel: Enable require-cross-certification.
+
+2006-10-31 Werner Koch <wk@g10code.com>
+
+ * pkclist.c (warn_missing_aes_from_pklist): New.
+ * encode.c (encrypt_filter, encode_crypt): Use it here.
+
+2006-10-27 Werner Koch <wk@g10code.com>
+
+ * pkclist.c (warn_missing_mdc_from_pklist): New.
+ * encode.c (use_mdc): Use it here.
+
+2006-10-24 Marcus Brinkmann <marcus@g10code.de>
+
+ * Makefile.am (AM_CFLAGS): Add $(LIBASSUAN_CFLAGS).
+
+2006-10-23 Werner Koch <wk@g10code.com>
+
+ * gpg.c (main): New command --gpgconf-test.
+
+ * Makefile.am (bzip2_source): New.
+
+2006-10-20 Werner Koch <wk@g10code.com>
+
+ * getkey.c (classify_user_id): Reserve '&' for search by keygrip.
+
+2006-10-19 Werner Koch <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * Makefile.am (LDADD): Replaced W32LIBS by NETLIBS.
+
+2006-10-12 David Shaw <dshaw@jabberwocky.com>
+
+ * parse-packet.c (parse_symkeyenc): Show the unpacked as well as
+ the packed s2k iteration count.
+
+ * main.h, options.h, gpg.c (encode_s2k_iterations, main),
+ passphrase.c (hash_passphrase): Add --s2k-count option to specify
+ the number of s2k hash iterations.
+
+2006-10-08 Werner Koch <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * Makefile.am (AM_CFLAGS): Use PTH version of libassuan.
+
+2006-10-06 David Shaw <dshaw@jabberwocky.com>
+
+ * keyserver.c (keyserver_spawn): Write the 16-digit keyid rather
+ than whatever key selector the user used on the command line.
+
+2006-10-05 Werner Koch <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * encr-data.c (decrypt_data, mdc_decode_filter): Check the MDC
+ right here and don't let parse-packet handle the MDC.
+
+2006-09-29 Werner Koch <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ Replaced all STRLIST by strlist_t.
+
+2006-09-21 Werner Koch <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * call-agent.c: Include asshelp.h.
+ (start_agent): Use send_pinentry_environment.
+
+2006-09-14 Werner Koch <wk@g10code.com>
+
+ Replaced all call gpg_error_from_errno(errno) by
+ gpg_error_from_syserror().
+
+2006-09-13 Werner Koch <wk@g10code.com>
+
+ * gpg.c (main): Made --require-cross-certification the default.
+
+2006-09-06 Marcus Brinkmann <marcus@g10code.de>
+
+ * 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 <wk@g10code.com>
+
+ * gpg.c (main): Enable new assuan API.
+ * call-agent.c: Changed to new Assuan API.
+
+2006-09-01 Werner Koch <wk@g10code.com>
+
+ * call-agent.c: Do not force using the pipe server.
+
+ * gpg.c (main): Enable card related commands.
+
+2006-08-22 Werner Koch <wk@g10code.com>
+
+ * mainproc.c (proc_plaintext): Fixed a #warning
+
+2006-08-21 Werner Koch <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * parse-packet.c (parse_comment): Cap comments at 65k.
+ (parse_gpg_control): Skip too large control packets.
+
+2006-07-24 David Shaw <dshaw@jabberwocky.com> (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 <wk@g10code.com>
+
+ * misc.c (checksum_mpi): No need for nbits as they are alredy
+ included in the buffer.
+
+2006-06-29 Werner Koch <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ 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 <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * 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 <dshaw@jabberwocky.com> (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 <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * g10.c (print_hashline, add_group): Fixes for signed/unsigned
+ pointer mismatch warnings.
+
+2005-06-01 Werner Koch <wk@g10code.com>
+
+ * mkdtemp.c: Removed.
+ * exec.c: Include mkdtemp.h
+
+2004-12-21 Werner Koch <wk@g10code.com>
+
+ * gpgv.c, g10.c (main): Use default_hoemdir ().
+
+2004-12-18 Werner Koch <wk@g10code.com>
+
+ * gpg.h (map_assuan_err): Define in terms of
+ map_assuan_err_with_source.
+
+2004-12-15 Werner Koch <wk@g10code.com>
+
+ * Makefile.am (LDADD): Remove ZLIBS.
+
+2004-10-22 Werner Koch <wk@g10code.com>
+
+ * 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 <wk@g10code.com>
+
+ * gpgv.c (i18n_init): Always use LC_ALL.
+
+ * Makefile.am (LDADD): Adjusted for gettext 0.14.
+
+2004-09-20 Werner Koch <wk@g10code.com>
+
+ * keyedit.c (show_key_with_all_names): Print the card S/N.
+
+2004-09-11 Moritz Schulte <moritz@g10code.com>
+
+ * openfile.c (copy_options_file): Fixed last commit (added a `+').
+
+2004-08-31 Werner Koch <wk@g10code.de>
+
+ * 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 <marcus@g10code.de>
+
+ * passphrase.c (agent_get_passphrase): Fix detection of gpg-agent
+ cancellation.
+
+2004-07-01 Werner Koch <wk@gnupg.org>
+
+ * 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 <wk@gnupg.org>
+
+ * g10.c (main) <gpgconf>: Use gpg.conf and not /dev/null as
+ default filename.
+
+2004-04-28 Werner Koch <wk@gnupg.org>
+
+ * card-util.c (card_edit): Remove PIN verification.
+ (generate_card_keys): New arg SERIALNO. Do PIN verification here
+ after resetting forced_chv1.
+
+2004-04-26 Werner Koch <wk@gnupg.org>
+
+ * card-util.c (change_name): Check that the NAME is not too long.
+ (change_url): Likewise.
+ (change_login): Likewise.
+
+2004-03-23 Werner Koch <wk@gnupg.org>
+
+ * 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 <wk@gnupg.org>
+
+ * call-agent.c (start_agent): Ignore an empty GPG_AGENT_INFO.
+ * passphrase.c (agent_open): Ditto.
+
+2004-02-12 Werner Koch <wk@gnupg.org>
+
+ * gpgv.c: Removed g10defs.h.
+
+ * Makefile.am: Include cmacros.am for common flags.
+
+2004-02-11 Werner Koch <wk@gnupg.org>
+
+ * openfile.c (try_make_homedir): Use GNUPG_DEFAULT_HOMEDIR.
+ * gpgv.c (main): Ditto.
+ * g10.c (main): Ditto.
+
+2004-01-19 Moritz Schulte <mo@g10code.com>
+
+ * 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 <mo@g10code.com>
+
+ * keygen.c (do_generate_keypair): Print member fname, instead of
+ newfname.
+
+2003-12-17 Werner Koch <wk@gnupg.org>
+
+ * 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 <wk@gnupg.org>
+
+ * 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 <mo@g10code.com>
+
+ * signal.c: Removed unused file.
+
+2003-11-10 Moritz Schulte <mo@g10code.com>
+
+ * Makefile.am (INCLUDES): Added: @LIBGCRYPT_CFLAGS@.
+
+2003-10-25 Werner Koch <wk@gnupg.org>
+
+ * call-agent.c (learn_status_cb, scd_genkey_cb): Fixed faulty use
+ of !spacep().
+
+2003-10-20 Werner Koch <wk@gnupg.org>
+
+ * 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 <wk@gnupg.org>
+
+ * 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 <wk@gnupg.org>
+
+ * card-util.c: Tweaked to use this source also under 1.3.
+
+2003-09-30 Werner Koch <wk@gnupg.org>
+
+ * 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 <wk@gnupg.org>
+
+ * sign.c (do_sign): Removed disabled testcode.
+
+2003-09-26 Timo Schulz <twoaday@freakmail.de>
+
+ * card_status (card_status): Do not use fputs since the fp
+ parameter can be NULL. This fixes a segv.
+
+2003-09-24 Werner Koch <wk@gnupg.org>
+
+ * card-util.c (print_isoname,card_status): Handle opt.with_colons.
+ (print_sha1_fpr_colon): New.
+
+2003-09-23 Werner Koch <wk@gnupg.org>
+
+ 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 <twoaday@freakmail.de>
+
+ * 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 <wk@gnupg.org>
+
+ * 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 <wk@gnupg.org>
+
+ * 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 <wk@gnupg.org>
+
+ * 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 <twoaday@freakmail.de>
+
+ * pkglue.c (mpi_from_sexp): New. Used to factor out
+ some common code.
+
+2003-08-24 Werner Koch <wk@gnupg.org>
+
+ * keygen.c (do_generate_keypair): Print a reminder to use --gen-revoke.
+
+2003-08-18 Timo Schulz <twoaday@freakmail.de>
+
+ * 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 <wk@gnupg.org>
+
+ * 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 <wk@gnupg.org>
+
+ * pkglue.c (pk_sign): Allow signing using RSA.
+
+2003-08-05 Werner Koch <wk@gnupg.org>
+
+ * 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 <wk@gnupg.org>
+
+ * 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 <wk@gnupg.org>
+
+ * 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 <wk@gnupg.org>
+
+ Adjusted for gcry_mpi_print and gcry_mpi_scan API change.
+
+2003-07-24 Werner Koch <wk@gnupg.org>
+
+ * 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 <wk@gnupg.org>
+
+ * 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 <wk@gnupg.org>
+
+ * 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 <wk@gnupg.org>
+
+ * 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 <wk@gnupg.org>
+
+ * 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 <wk@gnupg.org>
+
+ * seckey-cert.c (is_secret_key_protected): Let it handle mode 1002.
+
+2003-07-01 Werner Koch <wk@gnupg.org>
+
+ * 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 <wk@gnupg.org>
+
+ * 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 <wk@gnupg.org>
+
+ * 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 <wk@gnupg.org>
+
+ * 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 <wk@gnupg.org>
+
+ * 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 <wk@gnupg.org>
+
+ * keyid.c (do_fingerprint_md): Made it work again.
+
+2003-06-19 Werner Koch <wk@gnupg.org>
+
+ 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 <wk@gnupg.org>
+
+ 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 <dshaw@jabberwocky.com>
+
+ * options.h, g10.c (main), keylist.c (list_keyblock_print): Add
+ "show-validity" and "show-long-keyid" list-options.
+
+ * gpgv.c (get_validity, trust_value_to_string): Stubs.
+
+ * g10.c (main): Use SAFE_VERSION instead of VERSION in the
+ version-specific gpg.conf file so it can be overridden on RISCOS.
+
+2003-06-01 David Shaw <dshaw@jabberwocky.com>
+
+ * g10.c (main), keylist.c (show_policy_url, show_notation),
+ mainproc.c (check_sig_and_print): Emulate the old policy and
+ notation behavior (display by default). Send to status-fd whether
+ it is displayed on the screen or not.
+
+ * g10.c (main): Since we now have some options in devel that won't
+ work in a stable branch gpg.conf file, try for a version-specific
+ gpg.conf-VERSION file before falling back to gpg.conf.
+
+ * main.h, options.h: Move various option flags to options.h.
+
+2003-05-31 David Shaw <dshaw@jabberwocky.com>
+
+ * mainproc.c (check_sig_and_print), main.h, keylist.c
+ (show_policy, show_notation): Collapse the old print_notation_data
+ into show_policy() and show_notation() so there is only one
+ function to print notations and policy URLs.
+
+ * options.h, main.h, g10.c (main), keyedit.c
+ (print_and_check_one_sig), keylist.c (list_one,
+ list_keyblock_print), pkclist.c (do_edit_ownertrust), sign.c
+ (mk_notation_and_policy): New "list-options" and "verify-options"
+ commands. These replace the existing
+ --show-photos/--no-show-photos,
+ --show-notation/--no-show-notation,
+ --show-policy-url/--no-show-policy-url, and --show-keyring
+ options. The new method is more flexible since a user can specify
+ (for example) showing photos during sig verification, but not in
+ key listings. The old options are emulated.
+
+ * main.h, misc.c (parse_options): New general option line
+ parser. Fix the bug in the old version that did not handle report
+ syntax errors after a valid entry.
+
+ * import.c (parse_import_options), export.c
+ (parse_export_options): Call it here instead of duplicating the
+ code.
+
+2003-05-30 David Shaw <dshaw@jabberwocky.com>
+
+ * keylist.c (list_one): Don't show the keyring filename when in
+ --with-colons mode. Actually translate "Keyring" string.
+
+ * mainproc.c (proc_tree): We can't currently handle multiple
+ signatures of different classes or digests (we'd pretty much have
+ to run a different hash context for each), but if they are all the
+ same, make an exception. This is Debian bug #194292.
+
+ * sig-check.c (check_key_signature2): Make string translatable.
+
+ * packet.h, getkey.c (fixup_uidnode): Mark real primary uids
+ differently than assumed primaries.
+
+ * keyedit.c (no_primary_warning): Use the differently marked
+ primaries here in a new function to warn when an --edit-key
+ command might rearrange the self-sig dates enough to change which
+ uid is primary.
+ (menu_expire, menu_set_preferences): Use no_primary_warning()
+ here.
+
+ * Makefile.am: Use @DLLIBS@ for -ldl.
+
+2003-05-26 David Shaw <dshaw@jabberwocky.com>
+
+ * getkey.c (premerge_public_with_secret): Made "no secret subkey
+ for" warning a verbose item and translatable. (From wk on stable
+ branch)
+
+ * sig-check.c (check_key_signature2): Made "no subkey for subkey
+ binding packet" a verbose item instead of a !quiet one. There are
+ too many garbled keys out in the wild. (From wk on stable branch)
+
+ * filter.h: Remove const from WHAT. (From wk on stable branch)
+
+ * progress.c (handle_progress): Store a copy of
+ NAME. (progress_filter): Release WHAT, make sure not to print a
+ NULL WHAT. (From wk on stable branch)
+
+ * openfile.c (open_sigfile): Adjust free for new progress
+ semantics. (From wk on stable branch)
+
+ * plaintext.c (ask_for_detached_datafile): Don't dealloc
+ pfx->WHAT. (From wk on stable branch)
+
+ * seckey-cert.c (do_check): Issue the RSA_OR_IDEA status when the
+ cipher algo is IDEA to make it easier to track down the
+ problem. (From twoaday on stable branch)
+
+2003-05-24 David Shaw <dshaw@jabberwocky.com>
+
+ * armor.c, g10.c, kbnode.c, misc.c, pkclist.c, sign.c,
+ build-packet.c, getkey.c, keydb.c, openfile.c, plaintext.c,
+ status.c, gpgv.c, keygen.c, options.h, sig-check.c, tdbio.h,
+ encode.c, mainproc.c, parse-packet.c, signal.c, textfilter.c: Edit
+ all preprocessor instructions to remove whitespace before the '#'.
+ This is not required by C89, but there are some compilers out
+ there that don't like it.
+
+2003-05-21 David Shaw <dshaw@jabberwocky.com>
+
+ * trustdb.h, trustdb.c (is_disabled), gpgv.c (is_disabled): Rename
+ is_disabled to cache_disabled_value, which now takes a pk and not
+ just the keyid. This is for speed since there is no need to
+ re-fetch a key when we already have that key handy. Cache the
+ result of the check so we don't need to hit the trustdb more than
+ once.
+
+ * getkey.c (skip_disabled): New function to get a pk and call
+ is_disabled on it. (key_byname): Use it here.
+
+ * packet.h, getkey.c (skip_disabled), keylist.c
+ (print_capabilities): New "pk_is_disabled" macro to retrieve the
+ cached disabled value if available, and fill it in via
+ cache_disabled_value if not available.
+
+ * trustdb.c (get_validity): Cache the disabled value since we have
+ it handy and it might be useful later.
+
+ * parse-packet.c (parse_key): Clear disabled flag when parsing a
+ new key. Just in case someone forgets to clear the whole key.
+
+ * getkey.c (merge_selfsigs_main): Add an "if all else fails" path
+ for setting a single user ID primary when there are multiple set
+ primaries all at the same second, or no primaries set and the most
+ recent user IDs are at the same second, or no signed user IDs at
+ all. This is arbitrary, but deterministic.
+
+ * exec.h, photoid.h: Add copyright message.
+
+ * keylist.c (list_keyblock_print): Don't dump attribs for
+ revoked/expired/etc uids for non-colon key listings. This is for
+ consistency with --show-photos.
+
+ * main.h, keylist.c (dump_attribs), mainproc.c
+ (check_sig_and_print): Dump attribs if --attrib-fd is set when
+ verifying signatures.
+
+ * g10.c (main): New --gnupg option to disable the various
+ --openpgp, --pgpX, etc. options. This is the same as --no-XXXX
+ for those options.
+
+ * revoke.c (ask_revocation_reason): Clear old reason if user
+ elects to repeat question. This is bug 153.
+
+ * keyedit.c (sign_uids): Show keyid of the key making the
+ signature.
+
+2003-05-21 Werner Koch <wk@gnupg.org>
+
+ * progress.c (handle_progress)
+ * sign.c (write_plaintext_packet)
+ * encode.c (encode_simple,encode_crypt): Make sure that a filename
+ of "-" is considered to be stdin so that iobuf_get_filelength
+ won't get called. This fixes bug 156 reported by Gregery Barton.
+
+2003-05-02 David Shaw <dshaw@jabberwocky.com>
+
+ * packet.h, build-packet.c (build_sig_subpkt), export.c
+ (do_export_stream), import.c (remove_bad_stuff, import),
+ parse-packet.c (dump_sig_subpkt, parse_one_sig_subpkt): Remove
+ vestigal code for the old sig cache subpacket. This wasn't
+ completely harmless as it caused subpacket 101 to disappear on
+ import and export.
+
+ * options.h, armor.c, cipher.c, g10.c, keyedit.c, pkclist.c,
+ sign.c, encode.c, getkey.c, revoke.c: The current flags for
+ different levels of PGP-ness are massively complex. This is step
+ one in simplifying them. No functional change yet, just use a
+ macro to check for compliance level.
+
+ * sign.c (sign_file): Fix bug that causes spurious compression
+ preference warning.
+
+ * sign.c (clearsign_file): Fix bug that prevents proper warning
+ message from appearing when clearsigning in --pgp2 mode with a
+ non-v3 RSA key.
+
+ * main.h, misc.c (compliance_option_string, compliance_string,
+ compliance_failure), pkclist.c (build_pk_list), sign.c (sign_file,
+ clearsign_file), encode.c (encode_crypt,
+ write_pubkey_enc_from_list): New functions to put the "this
+ message may not be usable...." warning in one place.
+
+ * options.h, g10.c (main): Part two of the simplification. Use a
+ single enum to indicate what we are compliant to (1991, 2440,
+ PGPx, etc.)
+
+ * g10.c (main): Show errors for failure in export, send-keys,
+ recv-keys, and refresh-keys.
+
+ * options.h, g10.c (main): Give algorithm warnings for algorithms
+ chosen against the --pgpX and --openpgp rules.
+
+ * keydb.h, pkclist.c (algo_available): Make TIGER192 invalid in
+ --openpgp mode.
+
+ * sign.c (sign_file), pkclist.c (algo_available): Allow passing a
+ hint of 0.
+
+2003-05-01 David Shaw <dshaw@jabberwocky.com>
+
+ * tdbio.c (create_version_record): Only create new trustdbs with
+ TM_CLASSIC or TM_PGP.
+
+ * trustdb.h, trustdb.c (trust_string, get_ownertrust_string,
+ get_validity_string, ask_ownertrust, validate_keys), pkclist.c
+ (do_edit_ownertrust): Rename trust_string to trust_value_to_string
+ for naming consistency.
+
+ * trustdb.h, trustdb.c (string_to_trust_value): New function to
+ translate a string to a trust value.
+
+ * g10.c (main): Use string_to_trust_value here for
+ --force-ownertrust.
+
+ * options.h, g10.c (main), trustdb.c (trust_model_string,
+ init_trustdb, check_trustdb, update_trustdb, get_validity,
+ validate_one_keyblock): An "OpenPGP" trust model is misleading
+ since there is no official OpenPGP trust model. Use "PGP"
+ instead.
+
+2003-04-30 David Shaw <dshaw@jabberwocky.com>
+
+ * build-packet.c (build_sig_subpkt): Comments.
+
+ * exec.c (exec_write): Cast NULL to void* to properly terminate
+ varargs list.
+
+ * keyedit.c (show_key_with_all_names): Just for safety, catch an
+ invalid pk algorithm.
+
+ * sign.c (make_keysig_packet): Crucial that the call to mksubpkt
+ comes LAST before the calls to finalize the sig as that makes it
+ possible for the mksubpkt function to get a reliable pointer to
+ the subpacket area.
+
+ * pkclist.c (do_we_trust_pre): If an untrusted key was chosen by a
+ particular user ID, use that ID as the one to ask about when
+ prompting whether to use the key anyway.
+ (build_pk_list): Similar change here when adding keys to the
+ recipient list.
+
+ * trustdb.c (update_validity): Fix bug that prevented more than
+ one validity record per trust record.
+ (get_validity): When retrieving validity for a (user) supplied
+ user ID, return the validity for that user ID only, and do not
+ fall back to the general key validity.
+ (validate_one_keyblock): Some commentary on whether
+ non-self-signed user IDs belong in the web of trust (arguably,
+ they do).
+
+2003-04-27 David Shaw <dshaw@jabberwocky.com>
+
+ * g10.c (main): Add --no-textmode.
+
+ * export.c (do_export_stream), keyedit.c (show_key_with_all_names,
+ menu_addrevoker), mainproc.c (check_sig_and_print), photoid.c
+ (show_photos), sign.c (mk_notation_and_policy), trustdb.c
+ (get_validity, reset_trust_records, validate_keys): Make some
+ strings translatable.
+
+ * mainproc.c (check_sig_and_print): Show digest algorithm and sig
+ class when verifying a sig with --verbose on, and add version, pk
+ and hash algorithms and sig class to VALIDSIG.
+
+ * parse-packet.c (enum_sig_subpkt): Make a warning message a
+ --verbose warning message since we don't need to warn every time
+ we see an unknown critical (we only need to invalidate the
+ signature).
+
+ * trustdb.c (init_trustdb): Check the trustdb options even with
+ TM_AUTO since the auto may become TM_CLASSIC or TM_OPENPGP.
+
+2003-04-26 David Shaw <dshaw@jabberwocky.com>
+
+ * sign.c (do_sign): Show the hash used when making a signature in
+ verbose mode.
+
+ * tdbio.h, tdbio.c (tdbio_read_model): New function to return the
+ trust model used in a given trustdb.
+
+ * options.h, g10.c (main), trustdb.c (init_trustdb, check_trustdb,
+ update_trustdb): Use tdbio_read_model to implement an "auto" trust
+ model which is set via the trustdb.
+
+2003-04-23 David Shaw <dshaw@jabberwocky.com>
+
+ * import.c (import_revoke_cert): Remove ultimate trust when
+ revoking an ultimately trusted key.
+
+ * keyedit.c (sign_uids): Allow replacing expired signatures.
+ Allow duplicate signatures with --expert.
+
+ * pkclist.c (check_signatures_trust): Don't display a null
+ fingerprint when checking a signature with --always-trust enabled.
+
+ * filter.h (progress_filter_context_t), progress.c
+ (handle_progress), plaintext.c (ask_for_detached_datafile,
+ hash_datafiles): Fix compiler warnings. Make "what" constant.
+
+ * build-packet.c (do_plaintext): Do not create invalid literal
+ packets with >255-byte names.
+
+2003-04-15 Werner Koch <wk@gnupg.org>
+
+ * Makefile.am (AM_CFLAGS): Make use of AM_CFLAGS and AM_LDFLAGS.
+
+ * g10.c, options.h: New option --enable-progress-filter.
+ * progress.c (handle_progress): Make use of it.
+
+2003-04-15 Marcus Brinkmann <marcus@g10code.de>
+
+ * progress.c: New file.
+ * Makefile.am (common_source): Add progress.c.
+ * filter.h (progress_filter_context_t): New type.
+ (progress_filter, handle_progress): New prototypes.
+ * main.h (open_sigfile): New argument for prototype.
+ * openfile.c (open_sigfile): New argument to install progress
+ filter.
+ * encode.c (encode_simple): New variable PFX. Register
+ progress filter. Install text_filter after that.
+ (encode_crypt): Likewise.
+ * sign.c (sign_file): Likewise.
+ (clearsign_file): Likewise.
+ * decrypt.c (decrypt_message): Likewise.
+ (decrypt_messages): Likewise.
+ * verify.c (verify_signatures): Likewise.
+ (verify_one_file): Likewise.
+ * plaintext.c (hash_datafiles): Likewise.
+ (ask_for_detached_datafile): Likewise.
+
+2003-04-10 Werner Koch <wk@gnupg.org>
+
+ * passphrase.c (read_passphrase_from_fd): Do a dummy read if the
+ agent is to be used. Noted by Ingo Klöcker.
+ (agent_get_passphrase): Inhibit caching when we have no
+ fingerprint. This is required for key generation as well as for
+ symmetric only encryption.
+
+ * passphrase .c (agent_get_passphrase): New arg CANCELED.
+ (passphrase_to_dek): Ditto. Passed to above. Changed all
+ callers to pass NULL.
+ * seckey-cert.c (do_check): New arg CANCELED.
+ (check_secret_key): Terminate loop when canceled.
+
+ * keyedit.c (change_passphrase): Pass ERRTEXT untranslated to
+ passphrase_to_dek and translate where appropriate.
+ * seckey-cert.c (check_secret_key): Ditto.
+ * keygen.c (ask_passphrase): Ditto.
+ * passphrase.c (agent_get_passphrase): Translate the TRYAGAIN_TEXT.
+ Switch the codeset to utf-8.
+
+2003-04-09 Werner Koch <wk@gnupg.org>
+
+ * decrypt.c (decrypt_messages): Fixed error handling; the function
+ used to re-loop with same file after an error. Reported by Joseph
+ Walton.
+
+2003-04-08 David Shaw <dshaw@jabberwocky.com>
+
+ * main.h, g10.c (main), import.c (parse_import_options,
+ fix_pks_corruption): It's really PKS corruption, not HKP
+ corruption. Keep the old repair-hkp-subkey-bug command as an
+ alias.
+
+ * g10.c (main): Rename --no-version to --no-emit-version for
+ consistency. Keep --no-version as an alias.
+
+2003-04-04 David Shaw <dshaw@jabberwocky.com>
+
+ * pkclist.c (algo_available): PGP 8 can use the SHA-256 hash.
+
+ * sign.c (sign_file, clearsign_file, sign_symencrypt_file): Remove
+ unused code.
+
+2003-04-01 Werner Koch <wk@gnupg.org>
+
+ * mainproc.c (check_sig_and_print): Add primary key fpr to VALIDSIG
+ status.
+
+2003-03-24 David Shaw <dshaw@jabberwocky.com>
+
+ * keydb.h: Err on the side of making an unknown signature a SIG
+ rather than a CERT.
+
+ * import.c (delete_inv_parts): Discard any key signatures that
+ aren't key types (i.e. 0x00, 0x01, etc.)
+
+ * g10.c (main): Add deprecated option warning for
+ --list-ownertrust. Add --compression-algo alias for
+ --compress-algo. Change --version output strings to match
+ "showpref" strings, and make translatable.
+
+ * status.c (do_get_from_fd): Accept 'y' as well as 'Y' for
+ --command-fd boolean input.
+
+ * trustdb.c: Fix typo (DISABLE_REGEXP -> DISABLE_REGEX)
+
+ * keyedit.c (show_key_with_all_names_colon): Show no-ks-modify
+ flag.
+
+2003-03-11 David Shaw <dshaw@jabberwocky.com>
+
+ * options.h, g10.c (main), keyserver.c (kopts): Add "try-dns-srv"
+ keyserver option. Defaults to on.
+
+ * passphrase.c (agent_get_passphrase): Fix memory leak with
+ symmetric messages. Fix segfault with symmetric messages. Fix
+ incorrect prompt with symmetric messages.
+
+2003-03-10 Werner Koch <wk@gnupg.org>
+
+ * compress.c (init_uncompress): Use a 15 bit window size so that
+ the output of implementations which don't run for PGP 2
+ compatibility won't get garbled.
+
+2003-03-04 David Shaw <dshaw@jabberwocky.com>
+
+ * trustdb.c (validate_keys): Mask the ownertrust when building the
+ list of fully valid keys so that disabled keys are still counted
+ in the web of trust.
+ (get_ownertrust_with_min): Do the same for the minimum ownertrust
+ calculation.
+
+ * parse-packet.c (dump_sig_subpkt): Show the notation names for
+ not-human-readable notations. Fix cosmetic off-by-one length
+ counter.
+
+ * options.skel: Add explantion and commented-out
+ "no-mangle-dos-filenames".
+
+ * mainproc.c (proc_encrypted): Make string translatable.
+
+ * keyserver.c (keyserver_spawn): Quote ':', '%', and any 8-bit
+ characters in the uid strings sent to the keyserver helper.
+
+ * keyring.c (keyring_rebuild_cache): Lock the keyring while
+ rebuilding the signature caches to prevent another gpg from
+ tampering with the temporary copy.
+
+ * keygen.c (keygen_set_std_prefs): Include AES192 and AES256 in
+ default prefs.
+
+ * keyedit.c (show_prefs): Make strings translatable.
+
+ * keydb.c: Double the maximum number of keyrings to 40.
+
+ * gpgv.c (main): Fix bug #113 - gpgv should accept the
+ --ignore-time-conflict option.
+
+ * g10.c (main): --openpgp disables --pgpX. Double the amount of
+ secure memory to 32k (keys are getting bigger these days).
+
+ * Makefile.am: Makefile.am: Use @CAPLIBS@ to link in -lcap if we
+ are using capabilities.
+
+2003-02-26 David Shaw <dshaw@jabberwocky.com>
+
+ * keyserver.c (keyserver_spawn): Include various pieces of
+ information about the key in the data sent to the keyserver
+ helper. This allows the helper to use it in instructing a remote
+ server which may not have any actual OpenPGP smarts in parsing
+ keys.
+
+ * main.h, export.c (export_pubkeys_stream, do_export_stream): Add
+ ability to return only the first match in an exported keyblock for
+ keyserver usage. This should be replaced at some point with a
+ more flexible solution where each key can be armored seperately.
+
+2003-02-22 David Shaw <dshaw@jabberwocky.com>
+
+ * sign.c (sign_file): Do not push textmode filter onto an unopened
+ IOBUF (segfault). Noted by Marcus Brinkmann. Push and
+ reinitialize textmode filter for each file in a multiple file
+ list.
+
+ * packet.h, getkey.c (fixup_uidnode), keyedit.c (show_prefs): Set
+ and show the keyserver no-modify flag.
+
+ * keygen.c (add_keyserver_modify): New.
+ (keygen_upd_std_prefs): Call it here.
+ (keygen_set_std_prefs): Accept "ks-modify" and "no-ks-modify" as
+ prefs to set and unset keyserver modify flag.
+
+ * g10.c (main): Accept "s1" in addition to "idea" to match the
+ other ciphers.
+
+ * main.h, misc.c (idea_cipher_warn): We don't need this if IDEA
+ has been disabled.
+
+2003-02-21 David Shaw <dshaw@jabberwocky.com>
+
+ * keygen.c (keygen_set_std_prefs): Don't put AES or CAST5 in
+ default prefs if they are disabled.
+
+ * g10.c (main): Use 3DES instead of CAST5 if we don't have CAST5
+ support. Use 3DES for the s2k cipher in --openpgp mode.
+ (print_mds): #ifdef all of the optional digest algorithms.
+
+2003-02-12 David Shaw <dshaw@jabberwocky.com>
+
+ * keydb.h, getkey.c (classify_user_id, classify_user_id2): Make
+ 'exact' a per-desc item. Merge into one function since
+ 'force_exact' is no longer needed.
+ (key_byname): Use new classify_user_id function, and new exact
+ flag in KEYDB_SEARCH_DESC.
+
+ * keyring.h, keyring.c (keyring_search): Return an optional index
+ to show which KEYDB_SEARCH_DESC was the matching one.
+
+ * keydb.h, keydb.c (keydb_search): Rename to keydb_search2, and
+ pass the optional index to keyring_search. Add a macro version of
+ keydb_search that calls this new function.
+
+ * export.c (do_export_stream): If the keyid! syntax is used,
+ export only that specified key. If the key in question is a
+ subkey, export the primary plus that subkey only.
+
+2003-02-11 David Shaw <dshaw@jabberwocky.com>
+
+ * exec.c (set_exec_path): Add debugging line.
+
+ * g10.c (print_hex, print_mds): Print long hash strings a lot
+ neater. This assumes at least an 80-character display, as there
+ are a few other similar assumptions here and there. Users who
+ need unformatted hashes can still use with-colons. Check that
+ SHA384 and 512 are available before using them as they are no
+ longer always available.
+
+ * Makefile.am: Use a local copy of libexecdir along with @PACKAGE@
+ as GNUPG_LIBEXECDIR so it can be easily overridden at make time.
+
+2003-02-04 David Shaw <dshaw@jabberwocky.com>
+
+ * armor.c (parse_hash_header, armor_filter): Accept the new SHAs
+ in the armor Hash: header.
+
+ * g10.c (print_hex): Print long hash strings a little neater.
+ (print_mds): Add the new SHAs to the hash list.
+
+2003-02-02 David Shaw <dshaw@jabberwocky.com>
+
+ * keyedit.c (menu_revuid): Properly handle a nonselfsigned uid on
+ a v4 key (treat as a v4 revocation).
+
+ * import.c (print_import_check): Do not re-utf8 convert user IDs.
+
+2003-01-27 David Shaw <dshaw@jabberwocky.com>
+
+ * mainproc.c (list_node): Show signature expiration date in
+ with-colons sig records.
+
+ * keylist.c (list_keyblock_colon), mainproc.c (list_node): Show
+ trust sig information in with-colons sig records.
+
+2003-01-16 David Shaw <dshaw@jabberwocky.com>
+
+ * g10.c (add_group): Trim whitespace after a group name so it does
+ not matter where the user puts the = sign.
+
+ * options.skel: Comment out the first three lines in case someone
+ manually copies the skel file to their homedir.
+
+ * sign.c (clearsign_file): Only use pgp2mode with v3 keys and
+ MD5. This matches what we do when decoding such messages and
+ prevents creating a message (v3+RIPEMD/160) that we can't verify.
+
+ * sig-check.c (signature_check2): Use G10ERR_GENERAL as the error
+ for signature digest conflict. BAD_SIGN implies that a signature
+ was checked and we may try and print out a user ID for a key that
+ doesn't exist.
+
+2003-01-15 David Shaw <dshaw@jabberwocky.com>
+
+ * trustdb.c (init_trustdb, get_validity): Don't use a changed
+ trust model to indicate a dirty trustdb, and never auto-rebuild a
+ dirty trustdb with the "always" trust model.
+
+ * g10.c (add_group): Last commit missed the \t ;)
+
+2003-01-14 David Shaw <dshaw@jabberwocky.com>
+
+ * packet.h, parse-packet.c (setup_user_id), free-packet.c
+ (free_user_id), keydb.h, keyid.c (namehash_from_uid): New function
+ to rmd160-hash the contents of a user ID packet and cache it in
+ the uid object.
+
+ * keylist.c (list_keyblock_colon): Use namehash in field 8 of
+ uids. Show dates for creation (selfsig date), and expiration in
+ fields 6 and 7.
+
+ * trustdb.c (get_validity, get_validity_counts, update_validity):
+ Use new namehash function rather than hashing it locally.
+
+2003-01-14 Werner Koch <wk@gnupg.org>
+
+ * g10.c (add_group): Fixed group parsing to allow more than one
+ delimiter in a row and also allow tab as delimiter.
+
+2003-01-12 David Shaw <dshaw@jabberwocky.com>
+
+ * tdbio.c (tdbio_set_dbname): Fix assertion failure with
+ non-fully-qualified trustdb names.
+
+2003-01-11 David Shaw <dshaw@jabberwocky.com>
+
+ * trustdb.c (get_validity_info, get_ownertrust_info,
+ trust_letter): Simplify by returning a ? for error directly.
+
+ * keyedit.c (show_key_with_all_names): Use get_validity_string and
+ get_ownertrust_string to show full word versions of trust
+ (i.e. "full" instead of 'f').
+
+ * trustdb.h, trustdb.c (get_ownertrust_string,
+ get_validity_string): Same as get_ownertrust_info, and
+ get_validity_info, except returns a full string.
+
+ * trustdb.c (get_ownertrust_with_min): New. Same as
+ 'get_ownertrust' but takes the min_ownertrust value into account.
+
+2003-01-10 David Shaw <dshaw@jabberwocky.com>
+
+ * armor.c (armor_filter): Comment about PGP's end of line tab
+ problem.
+
+ * trustdb.h, trustdb.c (trust_letter): Make
+ static. (get_ownertrust_info, get_validity_info): Don't mask the
+ trust level twice.
+
+ * trustdb.h, gpgv.c, trustdb.c (get_validity, get_validity_info),
+ keylist.c (list_keyblock_colon), keyedit.c
+ (show_key_with_all_names_colon, menu_revuid): Pass a user ID in
+ rather than a namehash, so we only have to do the hashing in one
+ place.
+
+ * packet.h, pkclist.c (build_pk_list), free-packet.c
+ (release_public_key_parts): Remove unused namehash element for
+ public keys.
+
+2003-01-07 David Shaw <dshaw@jabberwocky.com>
+
+ * keygen.c (keygen_set_std_prefs): Warn when setting an IDEA
+ preference when IDEA is not available.
+
+2003-01-06 David Shaw <dshaw@jabberwocky.com>
+
+ * trustdb.c (get_validity_info): 'd' for disabled is not a
+ validity value any more.
+
+ * packet.h, tdbio.h, tdbio.c (tdbio_read_record,
+ tdbio_write_record), trustdb.c (update_validity): Store temporary
+ full & marginal counts in the trustdb.
+ (clear_validity, get_validity_counts): Return and clear temp
+ counts.
+ (store_validation_status): Keep track of which keyids have been
+ stored.
+ (validate_one_keyblock, validate_key_list): Use per-uid copies of
+ the full & marginal counts so they can be recalled for multiple
+ levels.
+ (validate_keys): Only use unused keys for each new round.
+ (reset_unconnected_keys): Rename to reset_trust_records, and only
+ skip specifically excluded records.
+
+ * keylist.c (print_capabilities): Show 'D' for disabled keys in
+ capabilities section.
+
+ * trustdb.c (is_disabled): Remove incorrect comment.
+
+2003-01-03 David Shaw <dshaw@jabberwocky.com>
+
+ * import.c (import_one): Only do the work to create the status
+ display for interactive import if status is enabled.
+
+ * keyring.c (keyring_search): skipfnc didn't work properly with
+ non-keyid searches. Noted by Stefan Bellon.
+
+ * getkey.c (merge_selfsigs_main): Remove some unused code and make
+ sure that the pk selfsigversion member accounts for 1F direct
+ sigs.
+
+2003-01-02 Werner Koch <wk@gnupg.org>
+
+ * keydb.c (keydb_add_resource): Don't assume that try_make_homedir
+ terminates but check again for the existence of the directory and
+ continue then.
+ * openfile.c (copy_options_file): Print a warning if the skeleton
+ file has active options.
+
+2002-12-29 David Shaw <dshaw@jabberwocky.com>
+
+ * getkey.c (merge_selfsigs_main), main.h, sig-check.c
+ (check_key_signature2): Pass the ultimately trusted pk directly to
+ check_key_signature2 to avoid going through the key selection
+ mechanism. This prevents a deadly embrace when two keys without
+ selfsigs each sign the other.
+
+2002-12-27 David Shaw <dshaw@jabberwocky.com>
+
+ * keyserver.c (keyserver_refresh): Don't print the "refreshing..."
+ line if there are no keys to refresh or if there is no keyserver
+ set.
+
+ * getkey.c (merge_selfsigs_main): Any valid user ID should make a
+ key valid, not just the last one. This also fixes Debian bug
+ #174276.
+
+2002-12-27 Stefan Bellon <sbellon@sbellon.de>
+
+ * import.c (print_import_check): Changed int to size_t.
+
+2002-12-27 David Shaw <dshaw@jabberwocky.com>
+
+ * keyedit.c (keyedit_menu, menu_revuid): Add "revuid" feature to
+ revoke a user ID. This is the same as issuing a revocation for
+ the self-signature, but a much simpler interface to do it.
+
+2002-12-26 David Shaw <dshaw@jabberwocky.com>
+
+ * keydb.h, getkey.c (key_byname): Flag to enable or disable
+ including disabled keys. Keys specified via keyid (i.e. 0x...)
+ are always included.
+
+ * getkey.c (get_pubkey_byname, get_seckey_byname2,
+ get_seckey_bynames), keyedit.c (keyedit_menu, menu_addrevoker):
+ Include disabled keys in these functions.
+
+ * pkclist.c (build_pk_list): Do not include disabled keys for -r
+ or the key prompt. Do include disabled keys for the default key
+ and --encrypt-to.
+
+ * trustdb.h, trustdb.c (is_disabled): New skipfnc for skipping
+ disabled keys.
+
+ * gpgv.c (is_disabled): Stub.
+
+ * keygen.c (keygen_add_key_expire): Properly handle updating a key
+ expiration to a no-expiration value.
+
+ * keyedit.c (enable_disable_key): Comment.
+
+ * import.c (import_one): When in interactive mode and --verbose,
+ don't repeat some key information twice.
+
+2002-12-22 Timo Schulz <ts@winpt.org>
+
+ * import.c (print_import_check): New.
+ (import_one): Use it here.
+ Use merge_keys_and_selfsig in the interactive mode to avoid
+ wrong key information.
+ * status.h: Add new status code.
+ * status.c: Ditto.
+
+2002-12-13 David Shaw <dshaw@jabberwocky.com>
+
+ * pkclist.c (do_we_trust): Tweak language to refer to the "named
+ user" rather than "owner". Noted by Stefan Bellon.
+
+ * trustdb.h, trustdb.c (trustdb_pending_check): New function to
+ check if the trustdb needs a check.
+
+ * import.c (import_keys_internal): Used here so we don't rebuild
+ the trustdb if it is still clean.
+ (import_one, chk_self_sigs): Only mark trustdb dirty if the key
+ that is being imported has any sigs other than self-sigs.
+ Suggested by Adrian von Bidder.
+
+ * options.skel: Include the required '=' sign in the sample
+ 'group' option. Noted by Stefan Bellon.
+
+ * import.c (chk_self_sigs): Don't try and check a subkey as if it
+ was a signature.
+
+2002-12-11 David Shaw <dshaw@jabberwocky.com>
+
+ * tdbio.c (tdbio_read_record, tdbio_write_record): Compact the
+ RECTYPE_TRUST records a bit.
+
+ * g10.c (main): Comment out --list-trust-path until it can be
+ implemented.
+
+ * import.c (import_one): Warn when importing an Elgamal primary
+ that this may take some time (to verify self-sigs).
+ (chk_self_sigs): Try and cache all self-sigs so the keyblock is
+ written to the keyring with a good rich cache.
+
+ * keygen.c (ask_algo): Make the Elgamal sign+encrypt warning
+ stronger, and remove the RSA sign+encrypt warning.
+
+2002-12-06 Stefan Bellon <sbellon@sbellon.de>
+
+ * options.h: Fixed typo (mangle_dos_names instead of
+ mangle_dos_filenames).
+
+2002-12-05 Werner Koch <wk@gnupg.org>
+
+ * g10.c: New options --[no-]mangle-dos-filenames.
+ * options.h (opt): Added mangle-dos-filenames.
+ * openfile.c (open_outfile) [USE_ONLY_8DOT3]: Truncate the
+ filename only when this option is set; this is the default.
+
+2002-12-04 David Shaw <dshaw@jabberwocky.com>
+
+ * main.h, keyedit.c, keygen.c: Back out previous (2002-12-01)
+ change. Minimal isn't always best.
+
+ * sign.c (update_keysig_packet): Use the current time rather then
+ a modification of the original signature time. Make sure that
+ this doesn't cause a time warp.
+
+ * keygen.c (keygen_add_key_expire): Properly handle a key
+ expiration date in the past (use a duration of 0).
+
+ * keyedit.c (menu_expire): Use update_keysig_packet so any sig
+ subpackets are maintained during the update.
+
+ * build-packet.c (build_sig_subpkt): Mark sig expired or unexpired
+ when the sig expiration subpacket is added.
+ (build_sig_subpkt_from_sig): Handle making an expiration subpacket
+ from a sig that has already expired (use a duration of 0).
+
+ * packet.h, sign.c (update_keysig_packet), keyedit.c
+ (menu_set_primary_uid, menu_set_preferences): Add ability to issue
+ 0x18 subkey binding sigs to update_keysig_packet and change all
+ callers.
+
+ * trustdb.c (validate_keys): Show trust parameters when building
+ the trustdb, and make sure that the version record update was
+ successful.
+ (init_trustdb): If the current parameters aren't what was used for
+ building the trustdb, the trustdb is invalid.
+
+ * tbio.c (tdbio_db_matches_options): Update to work with new
+ trustdbs.
+
+2002-12-03 David Shaw <dshaw@jabberwocky.com>
+
+ * tdbio.h, tdbio.c (tdbio_read_record, tdbio_write_record): Store
+ trust model in the trustdb version record.
+ (tdbio_update_version_record): New function to update version
+ record values during a trustdb check or update.
+ (tdbio_dump_record): Show trust model in dump.
+
+ * trustdb.c (validate_keys): Call tdbio_update_version_record on
+ success so that the correct options are stored in the trustdb.
+
+ * options.h: rearrange trust models so that CLASSIC is 0 and
+ OPENPGP is 1.
+
+ * options.h, g10.c (main), encode.c (write_pubkey_enc_from_list),
+ pkclist.c (algo_available), revoke.c (gen_revoke): Add --pgp8
+ mode. This is basically identical to --pgp7 in all ways except
+ that signing subkeys, v4 data sigs (including expiration), and SK
+ comments are allowed.
+
+ * getkey.c (finish_lookup): Comment.
+
+ * main.h, keylist.c (reorder_keyblock), keyedit.c (keyedit_menu):
+ Reorder user ID display in the --edit-key menu to match that of
+ the --list-keys display.
+
+ * g10.c (add_notation_data): Fix initialization.
+
+2002-12-01 David Shaw <dshaw@jabberwocky.com>
+
+ * keyedit.c (menu_expire): Don't lose key flags when changing the
+ expiration date of a subkey. This is not the most optimal
+ solution, but it is minimal change on the stable branch.
+
+ * main.h, keygen.c (do_copy_key_flags): New function to copy key
+ flags, if any, from one sig to another.
+ (do_add_key_expire): New function to add key expiration to a sig.
+ (keygen_copy_flags_add_expire): New version of
+ keygen_add_key_expire that also copies key flags.
+ (keygen_add_key_flags_and_expire): Use do_add_key_expire.
+
+ * import.c (fix_hkp_corruption): Comment.
+
+2002-11-25 Stefan Bellon <sbellon@sbellon.de>
+
+ * plaintext.c (handle_plaintext) [__riscos__]: If nooutput is set,
+ no filetype is needed obviously.
+
+2002-11-24 David Shaw <dshaw@jabberwocky.com>
+
+ * main.h, misc.c (default_cipher_algo, default_compress_algo):
+ New. Return the default algorithm by trying
+ --cipher-algo/--compress-algo, then the first item in the pref
+ list, then s2k-cipher-algo or ZIP.
+
+ * sign.c (sign_file, sign_symencrypt_file), encode.c
+ (encode_simple, encode_crypt): Call default_cipher_algo and
+ default_compress_algo to get algorithms.
+
+ * g10.c (main): Allow pref selection for compress algo with
+ --openpgp.
+
+ * mainproc.c (proc_encrypted): Use --s2k-digest-algo for
+ passphrase mangling rather than --digest-algo.
+
+ * sign.c (hash_for): If --digest-algo is not set, but
+ --personal-digest-preferences is, then use the first hash
+ algorithm in the personal list. If the signing algorithm is DSA,
+ then use the first 160-bit hash algorithm in the personal list.
+ If --pgp2 is set and it's a v3 RSA key, use MD5.
+
+ * g10.c (main), keydb.c (keydb_add_resource,
+ keydb_locate_writable): Rename --default-keyring as
+ --primary-keyring. Stefan wins the naming contest.
+
+2002-11-23 David Shaw <dshaw@jabberwocky.com>
+
+ * g10.c (add_notation_data): Disallow notation names that do not
+ contain a '@', unless --expert is set. This is to help prevent
+ people from polluting the (as yet unused) IETF namespace.
+
+ * main.h: Comments about default algorithms.
+
+ * photoid.c (image_type_to_string): Comments about 3-letter file
+ extensions.
+
+ * encode.c (encode_simple), passphrase.c (passphrase_to_dek),
+ sign.c (sign_symencrypt_file): Use --s2k-digest-algo for
+ passphrase mangling rather than --digest-algo.
+
+2002-11-21 David Shaw <dshaw@jabberwocky.com>
+
+ * keygen.c (keygen_set_std_prefs): Properly handle an empty
+ preference string.
+
+ * misc.c (string_to_compress_algo): "none" is a bad choice since
+ it conflicts with the "none" in setpref.
+
+2002-11-14 David Shaw <dshaw@jabberwocky.com>
+
+ * g10.c (main): Allow compression algorithm names as the argument
+ to --compress-algo. The old algorithm names still work for
+ backwards compatibility.
+
+ * misc.c (string_to_compress_algo): Allow "none" as an alias for
+ "uncompressed".
+
+2002-11-13 Stefan Bellon <sbellon@sbellon.de>
+
+ * getkey.c (get_pubkey_byfprint_fast): Fixed type incompatibility,
+ was unsigned char instead of byte.
+
+2002-11-13 David Shaw <dshaw@jabberwocky.com>
+
+ * encode.c (encode_simple): Make sure that files larger than about
+ 4G use partial length encoding. This is required because OpenPGP
+ allows only for 32 bit length fields. From Werner on stable
+ branch.
+
+ * getkey.c (get_pubkey_direct): Renamed to...
+ (get_pubkey_fast): this and made extern.
+ (get_pubkey_byfprint_fast): New. From Werner on stable branch.
+
+ * keydb.h, import.c (import_one): Use get_pubkey_fast instead of
+ get_pubkey. We don't need a merged key and actually this might
+ lead to recursions.
+ (revocation_present): Likewise for search by fingerprint. From
+ Werner on stable branch.
+
+ * g10.c (main): Try to create the trustdb even for non-colon-mode
+ list-key operations. This is required because getkey needs to
+ know whether a a key is ultimately trusted. From Werner on stable
+ branch.
+
+ * exec.c [__CYGWIN32__]: Keep cygwin separate from Mingw32;
+ we don't need it here as it behaves more like a Posix system.
+ From Werner on stable branch.
+
+ * passphrase.c (agent_get_passphrase): Ditto. From Werner on
+ stable branch.
+
+ * tdbio.c (MY_O_BINARY): Need binary mode with Cygwin. From
+ Werner on stable branch.
+
+ * g10.c, gpgv.c (main) [__CYGWIN32__]: Don't get the homedir from
+ the registry. From Werner on stable branch.
+
+ * keyedit.c (show_key_with_all_names_colon): Make --with-colons
+ --edit display match the validity and trust of --with-colons
+ --list-keys.
+
+ * passphrase.c (agent_send_all_options): Fix compile warning.
+
+ * keylist.c (list_keyblock_colon): Validity for subkeys should
+ match that of the primary key, and not that of the last user ID.
+
+ * getkey.c (merge_selfsigs): Revoked/expired/invalid primary keys
+ carry these facts onto all their subkeys, but only after the
+ subkey has a chance to be marked valid. This is to fix an
+ incorrect "invalid public key" error verifying a signature made by
+ a revoked signing subkey, with a valid unrevoked primary key.
+
+2002-11-09 Werner Koch <wk@gnupg.org>
+
+ * passphrase.c (agent_send_all_options): Use tty_get_ttyname to
+ get the default ttyname.
+
+2002-11-07 David Shaw <dshaw@jabberwocky.com>
+
+ * keyring.h, keyring.c (keyring_register_filename): Return the
+ pointer if a given keyring is registered twice.
+
+ * keydb.h, keydb.c (keydb_add_resource): Use flags to indicate a
+ default keyring.
+ (keydb_locate_writable): Prefer the default keyring if possible.
+
+ * g10.c (main): Add --default-keyring option.
+
+2002-11-06 David Shaw <dshaw@jabberwocky.com>
+
+ * options.h, g10.c (main), trustdb.c (ask_ownertrust): Add
+ --force-ownertrust option for debugging purposes. This allows
+ setting a whole keyring to a given trust during an
+ --update-trustdb. Not for normal use - it's just easier than
+ hitting "4" all the time to test a large trustdb.
+
+ * pubkey-enc.c (get_session_key): With hidden recipients or try a
+ given passphrase against all secret keys rather than trying all
+ secret keys in turn. Don't if --try-all-secrets or --status-fd is
+ enabled.
+
+ * passphrase.c (passphrase_to_dek): Mode 1 means do a regular
+ passphrase query, but don't prompt with the key info.
+
+ * seckey-cert.c (do_check, check_secret_key): A negative ask count
+ means to enable passphrase mode 1.
+
+ * keydb.h, getkey.c (enum_secret_keys): Add flag to include
+ secret-parts-missing keys (or not) in the list.
+
+2002-11-05 David Shaw <dshaw@jabberwocky.com>
+
+ * keyserver.c (keyserver_search_prompt): When --with-colons is
+ enabled, don't try and fit the search output to the screen size -
+ just dump the whole list.
+
+2002-11-04 David Shaw <dshaw@jabberwocky.com>
+
+ * keyserver.c (keyserver_search_prompt): When --with-colons is
+ enabled, just dump the raw keyserver protocol to stdout and don't
+ print the menu.
+
+ * keyserver.c (show_prompt): Don't show a prompt when command-fd
+ is being used.
+
+ * trustdb.c (trust_model_string, check_trustdb, update_trustdb,
+ validate_one_keyblock): It's not clear what a trustdb rebuild or
+ check means with a trust model other than "classic" or "openpgp",
+ so disallow this.
+
+2002-11-03 David Shaw <dshaw@jabberwocky.com>
+
+ * options.h, g10.c (main): Add --trust-model option. Current
+ models are "openpgp" which is classic+trustsigs, "classic" which
+ is classic only, and "always" which is the same as the current
+ option --always-trust (which still works). Default is "openpgp".
+
+ * trustdb.c (validate_one_keyblock): Use "openpgp" trust model to
+ enable trust sigs.
+
+ * gpgv.c (main), mainproc.c (check_sig_and_print), pkclist.c
+ (do_we_trust, do_we_trust_pre, check_signatures_trust): Use new
+ --trust-model option in place of --always-trust.
+
+ * keyedit.c (sign_mk_attrib, trustsig_prompt, sign_uids,
+ keyedit_menu): Prompt for and create a trust signature with
+ "tsign". This is functional, but needs better UI text.
+
+ * build-packet.c (build_sig_subpkt): Able to build trust and
+ regexp subpackets.
+
+ * pkclist.c (do_edit_ownertrust): Comment.
+
+2002-11-02 David Shaw <dshaw@jabberwocky.com>
+
+ * keygen.c (set_one_pref, keygen_set_std_prefs): Allow using the
+ full algorithm name (CAST5, SHA1) rather than the short form (S3,
+ H2).
+
+ * main.h, keygen.c (keygen_get_std_prefs), keyedit.c
+ (keyedit_menu): Return and use a fake uid packet rather than a
+ string since we already have a nice parser/printer in
+ keyedit.c:show_prefs.
+
+ * main.h, misc.c (string_to_compress_algo): New.
+
+2002-11-01 David Shaw <dshaw@jabberwocky.com>
+
+ * g10.c (main): Add --no-throw-keyid.
+
+ * keydb.h, encode.c (write_pubkey_enc_from_list), g10.c (main),
+ pkclist.c (build_pk_list): Add --hidden-recipient (-R) and
+ --hidden-encrypt-to, which do a single-user variation on
+ --throw-keyid. The "hide this key" flag is carried in bit 0 of
+ the pk_list flags field.
+
+ * keyserver.c (parse_keyrec): Fix shadowing warning.
+
+2002-10-31 Stefan Bellon <sbellon@sbellon.de>
+
+ * compress.c (init_compress) [__riscos__]: Use
+ riscos_load_module() to load ZLib module.
+
+ * g10.c (main) [__riscos__]: Renames due to changes in riscos.c
+ (e.g. prefixes all RISC OS specific functions with riscos_*).
+ * photoid.c (show_photos) [__riscos__]: Likewise.
+ * signal.c (got_fatal_signal) [__riscos__]: Likewise.
+
+ * trustdb.c (check_regexp) [__riscos__]: Branch to RISC OS RegEx
+ handling.
+
+2002-10-31 David Shaw <dshaw@jabberwocky.com>
+
+ * build-packet.c (do_plaintext), encode.c (encode_sesskey,
+ encode_simple, encode_crypt), sign.c (write_plaintext_packet): Use
+ wipememory() instead of memset() to wipe sensitive memory as the
+ memset() might be optimized away.
+
+2002-10-30 David Shaw <dshaw@jabberwocky.com>
+
+ * trustdb.c (check_regexp): Modern regexps require REG_EXTENDED.
+
+2002-10-29 David Shaw <dshaw@jabberwocky.com>
+
+ * packet.h, trustdb.h, trustdb.c (trust_string): New. Return a
+ string like "fully trusted", "marginally trusted", etc.
+ (get_min_ownertrust): New. Return minimum ownertrust.
+ (update_min_ownertrust): New. Set minimum ownertrust.
+ (check_regexp): New. Check a regular epression against a user ID.
+ (ask_ownertrust): Allow specifying a minimum value.
+ (get_ownertrust_info): Follow the minimum ownertrust when
+ returning a letter.
+ (clear_validity): Remove minimum ownertrust when a key becomes
+ invalid.
+ (release_key_items): Release regexp along with the rest of the
+ info.
+ (validate_one_keyblock, validate_keys): Build a trust sig chain
+ while validating. Call check_regexp for regexps. Use the minimum
+ ownertrust if the user does not specify a genuine ownertrust.
+
+ * pkclist.c (do_edit_ownertrust): Only allow user to select a
+ trust level greater than the minimum value.
+
+ * parse-packet.c (can_handle_critical): Can handle critical trust
+ and regexp subpackets.
+
+ * trustdb.h, trustdb.c (clear_ownertrusts), delkey.c
+ (do_delete_key), import.c (import_one): Rename clear_ownertrust to
+ clear_ownertrusts and have it clear the min_ownertrust value as
+ well.
+
+ * keylist.c (list_keyblock_print): Indent uid to match pub and
+ sig.
+
+ * keyedit.c (print_and_check_one_sig, show_key_and_fingerprint,
+ menu_addrevoker), keylist.c (list_keyblock_print,
+ print_fingerprint): Show "T" or the trust depth for trust
+ signatures, and add spaces to some strings to make room for it.
+
+ * packet.h, parse-packet.c (dump_sig_subpkt, parse_one_sig_subpkt,
+ parse_signature): Parse trust signature values.
+
+ * tdbio.h, tdbio.c (tdbio_read_record, tdbio_write_record):
+ Reserve a byte for the minimum ownertrust value (for use with
+ trust signatures).
+
+2002-10-29 Stefan Bellon <sbellon@sbellon.de>
+
+ * build-packet.c (calc_plaintext, do_plaintext): Removed RISC OS
+ specific filetype parts (it's now done in make_basename()).
+
+ * plaintext.c (handle_plaintext): Tidied up RISC OS specific
+ filetype parts.
+
+ * encode.c (encode_simple, encode_crypt): Added argument to
+ make_basename() call.
+
+ * sign.c (write_plaintext_packet): Added argument to
+ make_basename() call.
+
+2002-10-28 Stefan Bellon <sbellon@sbellon.de>
+
+ * build-packet.c (calc_plaintext, do_plaintext): Added filetype
+ handling for RISC OS' file types.
+
+ * plaintext.c (handle_plaintext) [__riscos__]: Added filetype
+ handling for RISC OS' file types.
+
+2002-10-23 David Shaw <dshaw@jabberwocky.com>
+
+ * main.h, import.c (sec_to_pub_keyblock, import_secret_one,
+ parse_import_options), g10.c (main): New import-option
+ "convert-sk-to-pk" to convert a secret key into a public key
+ during import. It is on by default.
+
+2002-10-23 Werner Koch <wk@gnupg.org>
+
+ * pubkey-enc.c (get_it): Fix segv, test for revoked only when PK
+ has been assigned.
+
+2002-10-18 Timo Schulz <ts@winpt.org>
+
+ * keylist.c: (print_pubkey_info): New.
+ (print_seckey_info): New.
+ * main.h: Prototypes for the new functions.
+ * delkey.c (do_delete_key): Use it here.
+ * revoke.c (gen_desig_revoke): Ditto.
+
+2002-10-17 Werner Koch <wk@gnupg.org>
+
+ * pkclist.c (do_edit_ownertrust): Show all user IDs. This should
+ be enhanced to also show the current trust level. Suggested by
+ Florian Weimer.
+
+2002-10-17 David Shaw <dshaw@jabberwocky.com>
+
+ * g10.c (main): Handle --strict and --no-strict from the command
+ line before the options file is loaded.
+
+2002-10-15 David Shaw <dshaw@jabberwocky.com>
+
+ * g10.c (main): Disable --textmode when encrypting (symmetric or
+ pk) in --pgp2 mode as PGP 2 can't handle the unknown length
+ literal packet. Reported by Michael Richardson.
+
+2002-10-14 David Shaw <dshaw@jabberwocky.com>
+
+ * keyserver-internal.h, keyserver.c (print_keyrec, parse_keyrec,
+ show_prompt, keyserver_search_prompt, keyserver_spawn): Go to
+ version 1 of the keyserver protocol. This is a better design,
+ similar to --with-colons, that allows for keys with multiple user
+ IDs rather than using multiple keys. It also matches the machine
+ readable pksd format. Also use a prettier --search-keys listing
+ format that can fill different size windows (currently set at 24
+ lines).
+
+2002-10-12 Werner Koch <wk@gnupg.org>
+
+ * keygen.c (print_status_key_created): New.
+ (do_generate_keypair): Use it to print the fingerprint.
+ (generate_subkeypair): Likewise.
+
+2002-10-11 David Shaw <dshaw@jabberwocky.com>
+
+ * keyedit.c (menu_addrevoker): Properly back out if the signature
+ fails. Also, do not allow appointing the same revoker twice, and
+ report ALREADY_SIGNED if the user tries it.
+
+2002-10-07 David Shaw <dshaw@jabberwocky.com>
+
+ * import.c (import_keys_internal): Missed one s/inp/inp2/.
+
+ * keylist.c (print_capabilities): Properly indicate per-key
+ capabilities of sign&encrypt primary keys that have
+ secret-parts-missing (i.e. no capabilities at all)
+
+ * mainproc.c (symkey_decrypt_sesskey): Fix compiler warning.
+
+2002-10-04 David Shaw <dshaw@jabberwocky.com>
+
+ * getkey.c (get_pubkey_direct): Don't cache keys retrieved via
+ this function as they may not have all their fields filled in.
+
+ * sig-check.c (signature_check2): Use new is_primary flag to check
+ rather than comparing main_keyid with keyid as this still works in
+ the case of a not fully filled in pk.
+
+2002-10-04 Werner Koch <wk@gnupg.org>
+
+ * import.c (import_keys_internal): s/inp/inp2/ to avoid shadowing
+ warning.
+
+ * passphrase.c (agent_get_passphrase): Fixed signed/unsigned char
+ problem in %-escaping. Noted by Ingo Klöcker.
+
+2002-10-03 David Shaw <dshaw@jabberwocky.com>
+
+ * options.h, g10.c (main): Add --strict and --no-strict to switch
+ the log_warning severity level from info to error.
+
+ * keylist.c (print_capabilities): Secret-parts-missing keys should
+ show that fact in the capabilities, and only primary signing keys
+ can certify other keys.
+
+ * packet.h, parse_packet.c (parse_key): Add is_primary flag for
+ public keys (it already exists for secret keys).
+
+2002-10-02 David Shaw <dshaw@jabberwocky.com>
+
+ * import.c (import_secret_one): Check for an illegal (>110)
+ protection cipher when importing a secret key.
+
+ * keylist.c (list_keyblock_print): Show a '#' for a
+ secret-parts-missing key.
+
+ * parse_packet.c (parse_key): Some comments.
+
+ * revoke.c (gen_revoke): Remove some debugging code.
+
+ * trustdb.c (verify_own_keys): Make trusted-key a non-deprecated
+ option again.
+
+ * seckey-cert.c (do_check): Don't give the IDEA warning unless the
+ cipher in question is in fact IDEA.
+
+2002-10-01 David Shaw <dshaw@jabberwocky.com>
+
+ * import.c (import_one): Make sure that a newly imported key
+ starts with a clean ownertrust.
+
+2002-10-01 Werner Koch <wk@gnupg.org>
+
+ * getkey.c (get_pubkey_direct): New.
+ (merge_selfsigs_main): Use it here to look for an ultimately
+ trusted key. Using the full get_pubkey might lead to an
+ infinitive recursion.
+
+2002-09-29 David Shaw <dshaw@jabberwocky.com>
+
+ * keyserver.c (parse_keyserver_uri): Force the keyserver URI
+ scheme to lowercase to be case-insensitive.
+
+2002-09-28 David Shaw <dshaw@jabberwocky.com>
+
+ * export.c (do_export_stream): Comment.
+
+ * sig-check.c (check_key_signature2): Properly handle a
+ non-designated revocation import.
+
+2002-09-26 Werner Koch <wk@gnupg.org>
+
+ * g10.c (set_homedir): New. Changed all direct assignments to use
+ this.
+ * gpgv.c (set_homedir): Ditto.
+
+2002-09-25 David Shaw <dshaw@jabberwocky.com>
+
+ * Makefile.am: Link gpg with EGDLIBS (i.e. NETLIBS) as EGD uses
+ sockets. Remove the old NETLIBS variable since the keyserver
+ stuff is no longer internal.
+
+2002-09-24 David Shaw <dshaw@jabberwocky.com>
+
+ * import.c (import_keys_stream): Fix compiler type warning.
+
+ * keyring.c (keyring_rebuild_cache), sig-check.c
+ (check_key_signature2), import.c (import, chk_self_sigs): Minor
+ language cleanups.
+
+2002-09-23 Stefan Bellon <sbellon@sbellon.de>
+
+ * main.h: Introduced fast-import as import option. Removed
+ fast as separate option from prototypes.
+ * import.c (parse_import_options): Added fast-import option.
+ (import_*): Removed fast as separate option.
+ * g10.c (main): Added option fast-import, removed old fast
+ as separate argument.
+ * keyserver.c (keyserver_spawn): Removed old fast as separate
+ argument.
+
+2002-09-22 Stefan Bellon <sbellon@sbellon.de>
+
+ * import.c (import_keys, import_keys_stream,
+ import_keys_internal): Added trustdb update/check to key import if
+ not fast-import and interactive set/no-auto-check-trustdb unset.
+ Avoided function clone by introducing import_keys_internal.
+
+2002-09-19 David Shaw <dshaw@jabberwocky.com>
+
+ * keyserver.c (keyserver_spawn): Properly handle line truncation.
+ Don't leak memory (~10-20 bytes) on searches.
+ (keyserver_search_prompt): Cleanup.
+
+ * keylist.c (list_keyblock_colon): Show 1F direct key signatures
+ in --with-colons listing.
+
+2002-09-16 David Shaw <dshaw@jabberwocky.com>
+
+ * keyedit.c (menu_addrevoker): The direct key signature for
+ revocation keys must be at least v4 to carry the revocation key
+ subpacket. Add a PGP 2.x warning for revocation keys.
+
+2002-09-14 David Shaw <dshaw@jabberwocky.com>
+
+ * g10.c (check_permissions): Rearrange strings to make translating
+ easier (don't incorporate string parts).
+
+ * keyedit.c (sign_uids): Make strings translatable.
+
+ * sig-check.c (check_key_signature2): Make string translatable.
+
+2002-09-13 David Shaw <dshaw@jabberwocky.com>
+
+ * getkey.c (check_revocation_keys): Move....
+ * main.h, sig-check.c (check_revocation_keys): to here. Also
+ return the signature_check error code rather than 0/1 and cache
+ the sig result.
+
+ * sig-check.c (check_key_signature2): Divert to
+ check_revocation_keys if a revocation sig is made by someone other
+ than the pk owner.
+
+ * getkey.c (merge_selfsigs_main): Tidy.
+
+2002-09-13 Werner Koch <wk@gnupg.org>
+
+ * g10.c (main) [__MINGW32__]: Activate oLoadExtension.
+
+2002-09-12 David Shaw <dshaw@jabberwocky.com>
+
+ * Makefile.am, hkp.c, hkp.h, keyserver.c (keyserver_work): Remove
+ internal HKP support.
+
+ * keyserver.c (keyserver_spawn): Remove whitespace after keyserver
+ commands.
+
+2002-09-10 David Shaw <dshaw@jabberwocky.com>
+
+ * exec.c (expand_args): Remove loop left over from earlier
+ implementation.
+ (exec_write): Missed one tick.
+
+2002-09-10 Werner Koch <wk@gnupg.org>
+
+ * g10.c, options.h: Removed option --emulate-checksum-bug.
+ * misc.c (checksum_u16_nobug): Removed.
+ (checksum_u16): Removed the bug emulation.
+ (checksum_mpi): Ditto.
+ (checksum_mpi_counted_nbits): Removed and replaced all calls
+ with checksum_mpi.
+
+ * parse-packet.c (read_protected_v3_mpi): New.
+ (parse_key): Use it here to store it as an opaque MPI.
+ * seckey-cert.c (do_check): Changed the v3 unprotection to the new
+ why to store these keys.
+ (protect_secret_key): Likewise.
+ * build-packet.c (do_secret_key): And changed the writing.
+
+ * tdbio.c (tdbio_set_dbname, open_db): Use new macro MY_O_BINARY
+ to avoid silly ifdefs.
+ (open_db): Fallback to RDONLY so that gpg may be used from a
+ RO-medium.
+
+ * encode.c (encode_simple): Make sure we don't use an ESK packet
+ when we don't have a salt in the S2K.
+
+ * misc.c (pct_expando) <case f>: Make sure that LEN is initialized.
+
+ * exec.c (exec_finish): Use ticks to denote filenames in messages.
+ (make_tempdir, exec_write): Changed format of messages.
+
+ * keyserver.c (print_keyinfo): Release USERID in on error.
+ (keyserver_work) [!DISABLE_KEYSERVER_HELPERS]: Exclude the unused
+ code.
+
+2002-09-09 Werner Koch <wk@gnupg.org>
+
+ * parse-packet.c (make_attribute_uidname): Add new ar MAX_NAMELEN
+ for sanity checks. Changed both callers. Limit the size of an %s.
+
+ * options.skel: Comment lock-once out, so that this file does not
+ change anything when copied to a new home directory.
+ * openfile.c (try_make_homedir): Don't exit after copying the
+ option skeleton.
+
+ * options.h: Don't use a comma when declaring variables over more
+ than one line.
+
+ * mainproc.c (symkey_decrypt_sesskey): Check length of the session
+ key.
+
+ * hkp.c (dehtmlize): Use ascii_tolower to protect against weird
+ locales. Cast the argument for isspace for the sake of broken
+ HP/UXes.
+ (parse_hkp_index): s/ascii_memcasecmp/ascii_strncasecmp/.
+
+ * g10.c: Removed option --emulate-3des-s2k-bug.
+
+ * passphrase.c (hash_passphrase): Was used here.
+
+ * export.c (parse_export_options)
+ * keyserver.c (parse_keyserver_options)
+ * import.c (parse_import_options)
+ * g10.c (check_permissions): s/ascii_memcasecmp/ascii_strncasecmp/.
+
+2002-09-09 David Shaw <dshaw@jabberwocky.com>
+
+ * g10.c (add_group): Use '=' to separate group name from group
+ members. Use a better error message for when no = is found.
+
+ * hkp.c (hkp_export): Use CRLF in headers.
+
+2002-09-03 David Shaw <dshaw@jabberwocky.com>
+
+ * mainproc.c (print_pkenc_list): Don't increment the error counter
+ when printing the list of keys a message was encrypted to. This
+ would make gpg give a non-zero exit code even for completely valid
+ messages if the message was encrypted to more than one key that
+ the user owned.
+
+2002-09-02 Werner Koch <wk@gnupg.org>
+
+ * g10.c (main): Try to set a default character set. Print the
+ used one in verbosity level 3.
+ * gpgv.c (main): Try to set a default character set.
+
+ * status.c, status.h (STATUS_IMPORT_OK): New.
+ * import.c (import_one,import_secret_one): Print new status.
+
+2002-08-30 David Shaw <dshaw@jabberwocky.com>
+
+ * pkclist.c (build_pk_list): Add new status code to indicate an
+ untrusted user. This (or a disabled key) fail with "unavailable
+ pubkey" (G10ERR_UNU_PUBKEY).
+
+ * pkclist.c (build_pk_list): Fail if any recipient keys are
+ unusable.
+
+ * options.skel: The PGP LDAP keyserver is back. Use MIT keyserver
+ as a sample rather than cryptnet as cryptnet does not support
+ searching yet.
+
+ * keyedit.c (show_key_with_all_names): Fix error message
+ (preferences are userid/selfsig and not key specific).
+
+2002-08-30 Werner Koch <wk@gnupg.org>
+
+ * pkclist.c (do_we_trust_pre): Changed the wording of a warning.
+
+ * encode.c (encode_simple,encode_crypt): Use new style CTB for
+ compressssed packets when using MDC. We need to do this so that
+ concatenated messages are properly decrypted. Old style
+ compression assumes that it is the last packet; given that we
+ can't determine the length in advance, the uncompressor does not
+ know where to start. Actually we should use the new CTB always
+ but this would break PGP 2 compatibility.
+
+ * parse-packet.c (parse): Special treatment for new style CTB
+ compressed packets.
+
+ * build-packet.c (do_mdc): Removed. Was not used.
+ (do_encrypted_mdc): Count in the version number and the MDC packet.
+
+2002-08-28 David Shaw <dshaw@jabberwocky.com>
+
+ * sig-check.c (do_check_messages, do_check): Show keyid in error
+ messages.
+
+ * keyserver.c (print_keyinfo): More readable key listings for
+ --search-keys responses.
+
+2002-08-26 David Shaw <dshaw@jabberwocky.com>
+
+ * hkp.c (parse_hkp_index, dehtmlize): Move HTML functionality into
+ new "dehtmlize" function. Remove HTML before trying to parse each
+ line from the keyserver. If the keyserver provides key type
+ information in the listing, use it.
+
+2002-08-23 David Shaw <dshaw@jabberwocky.com>
+
+ * sig-check.c (do_check, do_check_messages): Emit the usual sig
+ warnings even for cached sigs. This also serves to protect
+ against missing a sig expiring while cached.
+
+ * getkey.c (merge_selfsigs_main): Don't check UID self-sigs twice.
+
+2002-08-22 David Shaw <dshaw@jabberwocky.com>
+
+ * import.c (clean_subkeys, chk_self_sigs): Merge clean_subkeys
+ into chk_self_sigs. This improves efficiency as the same
+ signatures are not checked multiple times. Clarify when a subkey
+ is revoked (any revocation signature, even if it is dated before
+ the binding signature).
+
+ * getkey.c (merge_selfsigs_subkey): Subkey revocation comments.
+
+ * keylist.c (list_one): Stats are only for public key listings.
+
+ * g10.c (main), options.skel: Default should be include-revoked
+ for keyserver operations.
+
+2002-08-21 Werner Koch <wk@gnupg.org>
+
+ * import.c (import_print_stats): Print new non_imported counter
+ which is currently not used because we terminate on errors.
+
+2002-08-20 David Shaw <dshaw@jabberwocky.com>
+
+ * options.skel: Document no-include-attributes for
+ keyserver-options.
+
+ * keylist.c, keyedit.c, keyserver.c, sign.c: Some TODOs and
+ comments.
+
+ * export.c (do_export_stream): Fix noop bug in exporting sensitive
+ revocation keys.
+
+ * pkclist.c (do_edit_ownertrust): Comment out the option for
+ showing trust paths until it can be implemented.
+
+2002-08-19 Werner Koch <wk@gnupg.org>
+
+ * getkey.c (get_user_id_native): Renamed to ..
+ (get_user_id_printable): this. Filter out all dangerous
+ characters. Checked all usages.
+ (get_user_id_string_native): Renamed to..
+ (get_user_id_string_printable): this. Filter out all dangerous
+ characters. Checked all usages.
+ * keyedit.c (show_basic_key_info): New.
+ * keylist.c (print_fingerprint): New mode 3.
+ * import.c (import_one): Use new function to display the user ID.
+
+2002-08-16 Timo Schulz <ts@winpt.org>
+
+ * g10.c (main): Enable opt.interactive.
+
+ * import.c (import_one): Ask the user if the key shall be
+ imported when the interactive mode is used. Useful to extract
+ selected keys from a file.
+
+2002-08-16 Werner Koch <wk@gnupg.org>
+
+ * seckey-cert.c: Workaround to allow decryption of v3 keys created
+ with a bug in the mpi_get_secure_buffer.
+
+2002-08-14 David Shaw <dshaw@jabberwocky.com>
+
+ * hkp.c (parse_hkp_index): Properly handle really large keys
+ (5 digit key length) in HKP searches.
+
+2002-08-13 David Shaw <dshaw@jabberwocky.com>
+
+ * encode.c (encode_simple): Fix problem with using compression
+ algo 2 and symmetric compressed files.
+
+ * encode.c (encode_simple, encode_crypt): If we are not using a
+ MDC, compress even if a file is already compressed. This is to
+ help against the chosen ciphertext attack.
+
+ * pkclist.c (select_algo_from_prefs): Fix requested algorithm bug
+ so the request succeeds even if the requested algorithm is not the
+ first found.
+
+ * cipher.c (write_header), encode.c (use_mdc, encode_simple,
+ encode_crypt, encrypt_filter), g10.c (main): Be more eager to use
+ a MDC. We use a MDC if the keys directly support it, if the keys
+ list AES (any) or TWOFISH anywhere in the prefs, or if the cipher
+ chosen does not have a 64 bit blocksize.
+
+2002-08-08 David Shaw <dshaw@jabberwocky.com>
+
+ * options.skel: Some language tweaks, and remove the
+ load-extension section for random gatherers.
+
+ * keyring.c (create_tmp_file, rename_tmp_file): Create tmp files
+ with user-only permissions, but restore the original permissions
+ if the user has something special set.
+
+ * openfile.c (copy_options_file): Create new options file
+ (gpg.conf) with user-only permissions.
+
+ * keydb.c (keydb_add_resource): Create new keyrings with user-only
+ permissions.
+
+ * tdbio.c (tdbio_set_dbname): Create new trustdbs with user-only
+ permissions.
+
+2002-08-07 David Shaw <dshaw@jabberwocky.com>
+
+ * sig-check.c (signature_check2): Sanity check that the md has a
+ context for the hash that the sig is expecting. This can happen
+ if a onepass sig header does not match the actual sig, and also if
+ the clearsign "Hash:" header is missing or does not match the
+ actual sig.
+
+ * keyedit.c (menu_revsig): Properly show a uid is revoked without
+ restarting gpg. This is Debian bug 124219, though their supplied
+ patch will not do the right thing.
+
+ * main.h, tdbio.c (tdbio_set_dbname), misc.c (removed
+ check_permissions), keydb.c (keydb_add_resource), g10.c (main,
+ check_permissions): Significant reworking of the permission check
+ mechanism. The new behavior is to check everything in the homedir
+ by checking the homedir itself. If the user wants to put
+ (possibly shared) keyrings outside the homedir, they are not
+ checked. The options file and any extension files are checked
+ wherever they are, as well as their enclosing directories. This
+ is Debian bug 147760.
+
+2002-08-06 Stefan Bellon <sbellon@sbellon.de>
+
+ * g10.c (main): Use of EXTSEP_S in new gpg.conf string.
+ * openfile.c (copy_options_file): Ditto.
+
+2002-08-06 David Shaw <dshaw@jabberwocky.com>
+
+ * options.h, g10.c (main), mainproc.c (proc_encrypted):
+ --ignore-mdc-error option to turn a MDC check error into a
+ warning.
+
+ * encode.c (encode_crypt), g10.c (main), sign.c (sign_file,
+ clearsign_file): Use the same --pgpX warning string everywhere to
+ ease translations.
+
+ * encode.c (write_pubkey_enc_from_list): Warn when using
+ --throw-keyid with --pgpX. Noted by Vedaal Nistar.
+
+ * revoke.c (export_minimal_pk, gen_desig_revoke, gen_revoke):
+ Export a minimal pk along with the revocation cert when in --pgpX
+ mode so that PGP can import it.
+
+2002-08-06 Werner Koch <wk@gnupg.org>
+
+ * options.skel: Changed comments.
+
+ * g10.c (main): Try to use "gpg.conf" as default option file.
+ * openfile.c (copy_options_file): Changed name of created file.
+
+2002-08-02 Werner Koch <wk@gnupg.org>
+
+ * Makefile.am (LDFLAGS): Removed DYNLINK_LDFLAGS.
+
+2002-07-30 David Shaw <dshaw@jabberwocky.com>
+
+ * options.h, g10.c (main), mainproc.c (proc_encrypted): Return a
+ decryption failed error if a MDC does not verify. Warn if a MDC
+ is not present (can disable via --no-mdc-warning).
+
+ * exec.c (exec_write), g10.c (main), keyserver.c
+ (keyserver_spawn): Use new DISABLE_KEYSERVER_PATH rather than
+ FIXED_EXEC_PATH.
+
+2002-07-28 David Shaw <dshaw@jabberwocky.com>
+
+ * sig-check.c (do_check): Properly validate v4 sigs with no hashed
+ section at all.
+
+2002-07-25 Werner Koch <wk@gnupg.org>
+
+ * delkey.c (do_delete_key): Always allow to delete a key in batch mode
+ when specified by fingerprint. Suggested by Enzo Michelangeli.
+
+2002-07-25 David Shaw <dshaw@jabberwocky.com>
+
+ * keyedit.c (menu_revsig): Change "revsig" to honor selected uids
+ so the user can revoke sigs from particular uids only.
+
+ * keylist.c (list_keyblock_print): Don't display expired uids in
+ --list-keys unless -v and not --list-sigs (just like revoked
+ uids).
+
+ * exec.c, export.c, import.c, keyedit.c, keyserver.c, misc.c:
+ "Warning" -> "WARNING"
+
+2002-07-24 David Shaw <dshaw@jabberwocky.com>
+
+ * main.h, import.c (parse_import_options, fix_hkp_corruption,
+ import_one, delete_inv_parts), g10.c (main): New import-option
+ "repair-hkp-subkey-bug", which repairs as much as possible the HKP
+ mangling multiple subkeys bug. It is on by default for keyserver
+ receives, and off by default for regular --import.
+
+ * main.h, import.c (import, import_one, delete_inv_parts), hkp.c
+ (hkp_ask_import), keyserver.c (keyserver_spawn): Use keyserver
+ import options when doing keyserver receives.
+
+ * options.h, exec.h, exec.c (set_exec_path, exec_write), g10.c
+ (main), keyserver.c (keyserver_spawn): If the user does not use
+ "exec-path", completely replace $PATH with GNUPG_LIBEXECDIR before
+ calling the keyserver helper. If the user does use "exec-path",
+ append GNUPG_LIBEXECDIR after the specified path.
+
+2002-07-23 David Shaw <dshaw@jabberwocky.com>
+
+ * import.c (parse_import_options), export.c
+ (parse_export_options): Fix offset problem with reversed ("no-")
+ meanings.
+
+ * import.c (delete_inv_parts): Discard subkey signatures (0x18 and
+ 0x28) if found in the userid section of the key.
+
+ * sig-check.c (signature_check2): Signatures made by invalid
+ subkeys (bad/missing binding sig) are also invalid.
+
+ * keylist.c (print_fingerprint): Show the primary as well as the
+ secondary key fingerprint in modes 1 & 2.
+
+2002-07-22 David Shaw <dshaw@jabberwocky.com>
+
+ * options.h, main.h, g10.c (main), import.c
+ (parse_import_options, delete_inv_parts), keyserver.c
+ (parse_keyserver_options): add new --import-options option. The
+ only current flag is "allow-local-sigs".
+
+ * g10.c (main): Don't disable MDC in pgp7 mode.
+
+ * options.h, g10.c (main), keyserver.c (parse_keyserver_options):
+ Remove old keyserver-option include-attributes now that there is
+ an export-option for the same thing.
+
+ * options.h, main.h, export.c (parse_export_options,
+ do_export_stream), g10.c (main): add new --export-options option.
+ Current flags are "include-non-rfc", "include-local-sigs",
+ "include-attributes", and "include-sensitive-revkeys".
+
+ * options.h, hkp.c (hkp_export), keyserver.c
+ (parse_keyserver_options, keyserver_spawn): try passing unknown
+ keyserver options to export options, and if successful, use them
+ when doing a keyserver --send-key.
+
+ * build-packet.c (build_sig_subpkt): We do not generate
+ SIGSUBPKT_PRIV_VERIFY_CACHE anymore.
+
+ * revoke.c (gen_desig_revoke): Lots more comments about including
+ sensitive revkeys along with the revocation sig itself.
+
+ * keyserver.c (parse_keyserver_options): Simpler implementation
+ that can skip one pass over the options.
+
+2002-07-18 David Shaw <dshaw@jabberwocky.com>
+
+ * keyedit.c (keyedit_menu, menu_addrevoker): Allow specifying
+ "sensitive" as an argument to an addrevoker command. This sets
+ the 0x40 sensitive revoker flag.
+
+ * revoke.c (gen_desig_revoke): When generating a designated
+ revocation, include the direct key sig that contains the
+ designated revoker subpacket. This allows sensitive designated
+ revocation subpackets to be exported. Also indicate which
+ revokers are sensitive in the first place.
+
+2002-07-17 David Shaw <dshaw@jabberwocky.com>
+
+ * keyedit.c (show_key_with_all_names_colon): The 0x40 class bit in
+ a designated revoker means "sensitive", not "local". It's
+ exportable under the right circumstances.
+
+ * main.h, options.h, export.c (do_export_stream), g10.c (main),
+ hkp.c (hkp_export), keyserver.c (keyserver_spawn: Add a flag to
+ skip attribute packets and their signatures while exporting. This
+ is to accomodate keyservers (pksd again) that choke on attributes.
+ Use keyserver-option "include-attributes" to control it. This
+ defaults to ON (i.e. don't skip).
+
+2002-07-09 David Shaw <dshaw@jabberwocky.com>
+
+ * options.h, keyserver.c (parse_keyserver_uri, keyserver_spawn,
+ keyserver_work), hkp.c (hkp_ask_import, hkp_export, hkp_search):
+ Use a much more strict reading of RFC-2396 for the keyserver URIs.
+ Specifically, don't try and be smart about checking the value of
+ ":port" so long as it is all digits, and properly handle opaque
+ data (those scheme specific parts that do not start with "//").
+
+2002-07-04 David Shaw <dshaw@jabberwocky.com>
+
+ * photoid.c (get_default_photo_command, show_photos): Honor
+ FIXED_PHOTO_VIEWER and DISABLE_PHOTO_VIEWER.
+
+ * mainproc.c (check_sig_and_print): Use --show-photos to show
+ photos when verifying a sig made by a key with a photo.
+
+ * keyserver.c (parse_keyserver_uri): Properly parse a URI with no
+ :port section and an empty file path, but with a terminating '/'.
+ (keyserver_work): Honor DISABLE_KEYSERVER_HELPERS.
+
+ * hkp.c (hkp_ask_import): Display keyserver URI as a URI, but only
+ if verbose.
+
+ * exec.c, g10.c: USE_EXEC_PATH -> FIXED_EXEC_PATH
+
+2002-07-03 David Shaw <dshaw@jabberwocky.com>
+
+ * exec.h, exec.c (set_exec_path, exec_write), g10.c (main): If
+ USE_EXEC_PATH is defined at compile time, use it to lock the
+ exec-path and not allow the user to change it.
+
+2002-07-02 David Shaw <dshaw@jabberwocky.com>
+
+ * options.h, g10.c (main), keyserver.c (keyserver_refresh):
+ Maintain and use the original keyserver URI for cosmetics rather
+ than trying to recreate it when needed.
+
+ * mainproc.c (check_sig_and_print): Properly disregard expired
+ uids. Make sure that the first uid listed is a real uid and not
+ an attribute (attributes should only be listed in the "aka"
+ section). When there are no valid textual userids, try for an
+ invalid textual userid before using any attribute uid.
+
+2002-07-01 David Shaw <dshaw@jabberwocky.com>
+
+ * options.skel: Fix a few typos, clarify "group", and remove
+ sample photo viewers for Win32 since they are the defaults now.
+
+ * parse-packet.c (make_attribute_uidname), keylist.c
+ (dump_attribs): Fix two typecast warnings.
+
+ * packet.h, build-packet.c (build_attribute_subpkt), exec.c
+ (expand_args), mkdtemp.c (mkdtemp), photoid.c
+ (parse_image_header): Fix some signedness compiler warnings.
+
+2002-07-01 Werner Koch <wk@gnupg.org>
+
+ * photoid.c (get_default_photo_command): Also use __MINGW32__
+ instead of HAVE_DOSISH_SYSTEM.
+
+ * encode.c (encode_symmetric): Do not use the new encryption code.
+
+2002-06-30 Werner Koch <wk@gnupg.org>
+
+ * photoid.c: Use __MINGW32__ to include windows because
+ HAVE_DOSISH_SYSTEM is also set for OS/2 and plain DOS. Provide
+ constant missing in older mingw installations.
+
+2002-06-21 Stefan Bellon <sbellon@sbellon.de>
+
+ * g10.c [__riscos__]: Moved RISC OS specific stuff to util/riscos.c
+ and include/util.h.
+
+ * gpgv.c [__riscos__]: Likewise.
+
+2002-06-20 David Shaw <dshaw@jabberwocky.com>
+
+ * keydb.h, pkclist.c (select_algo_from_prefs): Allow passing a
+ suggested algorithm which will be used if available.
+
+ * encode.c (encode_crypt, encrypt_filter), sign.c (sign_file): Use
+ new select_algo_from_prefs feature to check if forcing an
+ algorithm would violate the recipient preferences.
+
+ * photoid.c (get_default_photo_command, show_photos): Use
+ different default viewers on different platforms. Currently we
+ have Win 9x, Win NT (2k, xp), Mac OSX, RISC OS, and "everybody
+ else". These are #ifdefs as much as possible to avoid clutter.
+
+ * g10.c (strusage, build_list), keyedit.c (show_prefs), main.h,
+ misc.c (compress_algo_to_string, check_compress_algo), pkclist.c
+ (algo_available), keygen.c (keygen_set_std_prefs): New
+ algo_to_string and check functions for compress algorithms.
+
+2002-06-20 Werner Koch <wk@gnupg.org>
+
+ * misc.c (setsysinfo): Removed a #warning for Alpha's uniligedn
+ trap disabling - it is quite possible that this is a debug relict.
+
+2002-06-20 Stefan Bellon <sbellon@sbellon.de>
+
+ * g10.c [__riscos__]: Added image file system feature.
+
+ * gpgv.c [__riscos__]: Added image file system feature.
+
+ * photoid.c (show_photos) [__riscos__]: Set RISC OS filetype of
+ photo id according to MIME type.
+
+2002-06-19 David Shaw <dshaw@jabberwocky.com>
+
+ * hkp.c (parse_hkp_index): Don't leak memory when failing out of a
+ bad HKP keyserver.
+
+ * g10.c (add_notation_data): Relax slightly the rules as to what
+ can go into a notation name - 2440 allows "@", for example.
+
+2002-06-17 David Shaw <dshaw@jabberwocky.com>
+
+ * import.c (clean_subkeys, import_one): Only allow at most 1
+ binding sig and at most 1 revocation sig on a subkey, as per
+ 2440:11.1.
+
+ * hkp.c (parse_hkp_index, hkp_search): Error if the keyserver
+ returns an unparseable HKP response.
+
+2002-06-15 David Shaw <dshaw@jabberwocky.com>
+
+ * keyedit.c (show_key_with_all_names), keylist.c
+ (list_keyblock_print): Show "[expired]" before expired uids.
+
+ * keyedit.c (show_key_with_all_names_colon), mainproc.c
+ (list_node), keylist.c (list_keyblock_colon): Show flag 'e' for
+ expired user ids. Use "uat" for user attribute packets instead of
+ "uid". Also use '<count> <length>' rather than the fake user id
+ string on attributes.
+
+ * keygen.c (keygen_add_revkey): Remove unused code.
+
+ * misc.c (check_permissions): Check directory permissions
+ properly - they are not special files.
+
+ * pkclist.c (expand_id, expand_group, build_pk_list): When
+ expanding groups before building a pk list, inherit flags from the
+ original pre-expanded string.
+
+ * pubkey-enc.c (is_algo_in_prefs): Don't use prefs from expired
+ uids.
+
+2002-06-14 David Shaw <dshaw@jabberwocky.com>
+
+ * free-packet.c (copy_signature): Properly copy a signature that
+ carries a revocation key on it.
+
+ * pkclist.c (expand_id, expand_group, build_pk_list): Groups now
+ work properly when used in the "Enter the user ID" prompt.
+
+2002-06-14 David Shaw <dshaw@jabberwocky.com>
+
+ * keyedit.c (show_key_with_all_names): Display warning if a user
+ tries to show prefs on a v3 key with a v3 selfsig.
+
+ * kbnode.c (dump_kbnode): Show if a uid is expired.
+
+ * import.c (merge_blocks, import_revoke_cert): Show user ID
+ receiving a revocation certificate.
+
+ * free-packet.c (cmp_user_ids): Properly compare attribute ids.
+
+ * pkclist.c (expand_groups): Maintain the strlist flags while
+ expanding. Members of an expansion inherit their flags from the
+ expansion key.
+
+ * options.h, cipher.c (write_header), g10.c (main), keygen.c
+ (keygen_set_std_prefs): remove the personal_mdc flag. It no
+ longer serves a purpose now that the personal preference lists are
+ split into cipher/digest/zip.
+
+2002-06-14 Timo Schulz <ts@winpt.org>
+
+ * skclist.c (is_insecure): Implemented.
+
+2002-06-12 David Shaw <dshaw@jabberwocky.com>
+
+ * keyserver.c (keyserver_spawn): Properly handle PROGRAM responses
+ when they have a CRLF ending. Noted by Keith Ray.
+
+ * keyserver.c (keyserver_spawn): Handle CRLF endings from
+ keyserver helpers. Also don't leak the last line worth of memory
+ from the keyserver response.
+
+ * main.h, misc.c (deprecated_warning): New function to warn about
+ deprecated options and commands.
+
+ * g10.c (main), keyserver-internal.h, keyserver.c
+ (parse_keyserver_uri): Use new deprecated function to warn about
+ honor-http-proxy, auto-key-retrieve, and x-broken-hkp.
+
+2002-06-11 David Shaw <dshaw@jabberwocky.com>
+
+ * Makefile.am: link gpg with NETLIBS for the built-in HKP access.
+
+2002-06-10 David Shaw <dshaw@jabberwocky.com>
+
+ * options.h, keyserver.c (keyserver_opts), g10.c (main): New
+ keyserver option "include-subkeys". This feature already existed,
+ but now can be turned off. It defaults to on.
+
+ * options.h, keyserver.c (parse_keyserver_options,
+ keyserver_spawn): There are now enough options to justify making a
+ structure for the keyserver options rather than a page of
+ if-then-else-if-then-etc.
+
+ * getkey.c (merge_keys_and_selfsig, merge_selfsigs_main): Fix bug
+ in calculating key expiration dates.
+
+2002-06-09 David Shaw <dshaw@jabberwocky.com>
+
+ * keydb.h, getkey.c (get_user_id_native), import.c (import_one):
+ Display user ID while importing a key. Note this applies to both
+ --import and keyserver --recv-keys.
+
+ * exec.c (exec_finish): Log unnatural exit (core dump, killed
+ manually, etc) for fork/exec/pipe child processes.
+
+2002-06-08 Timo Schulz <ts@winpt.org>
+
+ * encode.c (encode_symmetric): Disable the compat flag
+ when the expert mode is enabled.
+
+2002-06-07 David Shaw <dshaw@jabberwocky.com>
+
+ * options.skel, options.h, main.h, keydb.h, pkclist.c
+ (build_pk_list, expand_groups), g10.c (main, add_group): Add new
+ "group" command to allow one name to expand into multiple keys.
+ For simplicity, and to avoid potential loops, we only expand once
+ - you can't make an alias that points to an alias.
+
+ * main.h, g10.c (main), keygen.c (build_personal_digest_list):
+ Simplify the default digest list - there is really no need for the
+ other hashes since they will never be used after SHA-1 in the
+ list.
+
+ * options.skel, options.h, g10.c (main), hkp.c (hkp_ask_import,
+ hkp_export, hkp_search), keyserver.c (parse_keyserver_options,
+ parse_keyserver_uri, keyserver_work, keyserver_refresh): Make the
+ "x-broken-hkp" keyserver scheme into keyserver-option
+ "broken-http-proxy". Move honor_http_proxy into
+ keyserver_options. Canonicalize the three variations of "hkp",
+ "x-hkp", and "x-broken-hkp" into "hkp".
+
+2002-06-07 Stefan Bellon <sbellon@sbellon.de>
+
+ * g10.c [__riscos__]: Added --attribute-file to do the same as
+ --attribute-fd, but with a filename not a fd as argument.
+ Added magic symbol for RISC OS to use different memory management.
+
+ * gpgv.c [__riscos__]: Added magic symbol for RISC OS to use
+ different memory management.
+
+2002-06-06 David Shaw <dshaw@jabberwocky.com>
+
+ * main.h, g10.c (main), keygen.c (build_personal_digest_list): Put
+ in a default digest preference list consisting of SHA-1, followed
+ by every other installed digest except MD5. Note this is the same
+ as having no digest preference at all except for SHA-1 being
+ favored.
+
+ * options.h, g10.c (main), keygen.c (keygen_set_std_prefs),
+ pkclist.c (select_algo_from_prefs): Split
+ --personal-preference-list into three:
+ --personal-{cipher|digest|compress}-preferences. This allows a
+ user to set one without affecting another (i.e. setting only a
+ digest pref doesn't imply an empty cipher pref).
+
+ * exec.c (exec_read): This is a safer way of guessing the return
+ value of system(). Noted by Stefan Bellon.
+
+2002-06-05 David Shaw <dshaw@jabberwocky.com>
+
+ * hkp.c (parse_hkp_index): Be more robust with keyservers
+ returning very unparseable responses.
+
+ * exec.c (exec_read): Catch and display an error when the remote
+ process exits unnaturally (i.e. segfault) so the user knows what
+ happened. Also fix exec_write stub which has a different number
+ of arguments now.
+
+2002-06-05 Timo Schulz <ts@winpt.org>
+
+ * encode.c (encode_simple): Ignore the new mode for RFC1991.
+ * mainproc.c (symkey_decrypt_sesskey): Better check for weird
+ keysizes.
+
+2002-06-05 Timo Schulz <ts@winpt.org>
+
+ * encode.c (encode_sesskey): New.
+ (encode_simple): Use it here. But by default we use the compat
+ mode which supress to generate encrypted session keys.
+
+2002-06-05 Timo Schulz <ts@winpt.org>
+
+ * mainproc.c (symkey_decrypt_sesskey): New.
+ (proc_symkey_enc): Support for encrypted session keys.
+
+2002-06-04 David Shaw <dshaw@jabberwocky.com>
+
+ * sign.c (hash_for, sign_file): When encrypting and signing at the
+ same time, consult the various hash prefs to pick a hash algorithm
+ to use. Pass in a 160-bit hint if any of the signing keys are
+ DSA.
+
+ * keydb.h, pkclist.c (select_algo_from_prefs, algo_available):
+ Pass a "hints" opaque pointer in to let the caller give hints as
+ to what algorithms would be acceptable. The only current hint is
+ for PREFTYPE_HASH to require a 160-bit hash for DSA. Change all
+ callers in encode.c (encode_crypt, encrypt_filter) and sign.c
+ (sign_file). If we settle on MD5 as the best algorithm based
+ solely on recepient keys and SHA1 is also a possibility, use SHA1
+ unless the user intentionally chose MD5. This is as per 2440:13.
+
+ * exec.c (make_tempdir): Fix duplicated filename problem.
+
+2002-06-03 David Shaw <dshaw@jabberwocky.com>
+
+ * packet.h, parse-packet.c (enum_sig_subpkt): Report back from
+ enum_sig_subpkt when a subpacket is critical and change all
+ callers in keylist.c (show_policy_url, show_notation), mainproc.c
+ (print_notation_data), and pkclist.c (do_show_revocation_reason).
+
+ * keylist.c (show_policy_url, show_notation): Display if the
+ policy or notation is critical.
+
+2002-06-03 David Shaw <dshaw@jabberwocky.com>
+
+ * main.h, g10.c (main), keylist.c (dump_attribs, set_attrib_fd,
+ list_keyblock_print, list_keyblock_colon), status.h, status.c
+ (get_status_string): New --attribute-fd feature to dump the
+ contents of attribute subpackets for frontends. If --status-fd is
+ also used, then a new status tag ATTRIBUTE is provided for each
+ subpacket.
+
+ * packet.h, getkey.c (fixup_uidnode, merge_selfsigs_main,
+ merge_selfsigs_subkey), parse-packet.c (setup_user_id): Keep track
+ of the expiration time of a user ID, and while we're at it, use
+ the expired flag from the selfsig rather than reparsing the
+ SIG_EXPIRE subpacket.
+
+ * photoid.c (generate_photo_id): When adding a new photo ID,
+ showing the photo for confirmation is not safe when noninteractive
+ since the "user" may not be able to dismiss a viewer window.
+ Noted by Timo Schulz.
+
+2002-06-03 David Shaw <dshaw@jabberwocky.com>
+
+ * options.skel: Sample photo viewers for Win32.
+
+ * misc.c (pct_expando): Use the seckey for %k/%K if the pubkey is
+ not available.
+
+ * photoid.h, photoid.c (show_photos): Include the seckey in case a
+ user tries to view a photo on a secret key, and change all callers
+ in keyedit.c (menu_showphoto), keylist.c (list_keyblock_print),
+ and photoid.c (generate_photo_id).
+
+2002-06-02 David Shaw <dshaw@jabberwocky.com>
+
+ * photoid.c (show_photos): Work properly when not called with a
+ public key.
+
+2002-05-31 David Shaw <dshaw@jabberwocky.com>
+
+ * sign.c (mk_notation_and_policy): Free unneeded buffer.
+
+ * hkp.c (parse_hkp_index): Properly handle the '&' character
+ (i.e. "&amp;") in HKP responses.
+
+ * getkey.c (merge_selfsigs_main): Fix reversed expiration time
+ check with self-sigs.
+
+ * keyedit.c (sign_uids): When making a new self-sig on a v3 key,
+ make a v3 self-sig unless it is currently a v3 self-sig being
+ promoted to v4.
+
+2002-05-31 Timo Schulz <ts@winpt.org>
+
+ * pkclist.c (do_show_revocation_reason): Don't use capital
+ letters for non-interactive output.
+ (show_revocation_reason): Now it is global.
+ * pubkey-enc.c (get_it): Show if the key has been revoked.
+
+2002-05-30 David Shaw <dshaw@jabberwocky.com>
+
+ * sign.c (write_signature_packets, sign_file, clearsign_file,
+ sign_symencrypt_file): Make a v4 signature if a policy URL or
+ notation is set, unless v3 sigs are forced via rfc1991 or
+ force-v3-sigs. Also remove some doubled code and clarify an error
+ message (we don't sign in PGP2 mode - just detach-sign).
+
+ * parse-packet.c (parse_one_sig_subpkt): Add KS_FLAGS to the "any
+ size" section.
+
+2002-05-29 David Shaw <dshaw@jabberwocky.com>
+
+ * keygen.c (keygen_set_std_prefs, add_feature_mdc): Use "mdc" and
+ "no-mdc" in the prefs string to allow switching on and off the MDC
+ feature. This is needed to properly export a key from GnuPG for
+ use on PGP which does not support MDC - without this, MDC-capable
+ implementations will still try and generate MDCs which will break
+ PGP.
+
+ * keygen.c (keygen_get_std_prefs): Show "[mdc]" in prefs string if
+ it is enabled.
+
+ * options.h, g10.c (main), cipher.c (write_header), keygen.c
+ (keygen_set_std_prefs): For consistency, allow the user to specify
+ mdc/no-mdc in the --personal-preference-list. If disabled, it
+ acts just like --disable-mdc.
+
+2002-05-29 David Shaw <dshaw@jabberwocky.com>
+
+ * options.h, exec.c: Add some debugging info, using the 1024 debug
+ flag.
+
+ * exec.c (win_system): New system()-like function for win32 that
+ does not return until the child process terminates. Of course,
+ this doesn't help if the process itself exits before it is
+ finished.
+
+2002-05-29 Werner Koch <wk@gnupg.org>
+
+ * encode.c (encode_simple): Intialize PKT when --no-literal is used.
+
+ * keyedit.c (show_key_with_all_names_colon): Renamed the record
+ for revocation keys to "rvk".
+
+2002-05-27 Werner Koch <wk@gnupg.org>
+
+ * keyedit.c (show_key_with_all_names_colon): New.
+ (show_key_with_all_names): Divert to new function when required.
+ Sanitize printing of revoker name.
+
+2002-05-27 David Shaw <dshaw@jabberwocky.com>
+
+ * build-packet.c (build_sig_subpkt): Handle setting sig flags for
+ certain subpacket types (notation, policy url, exportable,
+ revocable). keyedit.c (sign_mk_attrib): Flags no longer need to
+ be set here.
+
+ * packet.h, parse-packet.c (parse_one_sig_subpkt), build-packet.c
+ (build_sig_subpkt): Call parse_one_sig_subpkt to sanity check
+ buffer lengths before building a sig subpacket.
+
+2002-05-26 David Shaw <dshaw@jabberwocky.com>
+
+ * sign.c (mk_notation_and_policy): Include secret key to enable %s
+ expandos, and pass notations through pct_expando as well.
+
+ * main.h, misc.c (pct_expando): Add %s and %S expandos for
+ signer's keyid.
+
+2002-05-25 David Shaw <dshaw@jabberwocky.com>
+
+ * g10.c (strusage, build_list): Add compress algorithms to
+ --version list. Show algorithm numbers when --verbose --version
+ is done.
+
+2002-05-22 David Shaw <dshaw@jabberwocky.com>
+
+ * options.h, main.h, keygen.c (keygen_set_set_prefs,
+ keygen_get_std_prefs, keygen_upd_std_prefs), keyedit.c
+ (keyedit_menu), g10.c (main), pkclist.c (select_algo_from_prefs):
+ Add --personal-preference-list which allows the user to factor in
+ their own preferred algorithms when the preference lists are
+ consulted. Obviously, this does not let the user violate a
+ recepient's preferences (and the RFC) - this only influences the
+ ranking of the agreed-on (and available) algorithms from the
+ recepients. Suggested by David Hollenberg.
+
+ * options.h, keygen.c (keygen_set_std_prefs), g10.c (main): Rename
+ --preference-list to --default-preference-list (as that is what it
+ really is), and make it a true default in that if the user selects
+ "default" they get this list and not the compiled-in list.
+
+2002-05-22 Werner Koch <wk@gnupg.org>
+
+ * g10.c (main): Add missing LF in a info printout and made it
+ translatable. Noted by Michael Tokarev.
+
+2002-05-21 Werner Koch <wk@gnupg.org>
+
+ * g10.c (main): Removed the undef of USE_SHM_COPROCESSING which
+ was erroneously introduced on 2002-01-09.
+
+ * signal.c (got_fatal_signal): Don't write the Nul to stderr.
+ Reported by David Hollenberg.
+
+2002-05-18 David Shaw <dshaw@jabberwocky.com>
+
+ * main.h, g10.c (main), revoke.c (gen_desig_revoke): Generate a
+ designated revocation via --desig-revoke
+
+ * keyedit.c (keyedit_menu, menu_addrevoker): New "addrevoker"
+ command to add a designated revoker to a key.
+
+2002-05-17 David Shaw <dshaw@jabberwocky.com>
+
+ * gpgv.c: Add stub for get_ownertrust().
+
+ * g10.c (main): --allow-freeform-uid should be implied by
+ OpenPGP. Add --no-allow-freeform-uid.
+
+ * keyedit.c (sign_uids): Issue a warning when signing a
+ non-selfsigned uid.
+
+ * getkey.c (merge_selfsigs_main): If a key has no selfsigs, and
+ allow-non-selfsigned-uid is not set, still try and make the key
+ valid by checking all uids for a signature from an ultimately
+ trusted key.
+
+2002-05-16 David Shaw <dshaw@jabberwocky.com>
+
+ * main.h, keygen.c (keygen_add_revkey): Add revocation key
+ subpackets to a signature (callable by
+ make_keysig_packet). (write_direct_sig): Write a 1F direct key
+ signature. (parse_revocation_key): Parse a string in
+ algo:fpr:sensitive format into a revocation
+ key. (get_parameter_revkey, do_generate_keypair): Call above
+ functions when prompted from a batch key generation file.
+
+ * build-packet.c (build_sig_subpkt): Allow multiple revocation key
+ subpackets in a single sig.
+
+ * keydb.h, getkey.c (get_seckey_byfprint): Same as
+ get_pubkey_byfprint, except for secret keys. We only know the
+ fingerprint of a revocation key, so this is needed to retrieve the
+ secret key needed to issue a revokation.
+
+ * packet.h, parse-packet.c (parse_signature, parse_revkeys): Split
+ revkey parsing off into a new function that can be used to reparse
+ after manipulating the revkey list.
+
+ * sign.c (make_keysig_packet): Ability to make 1F direct key
+ signatures.
+
+2002-05-15 David Shaw <dshaw@jabberwocky.com>
+
+ * options.skel: keyserver.pgp.com is gone, so list pgp.surfnet.nl
+ as a sample LDAP server instead.
+
+ * getkey.c (merge_selfsigs_main): Properly handle multiple
+ revocation keys in a single packet. Properly handle revocation
+ keys that are in out-of-order packets. Remove duplicates in
+ revocation key list.
+
+2002-05-14 Timo Schulz <ts@winpt.org>
+
+ * exec.c (make_tempdir) [MINGW32]: Added missing '\'.
+
+2002-05-14 Stefan Bellon <sbellon@sbellon.de>
+
+ * exec.c (make_tempdir): Make use of EXTSEP_S instead of hardcoded
+ dot as extension separator.
+
+2002-05-13 David Shaw <dshaw@jabberwocky.com>
+
+ * photoid.c (show_photos): Use the long keyid as the filename for
+ the photo. Use the short keyid as the filename on 8.3 systems.
+
+ * exec.h, exec.c (make_tempdir, exec_write, exec_finish): Allow
+ caller to specify filename. This should make things easier on
+ windows and macs where the file extension is required, but a whole
+ filename is even better.
+
+ * keyedit.c (show_key_with_all_names, show_prefs): Show proper
+ prefs for a v4 key uid with no selfsig at all.
+
+ * misc.c (check_permissions): Don't check permissions on
+ non-normal files (pipes, character devices, etc.)
+
+2002-05-11 Werner Koch <wk@gnupg.org>
+
+ * mainproc.c (proc_symkey_enc): Avoid segv in case the parser
+ encountered an invalid packet.
+
+ * keyserver.c (keyserver_export): Get confirmation before sending
+ all keys.
+
+2002-05-10 Stefan Bellon <sbellon@sbellon.de>
+
+ * g10.c, hkp.c, keyedit.c, keyserver.c: Replaced all occurrances
+ of strcasecmp with ascii_strcasecmp and all occurrances of
+ strncasecmp with ascii_memcasecmp.
+
+2002-05-10 David Shaw <dshaw@jabberwocky.com>
+
+ * packet.h, getkey.c (fixup_uidnode), keyedit.c (show_prefs): Show
+ assumed prefs for hash and compression as well as the cipher pref.
+ Show assumed prefs if there are no prefs at all on a v4
+ self-signed key.
+
+ * options.h, g10.c (main), sign.c (make_keysig_packet): New
+ --cert-digest-algo function to override the default key signing
+ hash algorithm.
+
+2002-05-09 David Shaw <dshaw@jabberwocky.com>
+
+ * getkey.c (merge_selfsigs_main): Make sure the revocation key
+ list starts clean as this function may be called more than once
+ (e.g. from functions in --edit).
+
+ * g10.c, encode.c (encode_crypt), sign.c (sign_file,
+ sign_symencrypt_file): Make --compress-algo work like the
+ documentation says. It should be like --cipher-algo and
+ --digest-algo in that it can override the preferences calculation
+ and impose the setting the user wants. No --compress-algo setting
+ allows the usual preferences calculation to take place.
+
+ * main.h, compress.c (compress_filter): use new
+ DEFAULT_COMPRESS_ALGO define, and add a sanity check for compress
+ algo value.
+
+2002-05-08 David Shaw <dshaw@jabberwocky.com>
+
+ * pkclist.c (select_algo_from_prefs): There is an assumed
+ compression preference for uncompressed data.
+
+2002-05-07 David Shaw <dshaw@jabberwocky.com>
+
+ * options.h, g10.c (main), getkey.c (finish_lookup), pkclist.c
+ (algo_available): --pgp7, identical to --pgp6 except that it
+ permits a few algorithms that PGP 7 added: AES128, AES192, AES256,
+ and TWOFISH. Any more of these --pgpX flags, and it'll be time to
+ start looking at a generic --emulate-pgp X option.
+
+ * export.c (do_export_stream): Warn the user when exporting a
+ secret key if it or any of its secret subkeys are protected with
+ SHA1 while simple_sk_checksum is set.
+
+ * parse-packet.c (parse_key): Show when the SHA1 protection is
+ used in --list-packets.
+
+ * options.h, build-packet.c (do_comment), g10.c (main): Rename
+ --no-comment as --sk-comments/--no-sk-comments (--no-comment still
+ works) and make the default be --no-sk-comments.
+
+2002-05-07 Werner Koch <wk@gnupg.org>
+
+ * keygen.c (get_parameter_algo): Never allow generation of the
+ deprecated RSA-E or RSA-S flavors of PGP RSA.
+ (ask_algo): Allow generation of RSA sign and encrypt in expert
+ mode. Don't allow ElGamal S+E unless in expert mode.
+ * helptext.c: Added entry keygen.algo.rsa_se.
+
+2002-05-07 David Shaw <dshaw@jabberwocky.com>
+
+ * keyedit.c (sign_uids): If --expert is set, allow re-signing a
+ uid to promote a v3 self-sig to a v4 one. This essentially
+ deletes the old v3 self-sig and replaces it with a v4 one.
+
+ * packet.h, parse-packet.c (parse_key), getkey.c
+ (merge_keys_and_selfsig, merge_selfsigs_main): a v3 key with a v4
+ self-sig must never let the v4 self-sig express a key expiration
+ time that extends beyond the original v3 expiration time.
+
+2002-05-06 David Shaw <dshaw@jabberwocky.com>
+
+ * keyedit.c (sign_uids): When making a self-signature via "sign"
+ don't ask about sig level or expiration, and include the usual
+ preferences and such for v4 self-sigs. (menu_set_preferences):
+ Convert uids from UTF8 to native before printing.
+
+ * keyedit.c (sign_uids): Convert uids from UTF8 to native before
+ printing. (menu_set_primary_uid): Show error if the user tries to
+ make a uid with a v3 self-sig primary.
+
+2002-05-05 David Shaw <dshaw@jabberwocky.com>
+
+ * import.c (import_one): When merging with a key we already have,
+ don't let a key conflict (same keyid but different key) stop the
+ import: just skip the bad key and continue.
+
+ * exec.c (make_tempdir): Under Win32, don't try environment
+ variables for temp directories - GetTempDir tries environment
+ variables internally, and it's better not to second-guess it in
+ case MS adds some sort of temp dir handling to Windows at some
+ point.
+
+2002-05-05 Timo Schulz <ts@winpt.org>
+
+ * mainproc.c (proc_symkey_enc): Don't ask for a passphrase
+ in the list only mode.
+
+2002-05-05 David Shaw <dshaw@jabberwocky.com>
+
+ * keyserver.c (keyserver_refresh): --refresh-keys implies
+ --merge-only so as not to import keys with keyids that match the
+ ones being refreshed. Noted by Florian Weimer.
+
+2002-05-04 Stefan Bellon <sbellon@sbellon.de>
+
+ * free-packet.c (copy_public_key): Don't call m_alloc(0), therefore
+ added consistency check for revkey and numrefkeys.
+
+ * getkey.c (check_revocation_keys): Added consistency check for
+ revkey and numrefkeys.
+
+ * keyedit.c (show_key_with_all_names): Likewise.
+
+2002-05-03 David Shaw <dshaw@jabberwocky.com>
+
+ * photoid.c: Provide default image viewer for Win32.
+
+ * misc.c (pct_expando): %t means extension, not name ("jpg", not
+ "jpeg").
+
+ * keyserver.c (keyserver_spawn), photoid.c (show_photos), exec.h,
+ exec.c: Allow the caller to determine the temp file extension when
+ starting an exec_write and change all callers.
+
+ * keyedit.c (sign_uids): Nonrevocable key signatures cause an
+ automatic promotion to v4.
+
+ * exec.c: Provide stubs for exec_ functions when NO_EXEC is
+ defined.
+
+2002-05-02 David Shaw <dshaw@jabberwocky.com>
+
+ * photoid.h, photoid.c (parse_image_header, image_type_to_string):
+ Useful functions to return data about an image.
+
+ * packet.h, parse-packet.c (make_attribute_uidname,
+ parse_attribute_subpkts, parse_attribute), photoid.h, photoid.c
+ (show_photos): Handle multiple images in a single attribute
+ packet.
+
+ * main.h, misc.c (pct_expando), sign.c (mk_notation_and_policy),
+ photoid.c (show_photos): Simpler expando code that does not
+ require using compile-time string sizes. Call
+ image_type_to_string to get image strings (i.e. "jpg",
+ "image/jpeg"). Change all callers.
+
+ * keyedit.c (menu_showphoto), keylist.c (list_keyblock_print):
+ Allow viewing multiple images within a single attribute packet.
+
+ * gpgv.c: Various stubs for link happiness.
+
+2002-05-02 David Shaw <dshaw@jabberwocky.com>
+
+ * build-packet.c (build_sig_subpkt), keyedit.c (sign_uids),
+ options.h, sign.c (mk_notation_and_policy), g10.c (main,
+ add_notation_data, add_policy_url (new), check_policy_url
+ (removed)): Allow multiple policy URLs on a given signature.
+ Split "--notation-data" into "--cert-notation" and
+ "--sig-notation" so the user can set different policies for key
+ and data signing. For backwards compatibility, "--notation-data"
+ sets both, as before.
+
+2002-05-02 Werner Koch <wk@gnupg.org>
+
+ * options.skel: Removed the comment on trusted-keys because this
+ option is now deprecated.
+
+2002-05-01 David Shaw <dshaw@jabberwocky.com>
+
+ * keyedit.c (menu_adduid): 2440bis04 says that multiple attribute
+ packets on a given key are legal.
+
+ * keyserver.c (keyserver_refresh): the fake v3 keyid hack applies
+ to "mailto" URLs as well since they are also served by pksd.
+
+2002-04-29 Werner Koch <wk@gnupg.org>
+
+ Added a copyright year for files changed this year.
+
+2002-04-25 Werner Koch <wk@gnupg.org>
+
+ * g10.c, options.h: New options --display, --ttyname, --ttytype,
+ --lc-ctype, --lc-messages to be used with future versions of the
+ gpg-agent.
+ * passphrase.c (agent_send_option,agent_send_all_options): New.
+ (agent_open): Send options to the agent.
+
+ * trustdb.c (update_ownertrust, clear_ownertrust): Do an explicit
+ do_sync because revalidation_mark does it only if when the
+ timestamp actually changes.
+
+2002-04-23 David Shaw <dshaw@jabberwocky.com>
+
+ * main.h, keygen.c (do_generate_keypair), keylist.c
+ (print_signature_stats, list_all, list_one, list_keyblock,
+ list_keyblock_print, list_keyblock_colon): After generating a new
+ key, show the key information (name, keyid, fingerprint, etc.)
+ Also do not print uncheckable signatures (missing key..) in
+ --check-sigs. Print statistics (N missing keys, etc.) after
+ --check-sigs.
+
+ * keyedit.c (sign_uids): When signing a key with an expiration
+ date on it, the "Do you want your signature to expire at the same
+ time?" question should default to YES.
+
+2002-04-22 David Shaw <dshaw@jabberwocky.com>
+
+ * parse-packet.c (parse_plaintext), packet.h, plaintext.c
+ (handle_plaintext): Fix bug in handling literal packets with
+ zero-length data (no data was being confused with partial body
+ length).
+
+ * misc.c (pct_expando), options.skel: %t means extension ("jpg").
+ %T means MIME type ("image/jpeg").
+
+ * import.c (import_one): Only trigger trust update if the keyring
+ is actually changed.
+
+ * export.c (do_export_stream): Missing a m_free.
+
+2002-04-22 Stefan Bellon <sbellon@sbellon.de>
+
+ * keyid.c (expirestr_from_sk, expirestr_from_sig): Added _() to
+ string constant.
+
+ * exec.c (make_tempdir) [__riscos__]: Better placement of
+ temporary file.
+
+2002-04-20 David Shaw <dshaw@jabberwocky.com>
+
+ * keygen.c (generate_subkeypair): 2440bis04 adds that creating
+ subkeys on v3 keys is a MUST NOT.
+
+ * getkey.c (finish_lookup): The --pgp6 "use the primary key"
+ behavior should only apply while data signing and not encryption.
+ Noted by Roger Sondermann.
+
+2002-04-19 Werner Koch <wk@gnupg.org>
+
+ * keygen.c (keygen_set_std_prefs): Put back 3DES because the RFC
+ says it is good form to do so.
+
+2002-04-19 David Shaw <dshaw@jabberwocky.com>
+
+ * keyedit.c (menu_deluid): Only cause a trust update if we delete
+ a non-revoked user id.
+
+ * hkp.c (hkp_ask_import), keyserver.c (parse_keyserver_options,
+ keyserver_spawn), options.h: Remove fast-import keyserver option
+ (no longer meaningful).
+
+ * g10.c (main), keyedit.c (sign_uids), options.h: Change
+ --default-check-level to --default-cert-check-level as it makes
+ clear what it operates on.
+
+ * g10.c (main): --pgp6 also implies --no-ask-sig-expire.
+
+ * delkey.c (do_delete_key): Comment.
+
+ * keyedit.c (sign_uids, keyedit_menu, menu_deluid, menu_delsig,
+ menu_expire, menu_revsig, menu_revkey): Only force a trustdb check
+ if we did something that changes it.
+
+ * g10.c: add "--auto-check-trustdb" to override a
+ "--no-auto-check-trustdb"
+
+2002-04-19 Werner Koch <wk@gnupg.org>
+
+ * tdbio.c (tdbio_write_nextcheck): Return a status whether the
+ stamp was actually changed.
+ * trustdb.c (revalidation_mark): Sync the changes. Removed the
+ sync operation done by its callers.
+ (get_validity): Add logic for maintaining a pending_check flag.
+ (clear_ownertrust): New.
+
+ * keyedit.c (sign_uids): Don't call revalidation_mark depending on
+ primary_pk.
+ (keyedit_menu): Call revalidation_mark after "trust".
+ (show_key_with_all_names): Print a warning on the wrong listed key
+ validity.
+
+ * delkey.c (do_delete_key): Clear the owenertrust information when
+ deleting a public key.
+
+2002-04-18 Werner Koch <wk@gnupg.org>
+
+ * seskey.c (encode_md_value): Print an error message if a wrong
+ digest algorithm is used with DSA. Changed all callers to cope
+ with a NULL return. Problem noted by Imad R. Faiad.
+
+2002-04-18 David Shaw <dshaw@jabberwocky.com>
+
+ * trustdb.c (mark_usable_uid_certs): Properly handle nonrevocable
+ signatures that can expire. In short, the only thing that can
+ override an unexpired nonrevocable signature is another unexpired
+ nonrevocable signature.
+
+ * getkey.c (finish_lookup): Always use primary signing key for
+ signatures when --pgp6 is on since pgp6 and 7 do not understand
+ signatures made by signing subkeys.
+
+2002-04-18 Werner Koch <wk@gnupg.org>
+
+ * trustdb.c (validate_keys): Never schedule a nextcheck into the
+ past.
+ (validate_key_list): New arg curtime use it to set next_expire.
+ (validate_one_keyblock): Take the current time from the caller.
+ (clear_validity, reset_unconnected_keys): New.
+ (validate_keys): Reset all unconnected keys.
+
+ * getkey.c (premerge_public_with_secret): Fixed 0x12345678! syntax
+ for use with secret keys.
+ (lookup): Advance the searchmode after a search FIRST.
+
+ * seckey-cert.c (do_check): Always calculate the old checksum for
+ use after unprotection.
+
+ * g10.c, options.skel: New option --no-escape-from. Made
+ --escape-from and --force-v3-sigs the default and removed them
+ from the options skeleton.
+
+2002-04-16 Werner Koch <wk@gnupg.org>
+
+ * parse-packet.c (parse_key): Support a SHA1 checksum as per
+ draft-rfc2440-bis04.
+ * packet.h (PKT_secret_key): Add field sha1chk.
+ * seckey-cert.c (do_check): Check the SHA1 checksum
+ (protect_secret_key): And create it.
+ * build-packet.c (do_secret_key): Mark it as sha-1 protected.
+ * g10.c, options.h: New option --simple-sk-checksum.
+
+2002-04-13 David Shaw <dshaw@jabberwocky.com>
+
+ * parse-packet.c (parse_signature): Minor fix - signatures should
+ expire at their expiration time and not one second later.
+
+ * keygen.c (proc_parameter_file): Allow specifying preferences
+ string (i.e. "s5 s2 z1 z2", etc) in a batchmode key generation
+ file.
+
+ * keyedit.c (keyedit_menu): Print standard error message when
+ signing a revoked key (no new translation).
+
+ * getkey.c (merge_selfsigs): Get the default set of key prefs from
+ the real (not attribute) primary uid.
+
+2002-04-12 David Shaw <dshaw@jabberwocky.com>
+
+ * pkclist.c (build_pk_list): Fix bug that allowed a key to be
+ selected twice in batch mode if one instance was the default
+ recipient and the other was an encrypt-to. Noted by Stefan
+ Bellon.
+
+ * parse-packet.c (dump_sig_subpkt): Show data in trust and regexp
+ sig subpackets.
+
+ * keyedit.c (keyedit_menu): Use new function real_uids_left to
+ prevent deleting the last real (i.e. non-attribute) uid. Again,
+ according to the attribute draft. (menu_showphoto): Make another
+ string translatable.
+
+2002-04-11 David Shaw <dshaw@jabberwocky.com>
+
+ * build-packet.c (build_sig_subpkt): Delete subpackets from both
+ hashed and unhashed area on update. (find_subpkt): No longer
+ needed.
+
+ * keyedit.c (sign_uids): With --pgp2 on, refuse to sign a v3 key
+ with a v4 signature. As usual, --expert overrides. Try to tweak
+ some strings to a closer match so they can all be translated in
+ one place. Use different helptext keys to allow different help
+ text for different questions.
+
+ * keygen.c (keygen_upd_std_prefs): Remove preferences from both
+ hashed and unhashed areas if they are not going to be used.
+
+2002-04-10 David Shaw <dshaw@jabberwocky.com>
+
+ * misc.c (pct_expando), options.skel: Use %t to indicate type of a
+ photo ID (in this version, it's always "jpeg"). Also tweak string
+ expansion loop to minimize reallocs.
+
+ * mainproc.c (do_check_sig): Variable type fix.
+
+ * keyedit.c (menu_set_primary_uid): Differentiate between true
+ user IDs and attribute user IDs when making one of them primary.
+ That is, if we are making a user ID primary, we alter user IDs.
+ If we are making an attribute packet primary, we alter attribute
+ packets. This matches the language in the latest attribute packet
+ draft.
+
+ * keyedit.c (sign_uids): No need for the empty string hack.
+
+ * getkey.c (fixup_uidnode): Only accept preferences from the
+ hashed segment of the self-sig.
+
+2002-04-10 Werner Koch <wk@gnupg.org>
+
+ * tdbio.c (migrate_from_v2): Fixed the offset to read the old
+ ownertrust value and only add entries to the table if we really
+ have a value.
+
+2002-04-08 David Shaw <dshaw@jabberwocky.com>
+
+ * status.h, status.c (get_status_string): Add KEYEXPIRED, EXPSIG,
+ and EXPKEYSIG. Add "deprecated-use-keyexpired-instead" to
+ SIGEXPIRED.
+
+ * sig-check.c (do_check): Start transition from SIGEXPIRED to
+ KEYEXPIRED, since the actual event is signature verification by an
+ expired key and not an expired signature. (do_signature_check,
+ packet.h): Rename as signature_check2, make public, and change all
+ callers.
+
+ * mainproc.c (check_sig_and_print, do_check_sig): Use status
+ EXPSIG for an expired, but good, signature. Add the expiration
+ time (or 0) to the VALIDSIG status line. Use status KEYEXPSIG for
+ a good signature from an expired key.
+
+ * g10.c (main): remove checks for no arguments now that argparse
+ does it.
+
+2002-04-06 Werner Koch <wk@gnupg.org>
+
+ * keyring.c (keyring_get_keyblock): Disable the keylist mode here.
+
+ * encode.c (encode_simple, encode_crypt): Only test on compressed
+ files if a compress level was not explicity set.
+
+ * keygen.c (keygen_set_std_prefs): Removed Blowfish and Twofish
+ from the list of default preferences, swapped the preferences of
+ RMD160 and SHA1. Don't include a preference to 3DES unless the
+ IDEA kludge gets used.
+
+ * free-packet.c (free_packet): call free_encrypted also for
+ PKT_ENCRYPTED_MDC.
+
+ * compress.c (release_context): New.
+ (handle_compressed): Allocate the context and setup a closure to
+ release the context. This is required because there is no
+ 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 <wk@gnupg.org>
+
+ * Makefile.am (EXTRA_DIST): Removed OPTIONS an pubring.asc - they
+ are no longer of any use.
+
+2002-04-03 David Shaw <dshaw@jabberwocky.com>
+
+ * keyserver.c (parse_keyserver_options): fix auto-key-retrieve to
+ actually work as a keyserver-option (noted by Roger Sondermann).
+
+ * keylist.c (reorder_keyblock): do not reorder the primary
+ attribute packet - the first user ID must be a genuine one.
+
+2002-03-31 David Shaw <dshaw@jabberwocky.com>
+
+ * keylist.c (list_keyblock_colon): Fix ownertrust display with
+ --with-colons.
+
+ * keygen.c (generate_user_id), photoid.c (generate_photo_id):
+ Properly initialize the user ID refcount. A few more "y/n" ->
+ "y/N" in photoid.c.
+
+ * keyedit.c (ask_revoke_sig): Warn the user if they are about to
+ revoke an expired sig (not a problem, but they should know). Also
+ tweak a few prompts to change "y/n" to "y/N", which is how most
+ other prompts are written.
+
+ * keyserver.c (keyserver_search_prompt): Control-d escapes the
+ keyserver search prompt.
+
+ * pkclist.c (show_revocation_reason & callers): If a subkey is
+ considered revoked solely because the parent key is revoked, print
+ the revocation reason from the parent key.
+
+ * trustdb.c (get_validity): Allow revocation/expiration to apply
+ to a uid/key with no entry in the trustdb.
+
+2002-03-29 David Shaw <dshaw@jabberwocky.com>
+
+ * keyserver.c (printunquoted): unquote backslashes from keyserver
+ searches
+
+ * hkp.c (write_quoted): quote backslashes from keyserver searches
+
+2002-03-26 Werner Koch <wk@gnupg.org>
+
+ * keygen.c (ask_keysize): Removed the warning for key sizes > 1536.
+
+2002-03-25 Werner Koch <wk@gnupg.org>
+
+ * keyedit.c (sign_uids): Use 2 strings and not a %s so that
+ translations can be done the right way.
+ * helptext.c: Fixed small typo.
+
+2002-03-23 David Shaw <dshaw@jabberwocky.com>
+
+ * import.c (append_uid, merge_sigs): it is okay to import
+ completely non-signed uids now (with --allow-non-selfsigned-uid).
+
+ * getkey.c (get_primary_uid, merge_selfsigs_main): do not choose
+ an attribute packet (i.e. photo) as primary uid. This prevents
+ oddities like "Good signature from [image of size 2671]". This is
+ still not perfect (one can still select an attribute packet as
+ primary in --edit), but is closer to the way the draft is going.
+
+ * g10.c (build_list): algorithms should include 110.
+
+ * g10.c (main): --pgp2 implies --no-ask-sig-expire and
+ --no-ask-cert-expire as those would cause a v4 sig/cert.
+
+ * armor.c (is_armor_header): be more lenient in what constitutes a
+ valid armor header (i.e. -----BEGIN blah blah-----) as some
+ Windows programs seem to add spaces at the end. --openpgp makes
+ it strict again.
+
+2002-03-18 David Shaw <dshaw@jabberwocky.com>
+
+ * keyserver.c (keyserver_search_prompt): Properly handle a "no
+ keys found" case from the internal HKP code (external HKP is ok).
+ Also, make a COUNT -1 (i.e. streamed) keyserver response a little
+ more efficient.
+
+ * g10.c (main): Add --no-allow-non-selfsigned-uid
+
+2002-03-17 David Shaw <dshaw@jabberwocky.com>
+
+ * g10.c (main): --openpgp implies --allow-non-selfsigned-uid.
+
+ * getkey.c (merge_selfsigs_main): If none of the uids are primary
+ (because none are valid) then pick the first to be primary (but
+ still invalid). This is for cosmetics in case some display needs
+ to print a user ID from a non-selfsigned key. Also use
+ --allow-non-selfsigned-uid to make such a key valid and not
+ --always-trust. The key is *not* automatically trusted via
+ --allow-non-selfsigned-uid.
+
+ * mainproc.c (check_sig_and_print): Make sure non-selfsigned uids
+ print [uncertain] on verification even though one is primary now.
+
+ * getkey.c (merge_selfsigs): If the main key is not valid, then
+ neither are the subkeys.
+
+ * import.c (import_one): Allow --allow-non-selfsigned-uid to work
+ on completely unsigned keys. Print the uids in UTF8. Remove
+ mark_non_selfsigned_uids_valid().
+
+ * keyedit.c (show_key_with_all_names): Show revocation key as
+ UTF8.
+
+ * sign.c (clearsign_file): Allow --not-dash-escaped to work with
+ v3 keys.
+
+2002-03-14 Werner Koch <wk@gnupg.org>
+
+ * main.h: Changed the default algorithms to CAST5 and SHA1.
+
+2002-03-13 David Shaw <dshaw@jabberwocky.com>
+
+ * import.c (chk_self_sigs): Show which user ID a bad self-sig
+ (invald sig or unsupported public key algorithm) resides on.
+
+ * import.c (chk_self_sigs): any valid self-sig should mark a user
+ ID or subkey as valid - otherwise, an attacker could DoS the user
+ by inventing a bogus invalid self-signature.
+
+2002-03-07 David Shaw <dshaw@jabberwocky.com>
+
+ * g10.c (main): make a few more strings translatable.
+
+ * options.h, options.skel, g10.c (main), gpgv.c, mainproc.c
+ (check_sig_and_print), keyserver.c (parse_keyserver_options):
+ --auto-key-retrieve should really be a keyserver-option variable.
+
+ * import.c (revocation_present): new function to print a warning
+ if a key is imported that has been revoked by designated revoker,
+ but the designated revoker is not present to verify the
+ revocation. If keyserver-options auto-key-retrieve is set, try
+ and fetch the designated revoker from the keyserver.
+
+ * import.c (import_one): call revocation_present after importing a
+ new key. Note that this applies to --import, --recv-keys, and
+ --search-keys.
+
+ * keyserver-internal.h, keyserver.c (keyserver_import_fprint):
+ import via fingerprint (for revocation keys).
+
+ * keyserver.c (keyserver_import_keyid): much simpler
+ implementation now that we're using KEYDB_SEARCH_DESC internally.
+
+2002-03-04 David Shaw <dshaw@jabberwocky.com>
+
+ * revoke.c (gen_revoke): do not prompt for revocation reason for
+ v3 revocations (unless force-v4-certs is on) since they wouldn't
+ be used anyway.
+
+ * keyedit.c (menu_revsig): show the status of the sigs
+ (exportable? revocable?) to the user before prompting for which
+ sig to revoke. Also, make sure that local signatures get local
+ revocations.
+
+ * keyedit.c (ask_revoke_sig): remind the user which sigs are
+ local.
+
+ * g10.c (main): Add "exec-path" variable to override PATH for
+ execing programs.
+
+ * export.c (do_export_stream): properly check return code from
+ classify_user_id to catch unclassifiable keys.
+
+2002-03-03 David Shaw <dshaw@jabberwocky.com>
+
+ * parse-packet.c (parse_signature): variable type tweak for RISC
+ OS (from Stefan)
+
+2002-02-28 David Shaw <dshaw@jabberwocky.com>
+
+ * getkey.c (check_revocation_keys): New function to check a
+ revocation against a list of potential revocation keys. Note the
+ loop-breaking code here. This is to prevent blowing up if A is
+ B's revocation key, while B is also A's. Note also that this is
+ written so that a revoked revoker can still issue revocations:
+ i.e. If A revokes B, but A is revoked, B is still revoked. I'm
+ not completely convinced this is the proper behavior, but it
+ matches how PGP does it. It does at least have the advantage of
+ much simpler code - my first version of this had lots of loop
+ maintaining code so you could chain revokers many levels deep and
+ if D was revoked, C was not, which meant that B was, and so on.
+ It was sort of scary, actually.
+
+ * getkey.c (merge_selfsigs_main): Add any revocation keys onto the
+ pk. This is particularly interesting since we normally only get
+ data from the most recent 1F signature, but you need multiple 1F
+ sigs to properly handle revocation keys (PGP does it this way, and
+ a revocation key could be marked "sensitive" and hence in a
+ different signature). Also, if a pk has a revocation key set,
+ check for revocation sigs that were not made by us - if made by a
+ valid revocation key, mark the pk revoked.
+
+ * packet.h, getkey.c (cache_public_key): do not cache key if
+ "dont_cache" is set. This allows the revocation key code to look
+ up a key and return information that may be inaccurate to prevent
+ loops without caching the fake data.
+
+ * packet.h, sig-check.c (do_signature_check): Record if a
+ signature was made by a revoked pk.
+
+ * packet.h, parse-packet.c (parse_one_sig_subpkt,
+ can_handle_critical, parse_signature): Get revocation key
+ information out of direct sigs.
+
+ * keylist.c (list_keyblock_print): don't assume that the presence
+ of a 0x20 signature means the key is revoked. With revocation
+ keys, this may not be true if the revocation key is not around to
+ verify it or if verification failed. Also, 0x1F should get listed
+ as "sig", and not "unexpected signature class".
+
+ * keyedit.c (show_key_with_all_names): Add a flag for printing
+ revoker information and change all callers.
+
+ * import.c (merge_blocks): merge in any new direct key (0x1F)
+ sigs.
+
+ * import.c (import_revoke_cert): don't keep processing after a
+ revocation is rejected.
+
+ * import.c (delete_inv_parts): Allow importing a revocation
+ signature even if it was not issued by the key. This allows a
+ revocation key to issue it. Of course, the sig still needs to be
+ checked before we trust it.
+
+ * free-packet.c (copy_public_key): Include a new copy of the
+ revocation keys when duping a pk.
+
+ * free-packet.c (free_seckey_enc, release_public_key_parts): Free
+ any revocation keys that are attached to a sig or pk.
+
+ * export.c (do_export_stream): Do not export signatures with
+ "sensitive" revocation keys in them.
+
+2002-02-27 David Shaw <dshaw@jabberwocky.com>
+
+ * export.c (do_export_stream): Do not include v3 keys in a
+ --export-secret-subkeys export.
+
+ * getkey.c (merge_selfsigs_main): If a key isn't valid (say,
+ because of no self-signature), allow --always-trust to force it
+ valid so it can be trusted.
+
+2002-02-25 David Shaw <dshaw@jabberwocky.com>
+
+ * hkp.c (hkp_ask_import), hkp.h, keyserver.c (all): treat key
+ lists internally as fingerprints when possible. All this is via
+ KEYDB_SEARCH_DESC - no point in reinventing the wheel. This allows
+ the helper program to search the keyserver by fingerprint if
+ desired (and the keyserver supports it). Note that automatic
+ fingerprint promotion during refresh only applies to v4 keys as a
+ v4 fingerprint can be easily changed into a long or short key id,
+ and a v3 cannot.
+
+ * pubkey-enc.c, getkey.c, misc.c, main.h: Take two copies of
+ hextobyte() from pubkey-enc.c and getkey.c and make them into one
+ copy in misc.c.
+
+2002-02-22 David Shaw <dshaw@jabberwocky.com>
+
+ * keyserver.c (keyserver_search_prompt): Detect a "no keys found"
+ case even if the helper program does not explicitly say how many
+ keys were found.
+
+ * hkp.c (parse_hkp_index): Bug fix - don't report non-revoked keys
+ as revoked in HKP key searches.
+
+2002-02-19 Werner Koch <wk@gnupg.org>
+
+ * parse-packet.c (parse_trust): Made parsing more robust.
+
+2002-02-19 David Shaw <dshaw@jabberwocky.com>
+
+ * hkp.c (parse_hkp_index): Catch corruption in HKP index lines
+ (can be caused by broken or malicious keyservers).
+
+ * keyserver.c (keyserver_work): Add KEYSERVER_NOT_SUPPORTED for
+ unsupported actions (say, a keyserver that has no way to search,
+ or a readonly keyserver that has no way to add). Also add a
+ USE_EXTERNAL_HKP define to disable the internal HKP keyserver
+ code.
+
+2002-02-14 Werner Koch <wk@gnupg.org>
+
+ * g10.c: New option --no-use-agent.
+
+ * pkclist.c (check_signatures_trust): Always print the warning for
+ unknown and undefined trust. Removed the did_add cruft. Reported
+ by Janusz A. Urbanowicz.
+
+2002-02-11 David Shaw <dshaw@jabberwocky.com>
+
+ * hkp.c (parse_hkp_index): Bug fix - properly handle user IDs with
+ colons (":") in them while HKP searching.
+
+2002-02-09 David Shaw <dshaw@jabberwocky.com>
+
+ * misc.c (pct_expando): More comments.
+
+ * keydb.h, sign.c (mk_notation_and_policy): Clarify what is a sig
+ and what is a cert. A sig has sigclass 0x00, 0x01, 0x02, or 0x40,
+ and everything else is a cert.
+
+ * g10.c (main), keyedit.c (keyedit_menu): Add a "nrlsign" for
+ nonrevocable and local key signatures.
+
+ * g10.c (main): Add a --no-force-mdc to undo --force-mdc.
+
+ * options.h, g10.c (main), cipher.c (write_header): Add a knob to
+ --disable-mdc/--no-disable-mdc. Off by default, of course, but is
+ used in --pgp2 and --pgp6 modes.
+
+ * pkclist.c (build_pk_list): Allow specifying multiple users in
+ the "Enter the user ID" loop. Enter a blank line to stop. Show
+ each key+id as it is added.
+
+ * keylist.c (show_policy_url), mainproc.c (print_notation_data):
+ It is not illegal (though possibly silly) to have multiple policy
+ URLs in a given signature, so print all that are present.
+
+ * hkp.c (hkp_search): More efficient implementation of URL-ifying
+ code.
+
+2002-02-04 David Shaw <dshaw@jabberwocky.com>
+
+ * main.h, misc.c (pct_expando): New function to generalize
+ %-expando processing in any arbitrary string.
+
+ * photoid.c (show_photo): Call the new pct_expando function rather
+ than expand strings internally.
+
+ * sign.c (mk_notation_and_policy): Show policy URLs and notations
+ when making a signature if show-policy/show-notation is on.
+ %-expand policy URLs during generation. This lets the user have
+ policy URLs of the form "http://notary.jabberwocky.com/keysign/%K"
+ which will generate a per-signature policy URL.
+
+ * main.h, keylist.c (show_policy_url, show_notation): Add amount
+ to indent so the same function can be used in key listings as well
+ as during sig generation. Change all callers.
+
+2002-02-04 David Shaw <dshaw@jabberwocky.com>
+
+ * keyserver.c, options.h (parse_keyserver_options, keyidlist):
+ Workaround for the pksd and OKS keyserver bug that calculates v4
+ RSA keyids as if they were v3. The workaround/hack is to fetch
+ both the v4 (e.g. 99242560) and v3 (e.g. 68FDDBC7) keyids. This
+ only happens for key refresh while using the HKP scheme and the
+ refresh-add-fake-v3-keyids keyserver option must be set. This
+ should stay off by default.
+
+2002-02-03 David Shaw <dshaw@jabberwocky.com>
+
+ * keyserver.c (keyserver_spawn): Bug fix - do not append keys to
+ each other when --sending more than one.
+
+2002-02-02 David Shaw <dshaw@jabberwocky.com>
+
+ * options.h, g10.c (main), keyedit.c (sign_uids), sign.c
+ (mk_notation_and_policy): Split "--set-policy-url" into
+ "--cert-policy-url" and "--sig-policy-url" so the user can set
+ different policies for key and data signing. For backwards
+ compatibility, "--set-policy-url" sets both, as before.
+
+2002-01-30 Werner Koch <wk@gnupg.org>
+
+ * g10.c (main): --gen-random --armor does now output a base64
+ encoded string.
+
+2002-01-28 David Shaw <dshaw@jabberwocky.com>
+
+ * g10.c (main), options.h, pkclist.c (algo_available): --pgp6
+ flag. This is not nearly as involved as --pgp2. In short, it
+ turns off force_mdc, turns on no_comment, escape_from, and
+ force_v3_sigs, and sets compression to 1. It also restricts the
+ user to IDEA (if present), 3DES, CAST5, MD5, SHA1, and RIPEMD160.
+ See the comments above algo_available() for lots of discussion on
+ why you would want to do this.
+
+2002-01-27 David Shaw <dshaw@jabberwocky.com>
+
+ * keygen.c (keygen_set_std_prefs): Comment
+
+ * keyedit.c (sign_uids): Bug fix - when signing with multiple
+ secret keys at the same time, make sure each key gets the sigclass
+ prompt.
+
+ * exec.c (exec_finish): Close the iobuf and FILE before trying to
+ waitpid, so the remote process will get a SIGPIPE and exit. This
+ is only a factor when using a pipe to communicate.
+
+ * exec.c (exec_write): Disable cache-on-close of the fd iobuf (is
+ this right? Why is a fd iobuf cached at all?)
+
+2002-01-26 Werner Koch <wk@gnupg.org>
+
+ * g10.c, options.h: New option --gpg-agent-info
+ * passphrase.c (agent_open): Let it override the environment info.
+ * seckey-cert.c (check_secret_key): Always try 3 times when the
+ agent is enabled.
+ * options.skel: Describe --use-agent.
+
+2002-01-24 David Shaw <dshaw@jabberwocky.com>
+
+ * pubkey-enc.c (is_algo_in_prefs, get_it): Only check preferences
+ against keys with v4 self sigs - there is really little point in
+ warning for every single non-IDEA message encrypted to an old key.
+
+ * pkclist.c (select_algo_from_prefs): Only put in the fake IDEA
+ preference if --pgp2 is on.
+
+ * mainproc.c (check_sig_and_print): Print "Expired" for expired
+ but good signatures (this still prints "BAD" for expired but bad
+ signatures).
+
+2002-01-23 David Shaw <dshaw@jabberwocky.com>
+
+ * keygen.c (ask_keysize): Cosmetic: don't present a RSA signing
+ key as a "keypair" which can be 768 bits long (as RSA minimum is
+ 1024).
+
+ * pubkey-enc.c (is_algo_in_prefs): Allow IDEA as a fake preference
+ for v3 keys with v3 selfsigs.
+
+2002-01-22 David Shaw <dshaw@jabberwocky.com>
+
+ * packet.h, getkey.c (merge_selfsigs_main), pkclist.c
+ (select_algo_from_prefs): Implement the fake IDEA preference as
+ per RFC2440:12.1. This doesn't mean that IDEA will be used (the
+ plugin may not be present), but it does mean that a v3 key with a
+ v3 selfsig has an implicit IDEA preference instead of 3DES. v3
+ keys with v4 selfsigs use preferences as normal.
+
+ * encode.c (encode_crypt): if select_algo_from_prefs fails, this
+ means that we could not find a cipher that both keys like. Since
+ all v4 keys have an implicit 3DES preference, this means there is
+ a v3 key with a v3 selfsig in the list. Use 3DES in this case as
+ it is the safest option (we know the v4 key can handle it, and
+ we'll just hope the v3 key is being used in an implementation that
+ can handle it). If --pgp2 is on, warn the user what we're doing
+ since it'll probably break PGP2 compatibility.
+
+ * g10.c (main): Do not force using IDEA for encrypted files in
+ --pgp2 mode - let the fake IDEA preference choose this for us for
+ better compatibility when encrypting to multiple keys, only some
+ of which are v3.
+
+ * keygen.c (keygen_set_std_prefs): Put 3DES on the end of the
+ default cipher pref list (RFC2440: "...it is good form to place it
+ there explicitly."). If the user has the IDEA plugin installed,
+ put a preference for IDEA *after* 3DES to effectively disable its
+ use for everything except encrypting along with v3 keys.
+
+ * encode.c, g10.c, sign.c: Change the PGP2 warning line from
+ "... will not be usable ..." to "... may not be usable ..." as the
+ user could be using one of the enhanced PGP2 variations.
+
+ * helptext.c: Revise the sign_uid.class help text as suggested by
+ Stefan.
+
+2002-01-20 Werner Koch <wk@gnupg.org>
+
+ * passphrase.c (passphrase_to_dek): Add tryagain_text arg to be
+ used with the agent. Changed all callers.
+ (agent_get_passphrase): Likewise and send it to the agent
+ * seckey-cert.c (do_check): New arg tryagain_text.
+ (check_secret_key): Pass the string to do_check.
+ * keygen.c (ask_passphrase): Set the error text is required.
+ * keyedit.c (change_passphrase): Ditto.
+
+ * passphrase.c (agent_open): Disable opt.use_agent in case of a
+ problem with the agent.
+ (agent_get_passphrase): Ditto.
+ (passphrase_clear_cache): Ditto.
+
+2002-01-19 Werner Koch <wk@gnupg.org>
+
+ * passphrase.c (agent_open): Add support for the new Assuan based
+ gpg-agent. New arg to return the used protocol version.
+ (agent_get_passphrase): Implemented new protocol here.
+ (passphrase_clear_cache): Ditto.
+ (readline): New.
+
+2002-01-15 Timo Schulz <ts@winpt.org>
+
+ * encode.c (encode_crypt_files): Fail if --output is used.
+
+ * g10.c: New command --decrypt-files.
+
+ * decrypt.c (decrypt_messages): New.
+
+2002-01-09 David Shaw <dshaw@jabberwocky.com>
+
+ * g10.c, misc.c, gpgv.c: move idea_cipher_warn to misc.c so gpgv.c
+ doesn't need a stub for it any longer.
+
+ * g10.c (get_temp_dir), main.h: no longer used (it's in exec.c now)
+
+ * g10.c (main), delkey.c (delete_keys), main.h : Allow
+ --delete-key (now --delete-keys, though --delete-key still works,
+ of course) to delete multiple keys in one go. This applies to
+ --delete-secret-key(s) and --delete-secret-and-public-key(s) as
+ well.
+
+2002-01-09 Timo Schulz <ts@winpt.org>
+
+ * encode.c (encode_crypt_files): Now it behaves like verify_files.
+
+ * g10.c (main): We don't need to check argc for encode_crypt_files
+ any longer.
+
+2002-01-09 Timo Schulz <ts@winpt.org>
+
+ * exec.c: Include windows.h for dosish systems.
+
+2002-01-08 Timo Schulz <ts@winpt.org>
+
+ * g10.c (main): New description for --encrypt-files.
+
+2002-01-08 Werner Koch <wk@gnupg.org>
+
+ * g10.c (main): Must register the secring for encryption because
+ it is needed to figure out the default recipient. Reported by
+ Roger Sondermann.
+
+2002-01-05 David Shaw <dshaw@jabberwocky.com>
+
+ * keyedit.c (menu_adduid): Require --expert before adding a photo
+ ID to a v3 key, and before adding a second photo ID to any key.
+
+ * keyedit.c (keyedit_menu): Don't allow adding photo IDs in
+ rfc1991 or pgp2 mode.
+
+ * getkey.c (merge_selfsigs_subkey): Permit v3 subkeys. Believe it
+ or not, this is allowed by rfc 2440, and both PGP 6 and PGP 7 work
+ fine with them.
+
+ * g10.c, options.h, keyedit.c, sign.c: Move the "ask for
+ expiration" switch off of --expert, which was getting quite
+ overloaded, and onto ask-sig-expire and ask-cert-expire. Both
+ default to off.
+
+ * g10.c (main): Change the default compression algo to 1, to be
+ more OpenPGP compliant (PGP also uses this, so it'll help with
+ interoperability problems as well).
+
+ * encode.c (encode_crypt): Handle compression algo 2, since the
+ default is now 1.
+
+ * build-packet.c (build_attribute_subpkt): Fix off-by-one error.
+
+2002-01-05 Werner Koch <wk@gnupg.org>
+
+ * g10.c (main): Do not register the secret keyrings for certain
+ commands.
+
+ * keydb.c (keydb_add_resource): Use access to test for keyring
+ existence. This avoids cached opened files which are bad under
+ RISC OS.
+
+2002-01-04 David Shaw <dshaw@jabberwocky.com>
+
+ * sign.c (sign_file, sign_symencrypt_file): always use one-pass
+ packets unless rfc1991 is enabled. This allows a signature made
+ with a v3 key to work in PGP 6 and 7. Signatures made with v4
+ keys are unchanged.
+
+ * g10.c (main): Disallow non-detached signatures in PGP2 mode.
+ Move the "you must use files and not pipes" PGP2 warning up so all
+ the PGP2 stuff is together.
+
+ * encode.c (encode_simple): Use the actual filesize instead of
+ partial length packets in the internal literal packet from a
+ symmetric message. This breaks PGP5(?), but fixes PGP2, 6, and 7.
+ It's a decent tradeoff. Note there was only an issue with
+ old-style RFC1991 symmetric messages. 2440-style messages in 6
+ and 7 work with or without partial length packets.
+
+2002-01-03 David Shaw <dshaw@jabberwocky.com>
+
+ * g10.c (main): Removed --no-default-check-level option, as it is
+ not consistent with other "default" options. Plus, it is the same
+ as saying --default-check-level 0.
+
+ * exec.c (exec_read): Disallow caching tempfile from child
+ process, as this keeps the file handle open and can cause unlink
+ problems on some platforms.
+
+ * keyserver.c (keyserver_search_prompt): Minor tweak - don't
+ bother to transform keyids into textual form if they're just going
+ to be transformed back to numbers.
+
+2002-01-03 Timo Schulz <ts@winpt.org>
+
+ * g10.c: New command --encrypt-files.
+
+ * verify.c (print_file_status): Removed the static because
+ encode_crypt_files also uses this function.
+
+ * main.h (print_files_status): New.
+ (encode_crypt_files): New.
+
+ * encode.c (encode_crypt_files): New.
+
+2002-01-02 Stefan Bellon <sbellon@sbellon.de>
+
+ * keyserver.c: Moved util.h include down in order to avoid
+ redefinition problems on RISC OS.
+
+ * keyring.c (keyring_lock): Only lock keyrings that are writable.
+
+ * keyring.c (keyring_update_keyblock): Close unused iobuf.
+
+ * hkp.c (parse_hkp_index, hkp_search) [__riscos__]: Changed
+ unsigned char* to char* because of compiler issues.
+
+ * exec.c (exec_finish) [__riscos__]: Invalidate close cache so
+ that file can be unlinked.
+
+2001-12-28 David Shaw <dshaw@jabberwocky.com>
+
+ * g10.c (main): Use a different strlist to check extensions since
+ they need to be handled seperately now.
+
+ * misc.c,main.h (check_permissions): Properly handle permission
+ and ownership checks on files in the lib directory
+ (e.g. /usr/local/lib/gnupg), which are owned by root and are
+ world-readable, and change all callers to specify extension or
+ per-user file.
+
+ * photoid.c (show_photo), keyserver.c (keyserver_spawn): Bug fix -
+ don't call exec_finish if exec_write fails.
+
+ * keyserver.c (keyserver_spawn): Look for OPTIONS from the
+ keyserver helper - specifically, a "OUTOFBAND" option for the
+ email keyserver.
+
+ * mainproc.c (list_node), keylist.c (list_keyblock_colon),
+ import.c (delete_inv_parts), export.c (do_export_stream): Use
+ signature flags for exportability check rather than re-parsing the
+ subpacket.
+
+ * keyid.c, keydb.h (get_lsign_letter): No longer needed.
+
+2001-12-27 David Shaw <dshaw@jabberwocky.com>
+
+ * exec.c (exec_finish): Show errors when temp files cannot be
+ deleted for whatever reason.
+
+ * exec.c (exec_read): Don't rely on WEXITSTATUS being present.
+
+ * exec.c (make_tempdir): Add temp file creator for win32. Don't
+ create an incoming temp file if the exec is write-only.
+
+ * keyserver.c (keyserver_spawn): Clean up error handling, for when
+ the spawn fails.
+
+ * photoid.c (show_photo): Clean up error handling.
+
+ * misc.c (check_permissions): Neaten.
+
+2001-12-25 David Shaw <dshaw@jabberwocky.com>
+
+ * mkdtemp.c (mkdtemp): Add copyleft info and tweak the 'X' counter
+ to be a bit simpler.
+
+ * keyserver.c, photoid.c: Remove unused headers left over from
+ when the exec functions lived there.
+
+2001-12-23 Timo Schulz <ts@winpt.org>
+
+ * misc.c (check_permissions): Do not use it for W32 systems.
+
+ * tdbio.c (migrate_from_v2): Define ftruncate as chsize() for W32.
+
+ * mkdtemp.c: W32 support.
+
+ * photoid.c: Ditto.
+
+ * exec.c: Ditto.
+
+2001-12-22 David Shaw <dshaw@jabberwocky.com>
+
+ * exec.c (make_tempdir): avoid compiler warning with const
+
+ * mkdtemp.c (mkdtemp): catch the empty ("") string case in case
+ someone repurposes mkdtemp at some point.
+
+ * photoid.c (generate_photo_id, show_photo): some type changes
+ from Stefan Bellon.
+
+ * exec.c (make_tempdir): handle Win32 systems, suggested by Timo
+ Schulz.
+
+2001-12-22 Werner Koch <wk@gnupg.org>
+
+ * encode.c (encode_simple, encode_crypt): i18n 2 strings.
+
+2001-12-22 Timo Schulz <ts@winpt.org>
+
+ * encode.c (encode_simple, encode_crypt): Use is_file_compressed
+ to avoid to compress compressed files.
+
+2001-12-22 Werner Koch <wk@gnupg.org>
+
+ * keyserver.c (keyserver_spawn): Removed some variables
+ declaration due to shadowing warnings.
+
+ * build-packet.c (build_attribute_subpkt): s/index/idx/ to avoid
+ compiler warnig due to index(3).
+
+ * getkey.c (get_ctx_handle): Use KEYDB_HANDLE as return value.
+ * keylist.c (list_one): Made resname const.
+
+ * keyedit.c (keyedit_menu): Allow "addphoto" only when --openpgp is
+ not used.
+
+ * options.skel: Changed one example photo viewer to qiv.
+
+2001-12-21 David Shaw <dshaw@jabberwocky.com>
+
+ * Makefile.am: add exec.c, exec.h, photoid.c, and photoid.h
+
+ * build-packet.c (build_attribute_subpkt): new function to build
+ the raw attribute subpacket. Note that attribute subpackets have
+ the same format as signature subpackets.
+
+ * exec.c: new file with generic exec-a-program functionality.
+ Used by both photo IDs and keyserver helpers. This is pretty much
+ the same code that used to be keyserver specific, with some
+ changes to be usable generically.
+
+ * free-packet.c (free_attributes (new)): function to free an
+ attribute packet.
+
+ * gpgv.c: added stub show_photo
+
+ * keyedit.c (keyedit_menu, menu_adduid, menu_showphoto): can add a
+ photo (calls generate_photo_id), or display a photo (calls
+ show_photo) from the --edit menu. New commands are "addphoto",
+ and "delphoto" (same as "deluid").
+
+ * keylist.c (list_keyblock_print): show photos during key list if
+ --show-photos enabled.
+
+ * keyserver.c (keyserver_spawn): use the generic exec_xxx
+ functions to call keyserver helper.
+
+ * g10.c, options.h: three new options - --{no-}show-photos, and
+ --photo-viewer to give the command line to display a picture.
+
+ * options.skel: instructions for the photo viewer
+
+ * parse-packet.c (parse_user_id, setup_user_id (new)): common code
+ for both user IDs and attribute IDs moved to setup_user_id.
+
+ * parse-packet.c (make_attribute_uidname (new)): constructs a fake
+ "name" for attribute packets (e.g. "[image of size ...]")
+
+ * parse-packet.c (parse_attribute (replaces parse_photo_id),
+ parse_attribute_subpkts): Builds an array of individual
+ attributes. Currently only handles attribute image / type jpeg
+ subpackets.
+
+ * sign.c (hash_uid): Fix bug in signing attribute (formerly
+ photo_id) packets.
+
+ * packet.h, and callers: globally change "photo_id" to "attribute"
+ and add structures for attributes. The packet format is generic
+ attributes, even though the only attribute type thus far defined
+ is jpeg.
+
+2001-12-21 David Shaw <dshaw@jabberwocky.com>
+
+ * parse-packet.c (can_handle_critical): Can handle critical
+ revocation subpackets now.
+
+ * trustdb.c (mark_usable_uid_certs): Disregard revocations for
+ nonrevocable sigs. Note that this allows a newer revocable
+ signature to override an older nonrevocable signature.
+
+ * sign.c (make_keysig_packet): add a duration field and change all
+ callers. This makes make_keysig_packet closer to
+ write_signature_packets and removes some duplicated expiration
+ code.
+
+ * keyedit.c (keyedit_menu, menu_revsig, sign_uids,
+ sign_mk_attrib): Add nrsign command, don't allow revoking a
+ nonrevocable signature,
+
+ * g10.c (main): Add --nrsign option to nonrevocably sign a key
+ from the command line.
+
+ * build-packet.c (build_sig_subpkt_from_sig): Comment to explain
+ the use of CRITICAL.
+
+2001-12-21 Werner Koch <wk@gnupg.org>
+
+ * g10.c. options.h : New option --show-keyring
+ * getkey.c (get_ctx_handle): New.
+ * keylist.c (list_one): Implement option here. By David Champion.
+
+2001-12-20 David Shaw <dshaw@jabberwocky.com>
+
+ * keyserver.c (keyserver_spawn): Use mkdtemp() to make temp
+ directory.
+
+ * mkdtemp.c: replacement function for those platforms that don't
+ have mkdtemp (make a temp directory securely).
+
+2001-12-19 David Shaw <dshaw@jabberwocky.com>
+
+ * misc.c (check_permissions): New function to stat() and ensure
+ the permissions of GNUPGHOME and the files have safe permissions.
+
+ * keydb.c (keydb_add_resource): Check keyring permissions.
+
+ * tdbio.c (tdbio_set_dbname): Check permissions of trustdb.gpg
+
+ * keyserver.c (keyserver_spawn): Disable keyserver schemes that
+ involve running external programs if the options file has unsafe
+ permissions or ownership.
+
+ * g10.c, options.h: New option --no-permission-warning to disable
+ the permission warning message(s). This also permits use of the
+ keyserver if it had been disabled (see above). Also check the
+ permissions/ownership of random_seed.
+
+ * keyserver.c (keyserver_spawn): The new glibc prints a warning
+ when using mktemp() (the code was already secure, but the warning
+ was bound to cause confusion). Use a different implementation
+ based on get_random_bits() instead. Also try a few times to get
+ the temp dir before giving up.
+
+2001-12-19 Werner Koch <wk@gnupg.org>
+
+ * g10.c, passphrase.c [CYGWIN32]: Allow this as an alias for MINGW32.
+
+2001-12-18 David Shaw <dshaw@jabberwocky.com>
+
+ * g10.c (idea_cipher_warn): Add a flag to show the warning always
+ or once per session and change all callers (show always except for
+ the secret key protection and unknown cipher from an encrypted
+ message errors). Also make the strings translatable.
+
+ * pubkey-enc.c (get_it): Add the IDEA cipher warning if the user
+ tries to decrypt an IDEA encrypted message without the IDEA
+ plugin.
+
+ * keyserver.c (parse_keyserver_uri): More strict checking of the
+ keyserver URI. Specifically, fail if the ":port" section is
+ anything except a number between 1 and 65535.
+
+2001-12-17 David Shaw <dshaw@jabberwocky.com>
+
+ * keyserver.c (print_keyinfo): No need to check for
+ control/illegal characters, as utf8_to_native does this for us.
+
+ * mainproc.c (proc_encrypted): Use generic IDEA warning.
+
+ * gpgv.c: add stub for idea_cipher_warn
+
+ * g10.c, hkp.c, keyserver.c: Fix capitalization and plural issues.
+
+ * encode.c (encode_crypt), sign.c (sign_file, clearsign_file):
+ disable pgp2 mode after the message is no longer pgp2 compatible.
+
+ * g10.c (main): Tweak the PGP2.x IDEA warning to use the generic
+ warning, and not merely fail if the IDEA plugin isn't there.
+
+ * g10.c (main, idea_cipher_warn), keygen.c (set_one_pref),
+ seckey-cert.c (do_check): Add a generic IDEA warning for when the
+ IDEA plugin is not present. This pops up when the user uses
+ "--cipher-algo idea", when setpref is used to set a "S1"
+ preference, and when a secret key protected with IDEA is used.
+
+2001-12-15 Werner Koch <wk@gnupg.org>
+
+ * keyserver.c (keyserver_spawn): Assert that we have dropped privs.
+
+2001-12-13 Werner Koch <wk@gnupg.org>
+
+ * pubkey-enc.c (get_session_key): Check that the public key
+ algorithm is indeed usable for en/decryption. This avoid a
+ strange error message from pubkey_decrypt if for some reasons a
+ bad algorithm indentifier is passed.
+
+2001-12-12 David Shaw <dshaw@jabberwocky.com>
+
+ * Fixed some types for portability. Noted by Stefan Bellon.
+
+2001-12-11 Werner Koch <wk@gnupg.org>
+
+ * hkp.c (hkp_export): Do not print possible control characters
+ from a keyserver response.
+ (parse_hkp_index): Made uid an unsigned char* because it is passed to
+ isspace().
+ (hkp_search): Ditto for the char* vars.
+
+ * g10.c (main): Print the IDEA warning also for -c and -se.
+
+ * g10.c (get_temp_dir): Assert that we have dropped privs
+
+ * encode.c (encode_crypt): Include the first key into the --pgp2
+ check.
+
+2001-12-07 David Shaw <dshaw@jabberwocky.com>
+
+ * g10.c, options.h: New option --pgp2. This is identical to
+ "--rfc1991 --cipher-algo idea --compress-algo 1 --digest-algo md5
+ --force_v3_sigs" with the addition of an warning to advise the
+ user not to use a pipe (which would break pgp2 compatibility).
+
+ * encode.c (encode_crypt): warn if the user tries to encrypt to
+ any key that is not RSA and <= 2048 bits when the --pgp2 option is
+ used.
+
+ * sign.c (sign_file, clearsign_file): When using --pgp2, make a v3
+ sig, and warn if the signature is made with a non-v3 key.
+
+2001-12-05 David Shaw <dshaw@jabberwocky.com>
+
+ * sign.c (sign_file, clearsign_file, sign_symencrypt_file): Prompt
+ for sig expiration if --expert is set and --force-v3-sigs is not
+ set (v3 sigs cannot expire).
+
+ * mainproc.c (check_sig_and_print): After checking a sig, print
+ expiration status. This causes a error return if the sig is
+ expired.
+
+ * build-packet.c (build_sig_subpkt_from_sig): Include a critical
+ sig expiration subpacket if the sig is to expire.
+
+ * keyedit.c (sign_uids): Do not sign an expired key unless
+ --expert is set, in which case prompt. Also, offer to expire a
+ signature when the key the user is signing expires.
+
+ * keygen.c (ask_expire_interval): Add a value to determine whether
+ to prompt for a key or sig expiration and change all callers.
+
+ * keyid.c: New functions: expirestr_from_sig and
+ colon_expirestr_from_sig.
+
+ * keylist.c (list_keyblock_colon): Show sig expiration date in the
+ --with-colons listing.
+
+ * sign.c (make_keysig_packet, write_signature_packets): Pass in an
+ optional timestamp for the signature packet, and change all
+ callers.
+
+ * keyedit.c (sign_mk_attrib): Include a critical expiration
+ subpacket in the signature if an expiration date is given.
+
+2001-12-04 David Shaw <dshaw@jabberwocky.com>
+
+ * keyedit.c (sign_uids): If the user tries to sign a
+ locally-signed key, allow the cert to be promoted to a full
+ exportable signature. This essentially deletes the old
+ non-exportable sig, and replaces it with a new exportable one.
+
+2001-12-04 David Shaw <dshaw@jabberwocky.com>
+
+ * keyedit.c (keyedit_menu): Do not allow signing a revoked key
+ unless --expert is set, and ask even then.
+
+ * keyedit.c (sign_uids): Do not allow signing a revoked UID unless
+ --expert is set, and ask even then.
+
+ * g10.c, options.h : New option --expert
+
+2001-11-16 David Shaw <dshaw@jabberwocky.com>
+
+ * Allow the user to select no compression via "--compress-algo 0"
+ on the command line.
+
+ * keyedit.c (show_prefs): Show compression preferences in the
+ long-form "showpref" style.
+
+ * keygen.c (set_one_pref): Permit setting a no-compression ("Z0")
+ preference.
+
+ * getkey.c (fixup_uidnode): Fix compression preference corruption
+ bug.
+
+2001-12-02 David Shaw <dshaw@jabberwocky.com>
+
+ * g10.c: Add advisory --for-your-eyes-only option as per section
+ 5.9 of 2440.
+
+2001-12-05 David Shaw <dshaw@jabberwocky.com>
+
+ * Force a V4 sig if the user has a notation or policy URL set.
+
+2001-12-04 David Shaw <dshaw@jabberwocky.com>
+
+ * g10.c: Add options --keyserver-options, --temp-directory, and
+ auto-key-retrieve (the opposite of no-auto-key-retrieve).
+
+ * hkp.c (hkp_search): New function to handle searching a HKP
+ keyserver for a key
+
+ * hkp.c (hkp_ask_import, hkp_export): Pretty large changes to make
+ them communicate via the generic functions in keyserver.c
+
+ * keyserver.c: new file with generic keyserver routines for
+ getting keys from a keyserver, sending keys to a keyserver, and
+ searching for keys on a keyserver. Calls the internal HKP stuff
+ in hkp.c for HKP keyserver functions. Other calls are handled by
+ an external program which is spawned and written to and read from
+ via pipes. Platforms that don't have pipes use temp files.
+
+2001-11-20 David Shaw <dshaw@jabberwocky.com>
+
+ * options.h, g10.c: New options show-notation, no-show-notation,
+ default-check-level, no-default-check-level, show-policy-url,
+ no-show-policy-url.
+
+ * packet.h, sign.c (make_keysig_packet), parse-packet.c
+ (parse_signature), free-packet.c (free_seckey_enc): Fill in
+ structures for notation, policy, sig class, exportability, etc.
+
+ * keyedit.c, keylist.c (print_and_check_one_sig,
+ list_keyblock_print): Show flags in signature display for cert
+ details (class, local, notation, policy, revocable). If selected,
+ show the notation and policy url.
+
+ * keyedit.c (sign_uids): Prompt for and use different key sig
+ classes.
+
+ * helptext.c (helptexts): Add help text to explain different
+ key signature classes
+
+2001-11-26 David Shaw <dshaw@jabberwocky.com>
+
+ * trustdb.c (mark_usable_uid_certs): Fix segfault from bad
+ initialization and fix reversed key signature expiration check.
+
+2001-11-09 Werner Koch <wk@gnupg.org>
+
+ * export.c (do_export_stream): Put all given names into a search
+ description and change the loop so that all matching names are
+ returned.
+
+2001-11-08 Werner Koch <wk@gnupg.org>
+
+ * pubkey-enc.c (get_it): To reduce the number of questions on the
+ MLs print the the name of cipher algorithm 1 with the error message.
+
+ * mainproc.c: Changed the way old rfc1991 encryption cipher is
+ selected. Based on a patch by W Lewis.
+
+ * pkclist.c (do_edit_ownertrust): Allow to skip over keys, the non
+ working "show info" is now assigned to "i"
+ * trustdb.c (ask_ownertrust, validate_keys): Implement a real quit
+ here. Both are by David Shaw.
+
+ * trustdb.c (validate_keys): Make sure next_exipire is initialized.
+
+ * sign.c (make_keysig_packet): Use SHA-1 with v4 RSA keys.
+
+ * g10.c, options.h : New option --[no-]froce-v4-certs.
+ * sign.c (make_keysig_packet): Create v4 sigs on v4 keys even with
+ a v3 key. Use that new option. By David Shaw
+
+ * revoke.c (ask_revocation_reason): Allow to select "no reason".
+ By David Shaw.
+
+ * keyid.c (fingerprint_from_sk): Calculation of an v3 fpr was
+ plain wrong - nearly the same code in fingerprint_from_pk is correct.
+
+ * build-packet.c (do_secret_key): Added a few comments to the code.
+
+2001-11-07 Werner Koch <wk@gnupg.org>
+
+ * g10.c (main): Print a warning when -r is used w/o encryption.
+ Suggested by Pascal Scheffers.
+
+2001-10-23 Werner Koch <wk@gnupg.org>
+
+ * keyedit.c (keyedit_menu): Changed helptext for showpref
+ command. Suggested by Reinhard Wobst.
+
+ * keyring.c (keyring_search): When marking the offtbl ready, take
+ into account that we may have more than one keyring.
+
+2001-10-22 Werner Koch <wk@gnupg.org>
+
+ * Makefile.am: Do not use OMIT_DEPENDENCIES
+
+ * build-packet.c (build_sig_subpkt): Default is now to put all
+ types of subpackets into the hashed area and only list those which
+ should go into the unhashed area.
+
+2001-10-18 Werner Koch <wk@gnupg.org>
+
+ * keydb.c (keydb_add_resource): Rearranged the way we keep track
+ of the resource. There will now be an entry for each keyring here
+ and not in keyring.c itself. Store a token to allow creation of a
+ keyring handle. Changed all functions to utilize this new design.
+ (keydb_locate_writable): Make a real implementation.
+ * keyring.c (next_kr): Removed and changed all callers to set the
+ resource directly from the one given with the handle.
+ (keyring_is_writable): New.
+ (keyring_rebuild_cache): Add an arg to pass the token from keydb.
+
+2001-10-17 Werner Koch <wk@gnupg.org>
+
+ * keyring.c (keyring_search): Enabled word search mode but print a
+ warning that it is buggy.
+
+2001-10-11 Werner Koch <wk@gnupg.org>
+
+ * hkp.c (hkp_ask_import): No more need to set the port number for
+ the x-hkp scheme.
+ (hkp_export): Ditto.
+
+2001-10-06 Stefan Bellon <sbellon@sbellon.de>
+
+ * passphrase.c [__riscos__]: Disabled agent specific stuff.
+ * g10.c: New option --no-force-v3-sigs.
+
+2001-10-04 Werner Koch <wk@gnupg.org>
+
+ * export.c (do_export_stream): Do not push the compress filter
+ here because the context would run out of scope due to the
+ iobuf_close done by the caller.
+ (do_export): Do it here instead.
+
+2001-09-28 Werner Koch <wk@gnupg.org>
+
+ * keyedit.c (sign_uids): Always use the primary key to sign keys.
+ * getkey.c (finish_lookup): Hack to return only the primary key if
+ a certification key has been requested.
+
+ * trustdb.c (cmp_kid_for_make_key_array): Renamed to
+ (validate_one_keyblock): this and changed arg for direct calling.
+ (make_key_array): Renamed to
+ (validate_one_keyblock): this and changed args for direct calling.
+ (mark_usable_uid_certs, validate_one_keyblock)
+ (validate_key_list): Add next_expire arg to keep track of
+ expiration times.
+ (validate_keys): Ditto for UTKs and write the stamp.
+
+ * tdbio.c (migrate_from_v2): Check return code of tbdio_sync.
+
+ * tdbdump.c (import_ownertrust): Do a tdbio_sync().
+
+ * keyring.c: Made the offtbl an global object.
+
+2001-09-27 Werner Koch <wk@gnupg.org>
+
+ * pkclist.c (do_edit_ownertrust): Allow settin of ultimate trust.
+
+ * trustdb.c (mark_keyblock_seen): New.
+ (make_key_array): Use it to mark the subkeys too.
+ (validate_keys): Store validity for ultimatly trusted keys.
+
+2001-09-26 Werner Koch <wk@gnupg.org>
+
+ * pkclist.c (check_signatures_trust, do_we_trust): Removed the
+ invocation of add_ownertrust. Minor changes to the wording.
+ (add_ownertrust, add_ownertrust_cb): Removed.
+
+ * trustdb.c (get_validity): Allow to lookup the validity using a
+ subkey.
+
+ * trustdb.c (new_key_hash_table): Increased the table size to 1024
+ and changed the masks accordingly.
+ (validate): Changed stats printing.
+ (mark_usable_uid_certs): New.
+ (cmp_kid_for_make_key_array): Does now check the signatures and
+ figures out a usable one.
+
+2001-09-25 Werner Koch <wk@gnupg.org>
+
+ * keyring.c (new_offset_item,release_offset_items)
+ (new_offset_hash_table, lookup_offset_hash_table)
+ (update_offset_hash_table, update_offset_hash_table_from_kb): New.
+ (keyring_search): Use a offset table to optimize search for
+ unknown keys.
+ (keyring_update_keyblock, keyring_insert_keyblock): Insert new
+ offsets.
+ * getkey.c (MAX_UNK_CACHE_ENTRIES): Removed the unknown keys
+ caching code.
+
+ * g10.c, options.h, import.c: Removed the entire
+ allow-secret-key-import stuff because the validity is now
+ controlled by other means.
+
+ * g10.c: New command --rebuild-keydb-caches.
+ * keydb.c (keydb_rebuild_caches): New.
+ * keyring.c (do_copy): Moved some code to
+ (create_tmp_file, rename_tmp_file, write_keyblock): new functions.
+ (keyring_rebuild_cache): New.
+
+ * packet.h (PKT_ring_trust): Add sigcache field.
+ * parse-packet.c (parse_trust): Parse sigcache.
+ * keyring.c (do_copy): Always insert a sigcache packet.
+ (keyring_get_keyblock): Copy the sigcache packet to the signature.
+ * sig-check.c (cache_sig_result): Renamed from
+ cache_selfsig_result. Changed implementation to use the flag bits
+ and changed all callers.
+ (mdc_kludge_check): Removed this unused code.
+ (do_check): Do not set the sig flags here.
+
+ * import.c (read_block): Make sure that ring_trust packets are
+ never imported.
+ * export.c (do_export_stream): and never export them.
+
+ * trustdb.c (make_key_array): Skip revoked and expired keys.
+
+2001-09-24 Werner Koch <wk@gnupg.org>
+
+ * g10.c, options.h: New option --no-auto-check-trustdb.
+
+ * keygen.c (do_generate_keypair): Set newly created keys to
+ ultimately trusted.
+
+ * tdbio.h, tdbio.c: Removed all support for records DIR, KEY, UID,
+ PREF, SIG, SDIR and CACH. Changed migration function to work
+ direct on the file.
+ (tdbio_read_nextcheck): New.
+ (tdbio_write_nextcheck): New.
+
+2001-09-21 Werner Koch <wk@gnupg.org>
+
+ Revamped the entire key validation system.
+ * trustdb.c: Complete rewrite. No more validation on demand,
+ removed some functions, adjusted to all callers to use the new
+ and much simpler interface. Does not use the LID anymore.
+ * tdbio.c, tdbio.h: Add new record types trust and valid. Wrote a
+ migration function to convert to the new trustdb layout.
+ * getkey.c (classify_user_id2): Do not allow the use of the "#"
+ prefix.
+ * keydb.h: Removed the TDBIDX mode add a skipfnc to the
+ descriptor.
+ * keyring.c (keyring_search): Implemented skipfnc.
+
+ * passphrase.c (agent_open): Add missing bracket. Include windows.h.
+
+2001-09-19 Werner Koch <wk@gnupg.org>
+
+ * keylist.c (print_fingerprint): Renamed from fingerprint, made
+ global available. Added new arg to control the print style.
+ * mainproc.c (print_fingerprint): Removed.
+ * pkclist.c (print_fpr, fpr_info): Removed and changed callers to
+ use print_fingerprint.
+ * keyedit.c (show_fingerprint): Ditto.
+
+ * passphrase.c (writen, readn)
+ (agent_open, agent_close)
+ (agent_get_passphrase)
+ (passphrase_clear_cache): Support for W32. Contributed by Timo.
+
+ * import.c (import_one): Release keydb handles at 2 more places.
+
+ * keyring.c (keyring_release): Close the iobuf.
+ (keyring_get_keyblock): Init ret_kb to NULL and store error contidion.
+
+ * import.c (import_new_stats_handle): New.
+ (import_release_stats_handle): New.
+ (import_print_stats): Renamed from static fnc print_stats.
+ (import_keys, import_keys_stream): Add an optional status handle
+ arg and changed all callers.
+ * hkp.c (hkp_ask_import): Add an stats_handle arg and changed all
+ callers.
+
+ * mainproc.c (print_pkenc_list): Use print_utf8_string2().
+
+2001-09-18 Werner Koch <wk@gnupg.org>
+
+ * g10.c: New command --refresh-keys.
+ * hkp.c (hkp_refresh_keys): New. Contributed by Timo Schulz.
+
+ * parse-packet.c (parse): Stop on impossible packet lengths.
+
+2001-09-17 Werner Koch <wk@gnupg.org>
+
+ * mainproc.c (print_notation_data): Wrap notation data status lines
+ after 50 chars.
+
+ * mainproc.c (proc_pubkey_enc): Make option try-all-secrets work.
+ By disastry@saiknes.lv.
+
+2001-09-14 Werner Koch <wk@gnupg.org>
+
+ * parse-packet.c (dump_sig_subpkt): List key server preferences
+ and show the revocable flag correctly. Contributed by David Shaw.
+
+2001-09-09 Werner Koch <wk@gnupg.org>
+
+ * keyedit.c (keyedit_menu): No need to define another p.
+
+ * keylist.c (print_capabilities): s/used/use/ so that it
+ does not shadow a global.
+ * sign.c (sign_file): Renamed arg encrypt to encryptflag
+ * keygen.c: Replaced all "usage" by "use".
+ * misc.c (openpgp_pk_algo_usage): Ditto.
+
+ * pubkey-enc.c (get_it): Renamed arg k to enc so that the later
+ defined k does not shadow it.
+
+ * parse-packet.c (parse_gpg_control): No need to define another i.
+
+ * getkey.c (get_pubkey_byfprint): Must use the enum values and not
+ the fprint_len.
+ * keyring.c (keyring_search): Removed a non-sense break. Both
+ bugs pointed out by Stefan.
+
+2001-09-07 Werner Koch <wk@gnupg.org>
+
+ * status.c, status.h: Added NO_RECP and ALREADY_SIGNED.
+ * pkclist.c (build_pk_list): Issue NO_RECP.
+ * keyedit.c (sign_uids): Added experimental ALREADY_SIGNED
+
+ * hkp.c (hkp_import): Use log_error. Bug reported by Neal H
+ Walfield.
+
+ * getkey.c (classify_user_id2): Change args to take the desc union
+ direct. It was a stupid idea to pass the individual fields of an
+ union to this function. Changed all callers.
+ (classify_user_id): Ditto and allow to pass NULL as the description.
+
+2001-09-06 Werner Koch <wk@gnupg.org>
+
+ * getkey.c (fixup_uidnode): Features flag is now a bit vector.
+ * keygen.c (add_feature_mdc): Ditto.
+
+ Revamped the entire key I/O code to be prepared for other ways of
+ key storages and to get rid of the existing shit. GDBM support has
+ gone.
+ * keydb.c: New
+ * keyring.c, keyring.h: New.
+ * ringedit.c: Removed. Moved some stuff to keyring.c
+ * getkey.c: Changed everything related to the key retrieving
+ functions which are now using the keydb_ functions.
+ (prepare_search, word_match_chars, word_match)
+ (prepare_word_match, compare_name): Moved to keyring.c
+ (get_pubkey_byname): Removed ctx arg and add ret_kdbhd
+ arg. Changed all callers.
+ (key_byname): Use get_pubkey_end to release the context and take
+ new ret_kbdhd arg. Changed all callers.
+ (classify_user_id2): Fill the 16 byte fingerprint up with 4 null
+ bytes not with zero bytes of value 4, tsss.
+ * import.c (import_one): Updated to use the new keydb interface.
+ (import_secret_one): Ditto.
+ (import_revoke_cert): Ditto.
+ * delkey.c (do_delete_key): Ditto.
+ * keyedit.c (keyedit_menu): Ditto.
+ (get_keyblock_byname): Removed.
+ * revoke.c (gen_revoke): Ditto.
+ * export.c (do_export_stream): Ditto.
+ * trustdb.c (update_trustdb): Ditto.
+ * g10.c, gpgv.c (main): Renamed add_keyblock_resource to
+ keydb_add_resource.
+ * Makefile.am: Added and removed files.
+
+ * keydb.h: Moved KBNODE typedef and MAX_FINGERPRINT_LEN to
+ * global.h: this new header.
+
+2001-09-03 Werner Koch <wk@gnupg.org>
+
+ * passphrase.c (agent_get_passphrase): Changed nread to size_t.
+ (passphrase_clear_cache): Ditto.
+
+ * keyid.c (mk_datestr): Avoid trigraphs.
+ (fingerprint_from_pk): Cache the keyid in the pk.
+
+ * options.h: Add opt.with_fingerprint so that we know whether the
+ corresponding options was used.
+ * g10.c (main): Set it here.
+ * pkclist.c (check_signatures_trust): Always print fingerprint
+ when this option is used. Mixed a minor memory leak.
+
+ * status.c, status.h: New status INV_RECP.
+ * pkclist.c (build_pk_list): Issue this status.
+
+2001-08-31 Werner Koch <wk@gnupg.org>
+
+ * parse-packet.c (parse_key,parse_pubkeyenc)
+ (parse_signature): Return error on reading bad MPIs.
+
+ * mainproc.c (check_sig_and_print): Always print the user ID even
+ if it is not bound by a signature. Use the primary UID in the
+ status messages and encode them in UTF-8
+ * status.c (write_status_text_and_buffer): New.
+
+2001-08-30 Werner Koch <wk@gnupg.org>
+
+ * packet.h (sigsubpkttype_t): Add SIGSUBPKT_FEATURES.
+ (PKT_public_key, PKT_user_id): Add a flag for it.
+ * parse-packet.c, build-packet.c: Add support for them.
+ * getkey.c (fixup_uidnode, merge_selfsigs): Set the MDC flags.
+ * keygen.c (add_feature_mdc): New.
+ (keygen_upd_std_prefs): Always set the MDC feature.
+ * keyedit.c (show_prefs): List the MDC flag
+ * pkclist.c (select_mdc_from_pklist): New.
+ * encode.c (encode_crypt, encrypt_filter): Test whether MDC
+ should be used.
+ * cipher.c (write_header): Set MDC use depending on the above test.
+ Print more status info.
+
+ * delkey.c (do_delete_key): Kludge to delete a secret key with no
+ public key available.
+
+ * ringedit.c (find_secret_keyblock_direct): New.
+ * getkey.c (seckey_available): Simplified.
+
+ * ringedit.c (cmp_seckey): Now compares the secret key against the
+ public key while ignoring all secret parts.
+ (keyring_search): Use a public key packet as arg. Allow to search
+ for subnkeys
+ (search): Likewise. Changed all callers.
+ (find_secret_keyblock_bypk): New.
+ (find_secret_keyblock_byname): First locate the pubkey and then
+ find the correponding secret key.
+ * parse-packet.c (parse): Renamed pkttype arg to onlykeypkts and
+ changed code accordingly. Changed all callers.
+ (search_packet): Removed pkttype arg.
+ * keyedit.c (keyedit_menu): First locate the public key and then
+ try to locate a secret key.
+
+ * ringedit.c (locate_keyblock_by_fpr): Removed.
+ (locate_keyblock_by_keyid): Removed.
+ (find_keyblock_bysk): Removed.
+
+ * sig-check.c (check_key_signature2): Print the keyid along with
+ the wrong sig class errors.
+
+2001-08-24 Werner Koch <wk@gnupg.org>
+
+ * sign.c (sign_file): Stripped the disabled comment packet code.
+ (sign_file, sign_symencrypt_file): Moved common code to ..
+ (write_onepass_sig_packets): .. this new function.
+ (sign_file, clearsign_file, sign_symencrypt_file): Moved common
+ code to
+ (write_signature_packets): this new function.
+ (write_signature_packets, make_keysig_packet)
+ (update_keysig_packet): Moved common code to
+ (hash_uid, hash_sigclass_to_magic): these new functions
+ (sign_file, sign_symencrypt_file): Moved common code to
+ (write_plaintext_packet): this new function.
+
+2001-08-21 Stefan Bellon <sbellon@sbellon.de>
+
+ * trustdb.c (query_trust_info): Changed trustlevel to signed int.
+ * g10.c [__riscos__]: Fixed handling of --use-agent --lock-multiple.
+
+2001-08-20 Werner Koch <wk@gnupg.org>
+
+ * encr-data.c (decrypt_data): Keep track on whether we already
+ printed information about the used algorithm.
+ * mainproc.c (proc_encrypted): Removed the non-working IDEA hack
+ and print a message about the assumed algorithm.
+ * passphrase.c (passphrase_to_dek): Use the same algorithm as above.
+ (proc_symkey_enc): Print the algorithm, so that the user knows it
+ before entering the passphrase.
+ (proc_pubkey_enc, proc_pubkey_enc): Zero the DEK out.
+ * encode.c (encode_crypt, encrypt_filter): Ditto.
+
+ * g10.c: Allow for --sign --symmetric.
+ * sign.c (sign_and_symencrypt): New.
+
+ Applied patches from Stefan Bellon <sbellon@sbellon.de> to support
+ RISC OS. Nearly all of these patches are identified by the
+ __riscos__ macro.
+ * compress.c: Added a couple of casts.
+ * g10.c [__riscos__]: Some patches and new options foo-file similar
+ to all foo-fd options.
+ * gpgv.c, openfile.c, ringedit.c, tdbio.c: Minor fixes. Mainly
+ replaced hardcoded path separators with EXTSEP_S like macros.
+ * 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 <wk@gnupg.org>
+
+ * keygen.c (ask_algo): New arg r_usage. Allow for RSA keys.
+ (gen_rsa): Enabled the code.
+ (do_create): Enabled RSA branch.
+ (parse_parameter_usage): New.
+ (proc_parameter_file): Handle usage parameter.
+ (read_parameter_file): Ditto.
+ (generate_keypair): Ditto.
+ (generate_subkeypair): Ditto.
+ (do_generate_keypair): Ditto.
+ (do_add_key_flags): New.
+ (keygen_add_std_prefs): Use the new function.
+ (keygen_add_key_flags_and_expire): New.
+ (write_selfsig, write_keybinding): Handle new usage arg.
+ * build-packet.c (build_sig_subpkt): Make sure that key flags go
+ into the hashed area.
+
+ * keygen.c (write_uid): Initialize the reference cunter.
+
+ * keyedit.c (keyedit_menu): No more need to update the trustdb for
+ preferences. Added calls to merge keblock.
+
+ * kbnode.c (dump_kbnode): Print some more flags.
+
+2001-08-10 Werner Koch <wk@gnupg.org>
+
+ Revamped the preference handling.
+
+ * packet.h (prefitem_t, preftype_t): New.
+ (PKT_public_key): Added a uid field.
+ (PKT_user_id): Added field to store preferences and a reference
+ counter.
+ * parse-packet.c (parse_user_id,parse_photo_id): Initialize them
+ * free-packet.c (free_user_id): Free them.
+ (copy_user_id): Removed.
+ (scopy_user_id): New.
+ (cmp_user_ids): Optimized for identical pointers.
+ (release_public_key_parts): Release the uid.
+ (copy_public_key_with_new_namehash): Removed.
+ (copy_prefs): New.
+ * keyedit.c (menu_adduid): Use the new shallow copy user id.
+ (show_prefs): Adjusted implementation.
+ (keyedit_menu): No more need to update the trustdb after changing
+ preferences.
+ * getkey.c (fixup_uidnode): Store preferences.
+ (find_by_name): Return a user id packet and remove namehash stuff.
+ (lookup): Removed the unused namehash stuff.
+ (finish_lookup): Added foundu arg.
+ (pk_from_block): Removed the namehash arg and changed all callers.
+ (merge_selfsigs): Copy prefs to all keys.
+ * trustdb.c (get_pref_data): Removed.
+ (is_algo_in_prefs): Removed.
+ (make_pref_record): Deleted and removed all class.
+ * pkclist.c (select_algo_from_prefs): Adjusted for the new
+ preference implementation.
+ * pubkey-enc.c (is_algo_in_prefs): New.
+ (get_it): Use that new function.
+
+2001-08-09 Werner Koch <wk@gnupg.org>
+
+ * build-packet.c (build_sig_subpkt): Fixed calculation of
+ newarea->size.
+
+ * g10.c (main): New option "--preference-list"
+ * keyedit.c (keyedit_menu): New commands "setpref" and "updpref".
+ (menu_set_preferences): New.
+ * keygen.c (keygen_set_std_prefs): New.
+ (set_one_pref): New.
+ (check_zip_algo): New.
+ (keygen_get_std_prefs): New.
+ (keygen_upd_std_prefs): New
+ (keygen_add_std_prefs): Move the pref setting code into the above fnc.
+ * build-packet.c (build_sig_subpkt): Updated the list of allowed
+ to update subpackets.
+
+2001-08-08 Werner Koch <wk@gnupg.org>
+
+ * packet.h (subpktarea_t): New.
+ (PKT_signature): Use that type for hashed_data and unhashed_data and
+ removed the _data prefix from those fields. Changed all users.
+ * parse-packet.c (parse_signature): Changed allocation for that.
+ (parse_sig_subpkt): Changed declaration
+ (enum_sig_subpkt): Ditto and changed implementation accordingly.
+ * free-packet.c (cp_subpktarea): Renamed from cp_data_block and
+ adjusted implementation. Changed caller.
+ * sig-check.c (mdc_kludge_check): Adjusted the hashing.
+ (do_check): Ditto.
+ * sign.c (sign_file, clearsign_file, make_keysig_packet,
+ update_keysig_packet): Ditto.
+ * build-packet.c (build_sig_subpkt): Partial rewrite.
+ (find_subpkt): Adjusted and made static.
+ (delete_sig_subpkt): Adjusted.
+ (do_signature): Ditto.
+
+ * keygen.c (ask_keysize): Do not print the notes about suggested
+ key sizes if just a DSA key is generated.
+
+ * trustdb.c (add_ultimate_key): s/log_error/log_info/ for
+ duplicated inserted trusted keys.
+
+2001-08-07 Werner Koch <wk@gnupg.org>
+
+ * sign.c (sleep): Redefine for W32.
+
+ * g10.c, options.h: Set new flag opt.no_homedir_creation when
+ --no-options is given.
+ * openfile.c (try_make_homedir): Don't create the homedir in that case.
+
+2001-08-03 Werner Koch <wk@gnupg.org>
+
+ * armor.c (armor_filter): Removed the default comment string
+ because it could get us in trouble due to translations using non
+ ascii characters.
+
+2001-08-01 Werner Koch <wk@gnupg.org>
+
+ * keylist.c (list_keyblock_print): Do not list revoked UIDs unless
+ in verbose mode and we do no signature listing.
+
+ * getkey.c (finish_lookup): Skip subkeys which are not yet valid.
+ * g10.c, options.h: New option --ignore-valid-from.
+
+ * sign.c (make_keysig_packet): Added new sigversion argument to
+ allow the caller to force generation of required signature
+ version. Changed all callers. Suggested by Thomas Roessler.
+
+ * keyedit.c (sign_uids): Force v4 signature generation for local
+ sigs. Removed the check for local signature and pre-v4 keys.
+
+2001-07-27 Werner Koch <wk@gnupg.org>
+
+ * keyedit.c (sign_uids): Check that we are not trying to to a
+ lsign with a pre-v4 key. Bug noticed by Thomas Roessler.
+
+2001-07-26 Werner Koch <wk@gnupg.org>
+
+ * parse-packet.c (parse_photo_id): Reset all variables.
+ * getkey.c (merge_selfsigs_main): Removed checks on PHOTO_ID
+ because this is handled identically to a user ID.
+
+2001-07-06 Werner Koch <wk@gnupg.org>
+
+ * cipher.c (write_header): Don't use MDC with --rfc1991. Suggested
+ by disastry@saiknes.lv.
+
+2001-07-05 Werner Koch <wk@gnupg.org>
+
+ * g10.c, options.h: New option --preserve-permissions.
+ * ringedit.c (add_keyblock_resource): Use it here
+ (keyring_copy): and here.
+
+ * trustdb.c (verify_own_keys): Be more silent on --quiet.
+ Suggested by Thomas Roessler.
+ * sig-check.c (check_key_signature2): Ditto.
+ * mainproc.c (proc_encrypted, proc_tree): Ditto
+ * getkey.c (lookup): Ditto.
+
+2001-07-04 Werner Koch <wk@gnupg.org>
+
+ * ringedit.c (add_keyblock_resource): Restore filename in case of error.
+
+2001-06-25 Werner Koch <wk@gnupg.org>
+
+ * kbnode.c (dump_kbnode): Print the signature timestamp.
+
+ * keyedit.c (keyedit_menu): New menu point "primary".
+ (change_primary_uid_cb): New.
+ (menu_set_primary_uid): New.
+ * sign.c (update_keysig_packet): New.
+ * build-packet.c (build_sig_subpkt): Put the primary UID flag into
+ the hashed area. Allow update of some more packets.
+
+2001-06-15 Werner Koch <wk@gnupg.org>
+
+ * getkey.c (merge_selfsigs): Exit gracefully when a secret key is
+ encountered. May happen if a secret key is in public keyring.
+ Reported by Francesco Potorti.
+
+2001-06-12 Werner Koch <wk@gnupg.org>
+
+ * getkey.c (compare_name): Use ascii_memistr(), ascii_memcasecmp()
+ * keyedit.c (keyedit_menu): Use ascii_strcasecmp().
+ * armor.c (radix64_read): Use ascii_toupper().
+ * ringedit.c (do_bm_search): Ditto.
+ * keygen.c (read_parameter_file): Ditto.
+ * openfile.c (CMP_FILENAME): Ditto.
+ * g10.c (i18n_init): We can now use just LC_ALL.
+
+2001-05-29 Werner Koch <wk@gnupg.org>
+
+ * keygen.c (generate_subkeypair): Print a warning if a subkey is
+ created on a v3 key. Suggested by Brian M. Carlson.
+
+2001-05-27 Werner Koch <wk@gnupg.org>
+
+ * keyid.c (get_lsign_letter): New.
+ * keylist.c (list_keyblock_colon): Use it here.
+ * mainproc.c (list_node): and here.
+
+ * getkey.c, packet.h, free-packet.c: Removed that useless key
+ created field; I dunno why I introducded this at all - the
+ creation time is always bound to the key packet and subject to
+ fingerprint calculation etc.
+
+ * getkey.c (fixup_uidnode): Add keycreated arg and use this
+ instead of the signature timestamp to calculate the
+ help_key_expire. Bug reported by David R. Bergstein.
+ (merge_selfsigs_main): Correct key expiration time calculation.
+ (merge_selfsigs_subkey): Ditto.
+
+2001-05-25 Werner Koch <wk@gnupg.org>
+
+ * revoke.c (gen_revoke): Add a cast to a tty_printf arg.
+ * delkey.c (do_delete_key): Ditto.
+ * keyedit.c (print_and_check_one_sig): Ditto.
+ (ask_revoke_sig): Ditto.
+ (menu_revsig): Ditto.
+ (check_all_keysigs): Removed unused arg.
+
+2001-05-23 Werner Koch <wk@gnupg.org>
+
+ * g10.c (opts): Typo fix by Robert C. Ames.
+
+2001-05-06 Werner Koch <wk@gnupg.org>
+
+ * revoke.c: Small typo fix
+
+2001-05-04 Werner Koch <wk@gnupg.org>
+
+ * passphrase.c (passphrase_clear_cache): Shortcut if agent usage
+ is not enabled.
+
+2001-05-01 Werner Koch <wk@gnupg.org>
+
+ * passphrase.c (writen): Replaced ssize_t by int. Thanks to
+ to Robert Joop for reporting that SunOS 4.1.4 does not have it.
+
+2001-04-28 Werner Koch <wk@gnupg.org>
+
+ * getkey.c (merge_public_with_secret): pkttype was not set to subkey.
+
+2001-04-27 Werner Koch <wk@gnupg.org>
+
+ * skclist.c (build_sk_list): Changed one log_debug to log_info.
+
+2001-04-25 Werner Koch <wk@gnupg.org>
+
+ * keyedit.c (show_prefs): Add a verbose mode.
+ (show_key_with_all_names): Pass verbose flag for special value of
+ with_pref.
+ (keyedit_menu): New command "showpref"
+ (show_key_with_all_names): Mark revoked uids and the primary key.
+
+2001-04-24 Werner Koch <wk@gnupg.org>
+
+ * getkey.c (get_primary_uid): Return a different string in case of
+ error and made it translatable.
+
+ * build-packet.c (do_secret_key): Ugly, we wrote a zero
+ instead of the computed ndays. Thanks to M Taylor for complaining
+ about a secret key import problem.
+
+2001-04-23 Werner Koch <wk@gnupg.org>
+
+ * hkp.c (hkp_ask_import): Allow to specify a port number for the
+ keyserver. Add a kudge to set the no_shutdown flag.
+ (hkp_export): Ditto.
+ * options.skel: Document the changes
+
+2001-04-20 Werner Koch <wk@gnupg.org>
+
+ * options.skel: Add some more comments.
+
+2001-04-19 Werner Koch <wk@gnupg.org>
+
+ * keyid.c (mk_datestr): New. Handles negative times. We must do
+ this because Windoze segvs on negative times passed to gmtime().
+ Changed all datestr_from function to use this one.
+
+ * keyid.c, keyid.h (colon_strtime): New. To implement the
+ fixed-list-mode.
+ (colon_datestr_from_pk): New.
+ (colon_datestr_from_sk): New.
+ (colon_datestr_from_sig): New.
+ * keylist.c (list_keyblock_colon): Use these functions here.
+ * mainproc.c (list_node): Ditto.
+
+2001-04-18 Werner Koch <wk@gnupg.org>
+
+ * openfile.c (open_sigfile): Fixed the handling of ".sign".
+ * mainproc.c (proc_tree): Use iobuf_get_real_fname.
+ Both are by Vincent Broman.
+
+2001-04-14 Werner Koch <wk@gnupg.org>
+
+ * getkey.c (fixup_uidnode): Removed check for !sig which is
+ pointless here. Thanks to Jan Niehusmann.
+
+2001-04-10 Werner Koch <wk@gnupg.org>
+
+ * sig-check.c (check_key_signature2): Use log_info instead of
+ log_error so that messed up keys do not let gpg return an error.
+ Suggested by Christian Kurz.
+
+ * getkey.c (merge_selfsigs_main): Do a fixup_uidnode only if we
+ have both, uid and sig. Thanks to M Taylor.
+
+2001-04-05 Werner Koch <wk@gnupg.org>
+
+ * armor.c (unarmor_pump_new,unarmor_pump_release): New.
+ (unarmor_pump): New.
+ * pipemode.c (pipemode_filter): Use the unarmor_pump to handle
+ armored or non-armored detached signatures. We can't use the
+ regular armor_filter 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 <wk@gnupg.org>
+
+ The length of encrypted packets for blocksizes != 8 was not
+ correct encoded. I think this is a minor problem, because we
+ usually use partial length packets. Kudos to Kahil D. Jallad for
+ pointing this out.
+ * packet.h: Add extralen to PKT_encrypted.
+ * cipher.c (write_header): Set extralen.
+ * build-packet.c (do_encrypted): Use extralen instead of const 10.
+ (do_encrypted_mdc): Ditto.
+ * parse-packet.c (parse_encrypted): Set extralen to 0 because we
+ don't know it here.
+
+2001-03-30 Werner Koch <wk@gnupg.org>
+
+ * getkey.c (premerge_public_with_secret): Changed wording an add
+ the keyID to the info message.
+
+2001-03-29 Werner Koch <wk@gnupg.org>
+
+ * getkey.c (premerge_public_with_secret): Use log_info instead of
+ log_error when no secret key was found for a public one.
+ Fix the usage if the secret parts of a key are not available.
+
+ * openfile.c (ask_outfile_name): Trim spaces.
+ (open_outfile): Allow to enter an alternate filename. Thanks to
+ Stefan Bellon.
+ * plaintext.c (handle_plaintext): Ditto.
+
+2001-03-28 Werner Koch <wk@gnupg.org>
+
+ * mainproc.c (do_check_sig): Allow direct key and subkey
+ revocation signature.
+ * sig-check.c (check_key_signature2): Check direct key signatures.
+ Print the signature class along with an error.
+
+2001-03-27 Werner Koch <wk@gnupg.org>
+
+ * packet.h: Add a missing typedef to an enum. Thanks to Stefan Bellon.
+
+ * g10.c: New option --no-sig-create-check.
+ * sign.c (do_sign): Implement it here.
+ * g10.c: New option --no-sig-cache.
+ * sig-check.c (check_key_signature2): Implement it here.
+ (cache_selfsig_result): and here.
+
+ * keylist.c (list_keyblock): Removed debugging stuff.
+
+ * getkey.c (cache_public_key): Made global.
+ * keygen.c (write_selfsig, write_keybinding): Cache the new key.
+
+ * getkey.c (key_byname): Add new arg secmode and changed all
+ callers to request explicitly the mode. Deriving this information
+ from the other supplied parameters does not work if neither pk nor
+ sk are supplied.
+
+2001-03-25 Werner Koch <wk@gnupg.org>
+
+ * packet.h (ctrlpkttype_t): New.
+ * mainproc.c (add_gpg_control,proc_plaintext,proc_tree): Use the
+ new enum values.
+ * pipemode.c (make_control): Ditto.
+ * armor.c (armor_filter): Ditto.
+
+2001-03-24 Werner Koch <wk@gnupg.org>
+
+ * sign.c (do_sign): Verify the signature right after creation.
+
+2001-03-23 Werner Koch <wk@gnupg.org>
+
+ * status.c, status.h (STATUS_UNEXPECTED): New.
+ * mainproc.c (do_proc_packets): And emit it here.
+
+2001-03-21 Werner Koch <wk@gnupg.org>
+
+ * status.c: Add sys/types.h so that it runs on Ultrix. Reported
+ by Georg Schwarz.x
+
+ * build-packet.c (build_sig_subpkt): Fixed generaton of packet
+ length header in case where 2 bytes headers are needed. Thanks to
+ Piotr Krukowiecki.
+
+2001-03-19 Werner Koch <wk@gnupg.org>
+
+ * g10.c (main): the default keyring is no always used unless
+ --no-default-keyring is given.
+
+ * ringedit.c (add_keyblock_resource): invalidate cache after file
+ creation.
+
+2001-03-15 Werner Koch <wk@gnupg.org>
+
+ * keygen.c (ask_algo): Changed the warning of the ElGamal S+E Algo.
+
+ * keylist.c (print_capabilities): New.
+ (list_keyblock_colon): and use it here.
+
+2001-03-13 Werner Koch <wk@gnupg.org>
+
+ * main.c, options.h: New option --fixed_list_mode.
+ * keylist.c (list_keyblock_colon): use it here.
+
+ * getkey.c (merge_keys_and_selfsig): Divert merging of public keys
+ to the function used in key selection..
+ * keylist.c (is_uid_valid): Removed.
+ (list_keyblock): Splitted into ..
+ (list_keyblock_print, list_keyblock_colon): .. these.
+ functions. Changed them to use the flags set in the key lookup code.
+ (reorder_keyblock): New, so that primary user IDs are listed first.
+
+ * ringedit.c (keyring_copy): flush the new iobuf chaces before
+ rename or remove operations. This is mainly needed for W32.
+
+ * hkp.c [HAVE_DOSISH_SYSTEM]: Removed the disabled code because we
+ have now W32 socket support in ../util/http.c
+
+ * skclist.c (key_present_in_sk_list): New.
+ (is_duplicated_entry): New.
+ (build_sk_list): Check for duplicates and do that before unlocking.
+
+2001-03-12 Werner Koch <wk@gnupg.org>
+
+ * armor.c (parse_header_line): Removed double empty line check.
+ (parse_header_line): Replaced trim_trailing_ws with a counting
+ function so that we can adjust for the next read.
+
+ * options.skel: Fixed 3 typos. By Thomas Klausner. Replaced the
+ keyserver example by a better working server.
+
+ * parse-packet.c (parse_symkeyenc): Return Invalid_Packet on error.
+ (parse_pubkeyenc): Ditto.
+ (parse_onepass_sig): Ditto.
+ (parse_plaintext): Ditto.
+ (parse_encrypted): Ditto.
+ (parse_signature): Return error at other places too.
+ (parse_key): Ditto.
+ * g10.c (main): Set opt.list_packets to another value when invoked
+ with the --list-packets command.
+ * mainproc.c (do_proc_packets): Don's stop processing when running
+ under --list-packets command.
+
+ * signal.c (do_sigaction): Removed.
+ (init_one_signal): New to replace the above. Needed to support
+ systems without sigactions. Suggested by Dave Dykstra.
+ (got_fatal_signal,init_signals): Use the above here.
+ (do_block): Use sigset() if sigprocmask() is not available.
+
+ * armor.c (parse_hash_header): Test on TIGER192, which is the
+ correct value as per rfc2440. By Edwin Woudt.
+
+2001-03-08 Werner Koch <wk@gnupg.org>
+
+ * misc.c: Include time.h. By James Troup.
+
+ * getkey.c: Re-enabled the unknown user Id and PK caches and
+ increased their sizes.
+
+ * getkey.c (merge_selfsigs_main): Set expire date and continue
+ processing even if we found a revoked key.
+ (merge_selfsigs_subkeys): Ditto.
+
+ * packet.h: Add an is_revoked flag to the user_id packet.
+ * getkey.c (fixup_uidnode): Set that flag here.
+ (merge_selfsigs_main): Fix so that the latest signature is used to
+ find the self-signature for an UID.
+ * parse-packet.c (parse_user_id): Zero out all fields.
+ * mainproc.c (check_sig_and_print): Print the primary user ID
+ according the the node flag and then all other non-revoked user IDs.
+ (is_uid_revoked): Removed; it is now handled by the key selection code.
+
+ Changed the year list of all copyright notices.
+
+2001-03-07 Werner Koch <wk@gnupg.org>
+
+ * getkey.c (finish_lookup): Print an info message only in verbose mode.
+
+2001-03-05 Werner Koch <wk@gnupg.org>
+
+ * packet.h: Replaced sigsubpkt_t value 101 by PRIV_VERIFY_CACHE.
+ We have never used the old value, so we can do this without any harm.
+ * parse-packet.c (dump_sig_subpkt): Ditto.
+ (parse_one_sig_subpkt): Parse that new sub packet.
+ * build-packet.c (build_sig_subpkt): Removed the old one from the
+ hashed area.
+ (delete_sig_subpkt): New.
+ (build_sig_subpkt): Allow an update of that new subpkt.
+ * sig-check.c (check_key_signature2): Add verification caching
+ (cache_selfsig_result): New.
+ * export.c (do_export_stream): Delete that sig subpkt before exporting.
+ * import.c (remove_bad_stuff): New.
+ (import): Apply that function to all imported data
+
+2001-03-03 Werner Koch <wk@gnupg.org>
+
+ * getkey.c: Introduced a new lookup context flag "exact" and used
+ it in all place where we once used primary.
+ (classify_user_id2): Replaced the old function and add an extra
+ argument to return whether an exact keyID has been requested.
+ (key_byname): Removed the unused ctx.primary flag
+ (get_seckey_byname2): Ditto.
+ (finish_lookup): Changed debugging output.
+
+2001-03-02 Werner Koch <wk@gnupg.org>
+
+ * keylist.c (list_one): Remove the merge key calls.
+
+2001-03-01 Werner Koch <wk@gnupg.org>
+
+ * getkey.c (finish_lookup): Don't use it if we no specific usage
+ has been requested.
+ (merge_selfsigs_main): fix UID only if we have an signature.
+ (lookup): Return UNU_PUBKEY etc. instead of NO_PUBKEY if we found
+ a key but the requested usage does not allow this key.
+ * import.c (import_one): Take UNU_PUBKEY into account.
+ * mainproc.c (list_node): Ditto.
+ * keylist.c (list_keyblock): Ditto.
+ * keyedit.c (print_and_check_one_sig): Ditto.
+
+2001-02-09 Werner Koch <wk@gnupg.org>
+
+ * delkey.c (delete_key): Removed that silly assert which rendered
+ the whole new stuff meaningless.
+
+2001-02-08 Werner Koch <wk@gnupg.org>
+
+ * getkey.c (key_byname): It can happen that we have both, sk and pk
+ NULL, fix for that.
+
+ * parse-packet.c (parse_one_sig_subpkt): Add support for
+ primary_uid and key_flags.
+ (can_handle_critical): Ditto
+
+ * parse-packet.c (parse_encrypted): Fixed listing of pktlen for
+ MDC packets.
+
+ * getkey.c: Backported the version of this file from gpg 1.1. this
+ involved some changes in other files too.
+ * parse-packet.c (parse_key): Clear req_usage.
+ * skclist.c (build_sk_list): Use req_usage to pass the usage
+ information to the lookup function.
+ * pkclist.c (build_pk_list): Ditto.
+ * free-packet.c (copy_public_parts_to_secret_key): New.
+ * keydb.h: Add IS_* macros to check the sig_class.
+ * misc.c (openpgp_cipher_test_algo): New.
+ (openpgp_pk_test_algo): New.
+ (openpgp_pk_algo_usage): New.
+ (openpgp_md_test_algo): New.
+ * packet.h: Add a few fields to PKT_{public,secret}_key and
+ PKT_user_id.
+ * seckey-cert.c (do_check): Use the new main_keyid field.
+
+2001-02-04 Werner Koch <wk@gnupg.org>
+
+ * encr-data.c (decrypt_data): Catch error when we had problems to
+ parse the encrypted packet. By Timo.
+
+2001-01-29 Werner Koch <wk@gnupg.org>
+
+ * g10.c (main): --batch does now set nogreeting.
+
+ * delkey.c (do_delete_key): Fixed delete-both functionality.
+
+2001-01-22 Werner Koch <wk@gnupg.org>
+
+ * g10.c: New command --delete-secret-and-public-key.
+ * delkey.c (delete_key): Add new arg allow_both.
+ (do_delete_key): Move most stuff from above to this new function.
+
+2001-01-12 Werner Koch <wk@gnupg.org>
+
+ * passphrase.c (passphrase_to_dek): Use MD5 when IDEA is installed
+ and we have no S2K.
+ * mainproc.c (proc_encrypted): Likewise
+
+2001-01-11 Werner Koch <wk@gnupg.org>
+
+ * sig-check.c (do_check): Print the signature key expire message
+ only in verbose mode and added the keyID.
+
+2001-01-09 Werner Koch <wk@gnupg.org>
+
+ * status.c, status.h: New status USERID_HINT.
+ (write_status_text): Replace LF and CR int text by C-escape sequence.
+
+ * passphrase.c (passphrase_to_dek): Fixed the NEED_PASSPHRASE
+ output. It does now always print 2 keyIDs. Emit the new
+ USERID_HINT.
+
+2001-01-08 Werner Koch <wk@gnupg.org>
+
+ * g10.c, options.h: New option --no-expensive-trust-checks.
+ * keylist.c (list_keyblock): Act on this option.
+
+2001-01-04 Werner Koch <wk@gnupg.org>
+
+ * g10.c (main): Set homedir only in the pre-parsing phase and
+ replace backslashes in the W32 version.
+
+2001-01-03 Werner Koch <wk@gnupg.org>
+
+ * status.c, status.h : New status KEY_CREATED
+ * keygen.c (do_generate_keypair,generate_subkeypair): Emit it.
+
+2000-12-28 Werner Koch <wk@gnupg.org>
+
+ * signal.c (got_fatal_signal): Remove lockfiles here because the
+ atexit stuff does not work due to the use of raise. Suggested by
+ Peter Fales.
+ * gpgv.c (remove_lockfiles): New stub.
+
+2000-12-19 Werner Koch <wk@gnupg.org>
+
+ * status.c, status.h (cpr_get_no_help): New.
+ * keyedit.c (keyedit_menu): Use it here because we have our own
+ help list here.
+
+2000-12-18 Werner Koch <wk@gnupg.org>
+
+ * mainproc.c (print_failed_pkenc): Don't print the sometimes
+ confusing message about unavailabe secret key. Renamed ...
+ (print_pkenc_list): ... to this and introduced failed arg.
+ (proc_encrypted): Print the failed encryption keys and then
+ the one to be used.
+ (proc_pubkey_enc): Store also the key we are going to use.
+
+ * mainproc.c (check_sig_and_print): Don't list revoked user IDs.
+ (is_uid_revoked): New.
+
+2000-12-08 Werner Koch <wk@gnupg.org>
+
+ * pipemode.c: Made the command work. Currently only for
+ non-armored detached signatures.
+ * mainproc.c (release_list): Reset the new pipemode vars.
+ (add_gpg_control): Handle the control packets for pipemode
+ * status.c, status.h: New stati {BEGIN,END}_STREAM.
+
+2000-12-07 Werner Koch <wk@gnupg.org>
+
+ * g10.c: New option --allow-secret-key-import.
+ * import.c (import_keys,import_keys_stream): Honor this option.
+ (import): New arg allow_secret and pass that arg down to ...
+ (import_secret_one): to this and print a warning if secret key
+ importing is not allowed.
+
+2000-12-05 Werner Koch <wk@gnupg.org>
+
+ * cipher.c (cipher_filter): Moved the end_encryption status ...
+ * encode.c (encode_simple,encode_crypt): to here
+ * sign.c (sign_file): and here.
+
+ * status.c (mywrite): Removed.
+ (get_status_string): Removed the LFs from the strings.
+ (set_status_fd,is_status_enabed,write_status_text,
+ write_status_buffer): Replaced all mywrite by stdio calls and use
+ fdopen to create a strem. This is needed to make things smoother
+ in the W32 version.
+
+2000-12-04 Werner Koch <wk@gnupg.org>
+
+ * import.c (merge_blocks): Increment n_sigs for revocations.
+
+2000-11-30 Werner Koch <wk@gnupg.org>
+
+ * g10.c (main): Use iobuf_translate_file_handle for all options
+ with filehandles as arguments. This is function does some magic
+ for the W32 API.
+
+ * verify.c (verify_signatures): Add a comment rant about the
+ detached signature problem.
+ * mainproc.c (proc_tree): Issue an error if a detached signature
+ is assumed but a standard one was found.
+ * plaintext.c (hash_datafiles): Don't fall back to read signature
+ from stdin.
+ * openfile.c (open_sigfile): Print verbose message only if the
+ file could be accessed.
+
+2000-11-24 Werner Koch <wk@gnupg.org>
+
+ * passphrase.c [HAVE_DOSISH_SYSTEM]: Disabled all the agent stuff.
+
+2000-11-16 Werner Koch <wk@gnupg.org>
+
+ * g10.c: New option --use-agent
+ * passphrase.c (agent_open,agent_close): New.
+ (agent_get_passphrase,agent_clear_passphrase): New.
+ (passphrase_clear_cache): New.
+ (passphrase_to_dek): Use the agent here.
+ * seckey-cert.c (do_check): Clear cached passphrases.
+
+2000-11-15 Werner Koch <wk@gnupg.org>
+
+ * status.c (write_status_text): Moved the big switch to ...
+ (get_status_string): ... new function.
+ (write_status_buffer): New.
+
+ * status.c (mywrite): New and replaced all write() by this.
+
+ * status.c, status.h: Add 3 status lcodes for notaions and policy.
+ * mainproc.c (print_notation_data): Do status output of notations.
+
+2000-11-13 Werner Koch <wk@gnupg.org>
+
+ * sign.c (clearsign_file): Use LF macro to print linefeed.
+
+2000-11-11 Paul Eggert <eggert@twinsun.com>
+
+ Clean up the places in the code that incorrectly use "long" or
+ "unsigned long" for file offsets. The correct type to use is
+ "off_t". The difference is important on large-file hosts,
+ where "off_t" is longer than "long".
+
+ * keydb.h (struct keyblock_pos_struct.offset):
+ Use off_t, not ulong, for file offsets.
+ * packet.h (dbg_search_packet, dbg_copy_some_packets,
+ search_packet, copy_some_packets): Likewise.
+ * parse-packet.c (parse, dbg_search_packet, search_packet,
+ dbg_copy_some_packets, copy_some_packets): Likewise.
+ * ringedit.c (keyring_search): Likewise.
+
+ * parse-packet.c (parse): Do not use %lu to report file
+ offsets in error diagnostics; it's not portable.
+ * ringedit.c (keyring_search): Likewise.
+
+2000-11-09 Werner Koch <wk@gnupg.org>
+
+ * g10.c (main): New option --enable-special-filenames.
+
+2000-11-07 Werner Koch <wk@gnupg.org>
+
+ * g10.c (main): New command --pipemode.
+ * pipemode.c: New.
+
+2000-10-23 Werner Koch <wk@gnupg.org>
+
+ * armor.c (armor_filter): Changed output of hdrlines, so that a CR
+ is emitted for DOS systems.
+
+ * keygen.c (read_parameter_file): Add a cast for isspace().
+
+ * status.c (myread): Use SIGINT instead of SIGHUP for DOS.
+
+2000-10-19 Werner Koch <wk@gnupg.org>
+
+ * g10.c: New option --ignore-crc-error
+ * armor.c (invalid_crc): New.
+ (radix64_read): Act on new option.
+
+ * openfile.c (try_make_homedir): Klaus Singvogel fixed a stupid
+ error introduced on Sep 6th.
+
+2000-10-18 Werner Koch <wk@gnupg.org>
+
+ * misc.c (print_cipher_algo_note): Don't print the note for AES.
+ Changed wording.
+
+2000-10-16 Werner Koch <wk@gnupg.org>
+
+ * mainproc.c (do_proc_packets): Hack to fix the problem that
+ signatures are not detected when there is a MDC packet but no
+ compression packet.
+
+ * g10.c (print_hashline): New.
+ (print_mds): Use above func with --with-colons.
+
+ * mainproc.c (check_sig_and_print): Detect multiple signatures
+ and don't verify them.
+
+2000-10-14 Werner Koch <wk@gnupg.org>
+
+ * mainproc.c (add_onepass_sig): There is an easier solution to the
+ error fixed yesterday; just check that we only have onepass
+ packets. However, the other solution provides an cleaner
+ interface and opens the path to get access to other information
+ from the armore headers.
+ (release_list): Reset some more variables.
+
+2000-10-13 Werner Koch <wk@gnupg.org>
+
+ * mainproc.c (add_gpg_control): New.
+ (do_proc_packets): use it.
+ (proc_plaintext): Changed logic to detect clearsigns.
+ (proc_tree): Check the cleartext sig with some new code.
+
+ * packet.h: New packet PKT_GPG_CONTROL.
+ * parse-packet.c (parse_gpg_control): New.
+ * misc.c (get_session_marker): New.
+ * armor.c (armor_filter): Replaced the faked 1-pass packet by the
+ new control packet.
+
+ * keyedit.c (keyedit_menu): Allow batchmode with a command_fd.
+ * status.c (my_read): New.
+ (do_get_from_fd): use it.
+
+2000-10-12 Werner Koch <wk@gnupg.org>
+
+ * keygen.c (keygen_add_std_prefs): Add Rijndael to the prefs.
+
+2000-10-07 Werner Koch <wk@gnupg.org>
+
+ * gpgv.c: Add more stubs for ununsed code to make the binary smaller.
+
+Wed Oct 4 15:50:18 CEST 2000 Werner Koch <wk@openit.de>
+
+ * sign.c (hash_for): New arg to take packet version in account, changed
+ call callers.
+
+ * gpgv.c: New.
+ * Makefile.am: Rearranged source files so that gpgv can be build with
+ at least files as possible.
+
+Mon Sep 18 12:13:52 CEST 2000 Werner Koch <wk@openit.de>
+
+ * hkp.c (not_implemented): Print a notice for W32
+
+Fri Sep 15 18:40:36 CEST 2000 Werner Koch <wk@openit.de>
+
+ * keygen.c (keygen_add_std_prefs): Changed order of preferences to
+ twofish, cast5, blowfish.
+
+ * pkclist.c (algo_available): Removed hack to disable Twofish.
+
+Thu Sep 14 17:45:11 CEST 2000 Werner Koch <wk@openit.de>
+
+ * parse-packet.c (dump_sig_subpkt): Dump key flags. Print special
+ warning in case of faked ARRs.
+
+ * getkey.c (finsih_lookup): Hack so that for v4 RSA keys the subkey
+ is used for encryption.
+
+Thu Sep 14 14:20:38 CEST 2000 Werner Koch <wk@openit.de>
+
+ * g10.c (main): Default S2K algorithms are now SHA1 and CAST5 - this
+ should solve a lot of compatibility problems with other OpenPGP
+ apps because those algorithms are SHOULD and not optional. The old
+ way to force it was by using the --openpgp option whith the drawback
+ that this would disable a couple of workarounds for PGP.
+
+ * g10.c (main): Don't set --quite along with --no-tty. By Frank Tobin.
+
+ * misc.c (disable_core_dump): Don't display a warning here but a return
+ a status value and ...
+ * g10.c (main): ...print warnining here. Suggested by Sam Roberts.
+
+Wed Sep 13 18:12:34 CEST 2000 Werner Koch <wk@openit.de>
+
+ * keyedit.c (keyedit_menu): Allow to use "debug" on the secret key.
+
+ * ringedit.c (cmp_seckey): Fix for v4 RSA keys.
+ * seckey-cert.c (do_check): Workaround for PGP 7 bug.
+
+Wed Sep 6 17:55:47 CEST 2000 Werner Koch <wk@openit.de>
+
+ * misc.c (print_pubkey_algo_note): Do not print the RSA notice.
+ * sig-check.c (do_signature_check): Do not emit the RSA status message.
+ * pubkey-enc.c (get_session_key): Ditto.
+
+ * encode.c (encode_simple, encode_crypt): Fix for large files.
+ * sign.c (sign_file): Ditto.
+
+Wed Sep 6 14:59:09 CEST 2000 Werner Koch <wk@openit.de>
+
+ * passphrase.c (hash_passphrase): Removed funny assert. Reported by
+ David Mathog.
+
+ * openfile.c (try_make_homedir): Changes for non-Posix systems.
+ * g10.c (main): Take the default homedir from macro.
+
+ * g10.c: The --trusted-key option is back.
+ * trustdb.c (verify_own_key): Handle this option.
+ (add_ultimate_key): Moved stuff from verify_own_key to this new func.
+ (register_trusted_key): New.
+
+Fri Aug 25 16:05:38 CEST 2000 Werner Koch <wk@openit.de>
+
+ * parse-packet.c (dump_sig_subpkt): Print info about the ARR.
+
+ * openfile.c (overwrite_filep): Always return okay if the file is
+ called /dev/null.
+ (make_outfile_name): Add ".sign" to the list of know extensions.
+ (open_sigfile): Ditto.
+
+Wed Aug 23 19:52:51 CEST 2000 Werner Koch <wk@openit.de>
+
+ * g10.c: New option --allow-freeform-uid. By Jeroen C. van Gelderen.
+ * keygen.c (ask_user_id): Implemented here.
+
+Fri Aug 4 14:23:05 CEST 2000 Werner Koch <wk@openit.de>
+
+ * status.c (do_get_from_fd): Ooops, we used fd instead of opt.command_fd.
+ Thanks to Michael Tokarev.
+
+Tue Aug 1 20:06:23 CEST 2000 Werner Koch <wk@openit.de>
+
+ * g10.c: New opttion --try-all-secrets on suggestion from Matthias Urlichs.
+ * pubkey-enc.c (get_session_key): Quite easy to implement here.
+
+Thu Jul 27 17:33:04 CEST 2000 Werner Koch <wk@openit.de>
+
+ * g10.c: New option --merge-only. Suggested by Brendan O'Dea.
+ * import.c (import_one): Implemented it here
+ (import_secret_one): Ditto.
+ (print_stats): and give some stats.
+
+Thu Jul 27 12:01:00 CEST 2000 Werner Koch <wk@openit.de>
+
+ * g10.c: New options --show-session-key and --override-session-key
+ * pubkey-enc.c (hextobyte): New.
+ (get_override_session_key): New.
+ * mainproc.c (proc_pubkey_enc): Add session-key stuff.
+ * status.h, status.c (STATUS_SESSION_KEY): New.
+
+Thu Jul 27 10:02:38 CEST 2000 Werner Koch <wk@openit.de>
+
+ * g10.c (main): Use setmode(O_BINARY) for MSDOS while generating random bytes
+ (print_mds): Likewise for stdin.
+ * plaintext.c (handle_plaintext): Likewise for stdout.
+
+Mon Jul 24 10:30:17 CEST 2000 Werner Koch <wk@openit.de>
+
+ * keyedit.c (menu_expire): expire date for primary key can be set again.
+
+Wed Jul 19 11:26:43 CEST 2000 Werner Koch <wk@openit.de>
+
+ * keylist.c (is_uid_valid): New.
+ (list_keyblock): Print validity information for all user IDs. Note, this
+ has to be done at other places too; for now we have only minimal support.
+
+Wed Jul 12 13:32:06 CEST 2000 Werner Koch <wk@openit.de>
+
+ * helptext.c, pkclist.c: s/superseeded/superseded/
+
+Mon Jul 10 16:08:57 CEST 2000 Werner Koch <wk@openit.de>
+
+ * parse-packet.c (enum_sig_subpkt): Fixed testing on crtitical bit in case
+ of a NULL buffer. Reported by Peter Marschall.
+
+Wed Jul 5 13:28:45 CEST 2000 Werner Koch <wk@openit.de>
+
+ * keyedit.c, keyid.c: Add some _()
+
+ * argparse.c: Changed the flag to suppress --version handling to also
+ suppress --help.
+
+Wed Jun 28 11:54:44 CEST 2000 Werner Koch <wk@openit.de>
+
+ * armor.c (armor_filter): Set sigclass to 0 in case of non-dash-escaped
+ clearsig. This makes this mode work again.
+
+ * mainproc.c (proc_tree): Fixed handling of one-pass-sig packets in textmode.
+ Disabled the ugly workaround for PGP 5 - let's see whether thi breaks less
+ cases. Found by Ted Cabeen.
+
+ * options.h (DBG_HASHING): New. All commented md_start_debug are now
+ controlled by this debug option.
+
+ * sign.c (print_status_sig_created): New and called from 2 places.
+
+ * keygen.c (gen_rsa): New, but commented.
+ (ask_algo): Commented support for RSA.
+
+ * seckey-cert.c (protect_secret_key): Started to fix the code for v4 RSA
+ keys - it is not solved yet. However, we have time until, Sep 20th ;)
+
+Wed Jun 14 12:27:09 CEST 2000 Werner Koch <wk@openit.de>
+
+ * status.c (init_shm_coprocessing): Changed the sequence of the get,attach
+ to cope with the changes in newer Linux kernels. This bug has been found
+ by <dmitri@advantrix.com> who also proposed this solution. Hopefully
+ this does not break gpg on to many systems.
+
+ * cipher.c (write_header): Protect the IV with the MDC too.
+ * encr-data.c (decrypt_data): Likewise.
+
+Fri Jun 9 10:09:52 CEST 2000 Werner Koch <wk@openit.de>
+
+ * g10.c: New options --no-auto-key-retrieve
+ * options.h (auto_key_retrieve): New.
+ * mainproc.c (check_sig_and_print): Implemented that.
+
+Wed Jun 7 19:19:09 CEST 2000 Werner Koch <wk@openit.de>
+
+ * sig-check.c (do_check): Use EMULATE_MDENCODE also on v4 packets.
+
+Wed Jun 7 17:25:38 CEST 2000 Werner Koch <wk@openit.de>
+
+ * cipher.c (write_header): Use plain CFB mode for MDC encrypted packets.
+ * encr-data.c (decrypt_data): Ditto.
+
+Mon Jun 5 23:41:54 CEST 2000 Werner Koch <wk@openit.de>
+
+ * seskey.c (do_encode_md, encode_md_value): Add new arg v3compathack to work
+ around a bug in old versions.
+ * sig-check.c (do_check): use the aboved workaround when enabled.
+ * g10.c: New option --emulate-md-decode-bug
+
+Mon Jun 5 12:37:43 CEST 2000 Werner Koch <wk@openit.de>
+
+ * build-packet.c (do_mdc): New.
+ (do_encrypted_mdc): Changed for the new proposal.
+ * parse-packet.c (parse_mdc): New.
+ (parse_encrypted): Fixed for the new proposal.
+ * packet.h (PKT_MDC): New.
+ * cipher.c (cipher_filter): Build the MDC packet here.
+ * g10.c (main): Enable --force-mdc.
+ * encr-data.c (mdc_decode_filter): Fixed for new MDC method
+
+ * options.h(rfc2440): New.
+ * g10.c (main): Changed the selected values for --openpgp to not include
+ optional algorithms.
+
+Thu May 18 11:38:54 CEST 2000 Werner Koch <wk@openit.de>
+
+ * keyedit.c (keyedit_menu): Add a keyword arg to the prompt.
+
+ * status.c, status.h: Added 3 new status tokens.
+ * status.c (do_get_from_fd): New.
+ (cpr_enabled,cpr_get,cpr_get_hidden,cpr_kill_prompt,
+ cpr_get_answer_is_yes,cpr_get_answer_yes_no_quit): Modified to work
+ with the new function.
+ * g10.c: Add new option --command-fd.
+
+ * status.c (progress_cb): New.
+ (set_status_fd): Register progress functions
+
+Fri May 12 14:01:20 CEST 2000 Werner Koch <wk@openit.de>
+
+ * delkey.c (delete_key): Add 2 new status messages
+ * status.c, status.h (STATUS_DELETE_PROBLEM): New.
+
+ Fixed years of copyright in all source files.
+
+Mon May 1 17:08:14 CEST 2000 Werner Koch <wk@openit.de>
+
+ * trustdb.c (propagate_validity): Fixed the bug that only one uid
+ gets fully trusted even when all are signed by an ultimate key.
+
+Mon May 1 15:38:04 CEST 2000 Werner Koch <wk@openit.de>
+
+ * getkey.c (key_byname): Always returned a defined context. Fixed
+ a segv for invalid user id specifications. Reported by Walter Koch.
+
+ * getkey.c (get_user_id): I18ned "no user id" string. By Walter.
+
+ * pkclist.c (do_show_revocation_reason): Typo fixes.
+ * helptext.c: Ditto.
+
+ * armor.c (armor_filter): Fixed some CRLF issues. By Mike McEwan.
+
+Fri Apr 14 19:37:08 CEST 2000 Werner Koch <wk@openit.de>
+
+ * pkclist.c (do_show_revocation_reason): New.
+ (show_revocation_reason): New and called at various places.
+
+ * g10.c (main): Fixed small typo.
+
+ * pkclist.c (do_we_trust): Act on always_trust but not for revoked
+ keys. Suggested by Chip Salzenberg.
+
+ * g10.c: New option --lock-never.
+
+ * ringedit.c (get_writable_keyblock_file): New.
+ * keygen.c (do_generate_keypair): Use this instead of the hardwired one.
+
+ * keygen.c (ask_user_id): Check that the email address is in the
+ correct field. Suggested by Christian Kurz.
+
+Mon Apr 10 13:34:19 CEST 2000 Werner Koch <wk@openit.de>
+
+ * keyedit.c (show_key_with_all_names): s/sbb/ssb/
+
+Tue Mar 28 14:26:58 CEST 2000 Werner Koch <wk@openit.de>
+
+ * trustdb.c (verify_own_keys): Do not print warning about unprotected
+ key when in quiet mode.
+
+Wed Mar 22 13:50:24 CET 2000 Werner Koch <wk@openit.de>
+
+ * mainproc.c (print_userid): Do UTF8 conversion before printing.
+ * import.c (import_one): Ditto.
+ (import_secret_one): Ditto.
+ (delete_inv_parts): Ditto.
+
+Thu Mar 16 16:20:23 CET 2000 Werner Koch <wk@openit.de>
+
+ * keylist.c (print_key_data): Handle a NULL pk gracefully.
+
+ * getkey.c (merge_one_pk_and_selfsig): Fixed silly code for
+ getting the primary keys keyID but kept using the one from the
+ subkey.
+ * pubkey-enc.c (get_it): Print a note for expired subkeys.
+
+ * getkey.c (has_expired): New.
+ (subkeys_expiretime): New.
+ (finish_lookup): Check for expired subkeys needed for encryption.
+ (merge_keys_and_selfsig): Fixed expiration date merging for subkeys.
+
+ * keylist.c (list_keyblock): Print expiration time for "sub".
+ (list_one): Add missing merging for public keys.
+ * mainproc.c (list_node): Ditto.
+
+2000-03-14 13:49:38 Werner Koch (wk@habibti.openit.de)
+
+ * keygen.c (keyedit_menu): Do not allow to use certain commands
+ while the secret key is selected.
+
+2000-03-09 12:53:09 Werner Koch (wk@habibti.openit.de)
+
+ * keygen.c (ask_expire_interval): Movede parsig to ...
+ (parse_expire_string): ... this new function. And some new control
+ commands.
+ (proc_parameter_file): Add expire date parsing.
+ (do_generate_keypair): Allow the use of specified output files.
+
+2000-03-08 10:38:38 Werner Koch (wk@habibti.openit.de)
+
+ * keygen.c (ask_algo): Removed is_v4 return value and the commented
+ code to create Elg keys in a v3 packet. Removed the rounding
+ of key sizes here.
+ (do_create): Likewise removed arg v4_packet.
+ (gen_elg): Likewise removed arg version. Now rounding keysizes here.
+ (gen_dsa): Rounding keysize now here.
+ (release_parameter_list): New
+ (get_parameter*): New.
+ (proc_parameter_file): New.
+ (read_parameter_file): New.
+ (generate_keypair): Splitted. Now uses read_parameter_file when in
+ batch mode. Additional argument to specify a parameter file.
+ (do_generate_keypair): Main bulk of above fucntion and uses the
+ parameter list.
+ (do_create): Don't print long notice in batch mode.
+ * g10.c (main): Allow batched key generation.
+
+Thu Mar 2 15:37:46 CET 2000 Werner Koch <wk@gnupg.de>
+
+ * pubkey-enc.c (get_it): Print a note about unknown cipher algos.
+
+ * g10.c (opts): Add a note to the help listing about the man page
+ and removed some options from the help listing.
+
+ * keyedit.c (print_and_check_one_sig): Use a new function to truncate
+ the output of the user ID. Suggested by Jan-Benedict Glaw.
+
+Wed Feb 23 10:07:57 CET 2000 Werner Koch <wk@gnupg.de>
+
+ * helptext.c: typo fix.
+
+Thu Feb 17 13:39:32 CET 2000 Werner Koch <wk@gnupg.de>
+
+ * revoke.c: Removed a bunch of commented code.
+
+ * packet.h (SIGSUBPKT_REVOC_REASON): New.
+ * build-packet.c (build_sig_subpkt): Support new sub packet.
+ * parse-packet.c (parse_one_sig_subpkt): Ditto.
+ (dump_sig_subpkt): Ditto.
+ * revoke.c (ask_revocation_reason): New.
+ (release_revocation_reason_info): New.
+ (revocation_reason_build_cb): New.
+ (gen_revoke): Ask for reason.
+ * main.h (struct revocation_reason_info): Add declaration.
+ * keyedit.c (menu_revsig): Add support for revocation reason.
+ (menu_revkey): Ditto.
+ (sign_uid_mk_attrib): Renamed to ...
+ (sign_mk_attrib): ... this, made static and add support for reasons.
+
+Tue Feb 15 08:48:13 CET 2000 Werner Koch <wk@gnupg.de>
+
+ * build-packet.c (build_packet): Fixed fixing of old comment packets.
+
+ * import.c (import_keys): Fixed importing from stdin when called with
+ nnames set to zero as it normally happens.
+
+Mon Feb 14 14:30:20 CET 2000 Werner Koch <wk@gnupg.de>
+
+ * sig-check.c (check_key_signature2): Add new arg r_expired.
+ (do_signature_check): New arg to pass it down to ...
+ (do_check): New arg r-expire which is set when the signature
+ has expired.
+ * trustdb.c (check_sig_record): Set SIGF_EXPIRED flag and set
+ the expiretime to zero so that thi signature will not be checked
+ anymore.
+
+Fri Feb 11 17:44:40 CET 2000 Werner Koch <wk@gnupg.de>
+
+ * g10.c (g10_exit): Update the random seed_file.
+ (main): Set the random seed file. New option --no-random-seed-file.
+
+Thu Feb 10 17:39:44 CET 2000 Werner Koch <wk@gnupg.de>
+
+ * keyedit.c (menu_expire): Fixed segv due to unitialized sub_pk.
+ By Rémi.
+
+Thu Feb 10 11:39:41 CET 2000 Werner Koch <wk@gnupg.de>
+
+ * keylist.c (list_keyblock): Don't print warnings in the middle of
+ regulat output lines. By Rémi.
+
+ * sig-check.c: Include options.h
+
+Wed Feb 9 15:33:44 CET 2000 Werner Koch <wk@gnupg.de>
+
+ * gpg.c: New option --ignore-time-conflict
+ * sig-check.c (do_check): Implemented this option.
+ * trustdb.c (check_trust): Ditto.
+ * sign.c (do_sign): Ditto.
+ * keygen.c (generate_subkeypair): Ditto.
+
+ * encode.c (encode_simple): use iobuf_cancel after open failure.
+ Reported by Huy Le.
+
+Fri Jan 14 18:32:01 CET 2000 Werner Koch <wk@gnupg.de>
+
+ * packet.h (STRING2KEY): Changed mode from byte to int.
+ * parse-packet.c (parse_key): Add the special GNU protection stuff
+ * build-packet.c (so_secret_key): Ditto.
+ * seckey-cert.c (do_check): Ditto.
+ * keyedit.c (change_passphrase): Ditto.
+ * export.c (export_secsubkeys): New.
+ (do_export_stream): Hack to export the primary key using mode 1001.
+ * g10.c: New command --export-secret-subkeys
+
+Thu Jan 13 19:31:58 CET 2000 Werner Koch <wk@gnupg.de>
+
+ * armor.c (is_armored): Check for 1-pass-sig packets. Reported by
+ David Hallinan <hallinan@rtd.com>.
+ (armor_filter): Replaced one LF by the LF macro. Reported by
+ Wolfgang Redtenbacher.
+
+Wed Jan 5 11:51:17 CET 2000 Werner Koch <wk@gnupg.de>
+
+ * g10.c (main): Reset new global flag opt.pgp2_workarounds
+ when --openpgp is used.
+ * mainproc.c (proc_plaintext): Do the PGP2,5 workarounds only
+ when the global flag is set.
+ (proc_tree): Ditto.
+ * textfilter.c (copy_clearsig_text): Ditto.
+ * armor.c (armor_filter): Ditto.
+
+ * g10.c: New option --list-only
+ * mainproc.c (proc_tree): Don't do it if opt.list_only is active.
+ (proc_pubkey_enc): Implement option.
+
+ * status.h, status.c ({BEGIN,END}_{EN,DE}CRYPTION): New.
+ * cipher.c (cipher_filter): New status outputs.
+ * mainproc.c (proc_encrypted): New status outputs.
+
+Fri Dec 31 14:08:15 CET 1999 Werner Koch <wk@gnupg.de>
+
+ * armor.c (armor_filter): Made the "Comment:" header translatable.
+
+ * hkp.c (hkp_import): Make sure that the program does not return
+ success when there is a connection problem. Reported by Phillip Jones.
+
+Sun Dec 19 15:22:26 CET 1999 Werner Koch <wk@gnupg.de>
+
+ * armor.c (LF): Use this new macro at all places where a line LF
+ is needed. This way DOSish textfiles should be created when the
+ input data is also in dos mode.
+ * sign.c (LF): Ditto.
+ * textfilter.c (LF): Ditto.
+ (copy_clearsig_text): Disabled the forcing of CR,LF sequences
+ for DOS systems.
+
+ * plaintext.c (handle_plaintext): Fixes for line endings on DOS.
+ and react on a LF in cleartext.
+ * armor.c (fake_packet): Restore the original line ending after
+ removing trailing spaces.
+
+ * signal.c (got_fatal_signal): DOS fix.
+
+Thu Dec 16 10:07:58 CET 1999 Werner Koch <wk@gnupg.de>
+
+ * mainproc.c (print_failed_pkenc): Fix for unknown algorithm.
+ Found by fygrave@epr0.org.
+
+Thu Dec 9 10:31:05 CET 1999 Werner Koch <wk@gnupg.de>
+
+ * hkp.c: i18n the strings.
+
+Sat Dec 4 15:32:20 CET 1999 Werner Koch <wk@gnupg.de>
+
+ * trustdb.c (verify_key): Shortcut for ultimately trusted keys.
+
+Sat Dec 4 12:30:28 CET 1999 Werner Koch <wk@gnupg.de>
+
+ * pkclist.c (build_pk_list): Validate the trust using the namehash
+ if this one has been set by the key lookup.
+
+ * g10.c: Add --delete-secret-key to the help page.
+
+ * openfile.c (copy_options_file): Made static.
+ (try_make_homedir): New.
+ * ringedit.c (add_keyblock_resource): Use the try_make_hoemdir logic.
+ * tdbio.c (tdbio_set_dbname): Likewise.
+
+ * keygen.c (generate_user_id): Use m_alloc_clear() here. We should
+ better use an allocation function specific to the user_id packet.
+
+ * keygen.c (keygen_add_std_prefs): Changed symmetric preferences
+ to include Blowfish again. This is due to it's better speed compared
+ to CAST5.
+
+ * g10.c (strusage): Print the home directory.
+
+ * armor.c (armor_filter): Take action on the cancel control msg.
+ * filter.h (armor_filter_context_t): Add cancel flag.
+
+Mon Nov 29 21:52:11 CET 1999 Werner Koch <wk@gnupg.de>
+
+ * g10.c: New option --fast-list-mode ..
+ * keylist.c (list_keyblock): .. and implemented.
+ * mainproc.c (list_node): Ditto.
+
+ * import.c (mark_non_selfsigned_uids_valid): Fixed the case that there
+ is a uid without any packet following.
+
+Mon Nov 22 11:14:53 CET 1999 Werner Koch <wk@gnupg.de>
+
+ * mainproc.c (proc_plaintext): Never enable the hash processing
+ when skip_verify is active.
+
+ * armor.c (parse_header_line): Stop parsing on a WS line too.
+ Suggested by Aric Cyr.
+
+ * tdbdump.c (HEXTOBIN): Changed the name of the argument, so that
+ traditional cpp don't mess up the macros. Suggested by Jos Backus.
+
+ * mainproc.c (list_node): Print the PK algo in the --with-colon mode.
+ * keylist.c (list_keyblock): Ditto.
+
+ * signal.c (got_fatal_signal): Found the reason why exit(8) did not
+ work - it is better to set the disposition back to default before
+ raising the signal. Print the notice on stderr always.
+
+Fri Nov 12 20:33:19 CET 1999 Werner Koch <wk@gnupg.de>
+
+ * g10.c (make_username): Swapped the logic.
+ * keylist.c (public_key_list): Now takes a STRLIST as arg and moved
+ the creation ot this list to the caller, so that he can copy with
+ UTF-conversion of user IDs. Changed all callers.
+ (secret_key_list): Likewise.
+
+ * getkey.c (get_user_id_string_native): New and ...
+ * encode.c (write_pubkey_enc_from_list): ... use it here.
+
+ * pubring.asc: Updated.
+
+ * packet.h (PKT_PHOTO_ID): New.
+ * parse-packet.c (parse_photo_id): New.
+ * build-packet.c (do_user_id: Handle photo IDs.
+ (build_packet): Change CTB for photo IDs
+ * free-packet.c (free_user_id): Release memory used for photo IDs
+ * sig-check.c (hash_uid_node): Handle photo IDs too.
+ * trustdb.c (print_uid_from_keyblock): Hash photo ID.
+ (make_uid_records): Ditto.
+ * getkey.c (find_by_name): Ditto.
+ * keyedit.c (show_prefs): Ditto.
+ * keylist.c (list_keyblock): Ditto.
+
+Thu Oct 28 16:08:20 CEST 1999 Werner Koch <wk@gnupg.de>
+
+ * keygen.c (ask_expire_interval): Print a warning for systems
+ with a signed 32 time_t if the exiration time is beyoind 2038.
+
+Fri Oct 8 20:40:50 CEST 1999 Werner Koch <wk@gnupg.de>
+
+ * ringedit.c (enum_keyblocks): The last fix way really stupid;
+ reverted and set rt to Unknown.
+
+Fri Oct 8 20:32:01 CEST 1999 Werner Koch <wk@gnupg.de>
+
+ * ringedit.c (enum_keyblocks): Zero the entire kbpos out on open.
+
+ * g10.c (oEntropyDLL): Removed option.
+ (main): Made the warning on development versions more verbose.
+
+ * g10.c (oHonorHttpProxy): New option.
+ * hkp.c (hkp_ask_import,hkp_export): Implement this option.
+ * options.skel: Enable this option for new installations
+
+Mon Oct 4 21:23:04 CEST 1999 Werner Koch <wk@gnupg.de>
+
+ * import.c (import_keys): Changed calling interface, adjusted caller.
+ (import): Moved printing of stats out ...
+ (print_stats): New. ... to here.
+ (import_keys_stream): Call stats print here.
+ (import_keys): Print stats as totals for all files.
+
+ * tdbio.h (DIRF_NEWKEYS): New
+ * tdbio.c (tdbio_dump_record): Print the new flag.
+ * trustdb.c (check_trust_record): New arg sigs_only. Adapted all
+ callers.
+ (do_update_trust_record): Removed recheck arg and add a new sigs_only
+ do we can later improve on the performance. Changed all callers too.
+ (check_trustdb): Evalutate the new flag and add a status output.
+ Do a check when the dir record has not been checked.
+ (build_cert_tree): Evaluate the new flag.
+ (check_trust): Ditto. Do a trust_record check, when the dir record
+ is not marked as checked.
+ (mark_fresh_keys): New.
+ (clear_lid_table): New.
+ (sync_trustdb): New.
+ * import.c (import_keys): Call sync_trustdb() after processing.
+ (import_keys_stream): Ditto.
+ * tdbdump.c (import_ownertrust): Ditto.
+
+ * import.c (import_revoke_cert): Notify the trust DB.
+ (do_update_trust_record): Use |= to set the REVOKED bit and not &=;
+ shame on me for this bad copy+paste introduced bug.
+ (do_we_trust): Add trustmask to allow revoked key override to work.
+ Chnaged are to allow return of a mofified trustlevel. Adapted the
+ one caller.
+
+ * g10.c: New options --emulate-3des-s2k-bug
+ * passphrase.c (hash_passphrase): Implemented above.
+
+ * mainproc.c (proc_tree): Check for standalone signatures.
+ (do_check_sig): Print a notice for a standalone revocation
+ (check_sig_and_print): Do not print an error for unchecked standalone
+ revocations.
+
+Tue Sep 28 20:54:37 CEST 1999 Werner Koch <wk@gnupg.de>
+
+ * encode.c (encode_simple): Use new CTB when we don't have the
+ length of the file. This is somewhat strange as the comment above
+ indicates that this part is actually fixed for PGP 5 - maybe I simply
+ lost the source line, tsss.
+
+ * armor.c (armor_filter): Set a flag if no OpenPGP data has been found.
+ * verify.c (verify_signatures): Add an error helptext.
+
+Thu Sep 23 19:24:30 CEST 1999 Werner Koch <wk@isil.d.shuttle.de>
+
+ * openfile.c (open_outfile): Fixed the 8dot3 handling.
+
+ * passphrase.c (passphrase_to_dek): Print uid using utf8 func.
+ * delkey.c (delete_key): Ditto.
+ * pkclist.c (show_paths,do_edit_ownertrust,do_we_trust): Ditto
+ (do_we_trust_pre): Ditto.
+ * trustdb.c (print_user_id,check_uidsigs): Ditto.
+ * revoke.c (gen_revoke,ask_revoke_sig): Ditto.
+
+Thu Sep 23 09:52:58 CEST 1999 Werner Koch <wk@isil.d.shuttle.de>
+
+ * verify.c (print_file_status): New.
+ (verify_one_file): Moved status print to th new fnc. Add error status.
+ * status.c, status.h (STATUS_FILE_ERROR): New
+
+Wed Sep 22 10:14:17 CEST 1999 Werner Koch <wk@isil.d.shuttle.de>
+
+ * openfile.c (make_outfile_name): Use case-insenstive compare for
+ DOS systems. Add ".pgp" to the list of know extensions.
+ (open_outfile): For DOS systems try to replace the suffiy instead of
+ appending it.
+
+ * status.c, status.h: Add STATUS_FILE_{START,DONE}.
+ * verify.c (verify_one_file): Emit these new stati.
+
+ * sign.c (clearsign_file): Avoid duplicated Entries in the "Hash:"
+ line. Those headers are now only _not_ printed when there are
+ only old-style keys _and_ all hashs are MD5.
+
+Mon Sep 20 12:24:41 CEST 1999 Werner Koch <wk@isil.d.shuttle.de>
+
+
+ * verify.c (verify_files, ferify_one_file): New.
+ * g10.c: New command --verify-files
+
+Fri Sep 17 12:56:42 CEST 1999 Werner Koch <wk@isil.d.shuttle.de>
+
+ * g10.c: Add UK spelling as alias for armor options ;-)
+
+ * import.c (append_uid): Fixed a SEGV when there is no selfsig and
+ no subkey.
+ (merge_sigs): Ditto. Removed the assertion.
+
+Wed Sep 15 16:22:17 CEST 1999 Werner Koch <wk@isil.d.shuttle.de>
+
+ * g10.c: New option --entropy-dll-name
+
+Mon Sep 13 10:51:29 CEST 1999 Werner Koch <wk@isil.d.shuttle.de>
+
+ * signal.c (got_fatal_signal): Print message using write(2) and
+ only for development versions.
+
+Mon Sep 6 19:59:08 CEST 1999 Werner Koch <wk@isil.d.shuttle.de>
+
+ * tdbio.c (tdbio_set_dbname): Use mkdir macro
+ * ringedit.c (add_keyblock_resource): Ditto.
+
+Fri Sep 3 10:04:45 CEST 1999 Werner Koch <wk@isil.d.shuttle.de>
+
+ * pkclist.c (build_pk_list): Skip keys set with --encrypt-to also
+ when asking for a key.
+
+ * plaintext.c (handle_plaintext): Make sure that we don't read a
+ second EOF in the read loop for partial length packets.
+
+ * mainproc.c (check_sig_and_print): print user ID as utf-8.
+
+Thu Sep 2 16:40:55 CEST 1999 Werner Koch <wk@isil.d.shuttle.de>
+
+ * import.c (merge_blocks): First add new subkeys, then merge subkey
+ certificates.
+ (merge_sigs): Don't merge subkey signatures here.
+
+Wed Sep 1 15:30:44 CEST 1999 Werner Koch <wk@isil.d.shuttle.de>
+
+ * keygen.c (ask_expire_interval): Fixed bug related to cpr_xx (tnx
+ Francis J. Lacoste).
+
+Tue Aug 31 17:20:44 CEST 1999 Werner Koch <wk@isil.d.shuttle.de>
+
+ * plaintext.c (do_hash): Hash CR,LF for a single CR.
+ (ask_for_detached_datafile): Changed arguments to be closer to
+ those of hash_datafiles and cleanup the code a bit.
+ * mainproc.c (proc_tree): Workaround for pgp5 textmode detached
+ signatures. Changed behavior of asking for data file to be the same
+ as with provided data files.
+
+ * keylist.c (list_keyblock): Use UTF8 print functions.
+
+Mon Aug 30 20:38:33 CEST 1999 Werner Koch <wk@isil.d.shuttle.de>
+
+ * import.c (chk_self_sigs): some s/log_error/log_info/ so that gpg
+ does not return an error if a key has some invalid packets.
+
+ * helptext.c: Fixed some typos and changed the way the
+ translation works. The english text is now the keyword for gettext
+ and not anymore the keyword supplied to the function. Done after
+ some discussion with Walter who thinks this is much easier for the
+ translators.
+
+ * misc.c (disable_core_dumps): Don't do it for DOSish systems.
+
+ * signal.c (signal_name): Bounds check on signum.
+
+Wed Aug 4 10:34:18 CEST 1999 Werner Koch <wk@isil.d.shuttle.de>
+
+ * pubring.asc: Updated.
+
+ * pkclist.c (do_we_trust_pre,check_signatures_trust): Do not print
+ the warning about --always_trust when --quiet is used.
+
+ * pkclist.c (fpr_info): New and called at several places.
+
+ * parse-packet.c (dump_sig_subpkt): List revocation key contents.
+
+Mon Jul 26 09:34:46 CEST 1999 Werner Koch <wk@isil.d.shuttle.de>
+
+ * pkclist.c (build_pk_list): Fixed typo in format string.
+
+ * trustdb.c (create_shadow_dir): Don't translate the error string.
+
+ * g10.c (main): Fixed spelling of user-id.
+ * getkey.c (find_by_name_pk,find_by_name_sk,
+ find_by_keyid,find_by_keyid_sk): Ditto and translate it.
+ * import.c (mark_non_selfsigned_uids_valid,delete_inv_parts): Ditto.
+
+
+Mon Jul 26 01:01:39 CEST 1999 Michael Roth <mroth@nessie.de>
+
+ * g10.c, options.h: New options --no-literal and --set-filesize
+
+ * encode.c (encode_simple, encode_crypt): Support for the options
+ --no-literal and --set-filesize.
+
+ * sign.c (sign_file): ditto.
+
+Fri Jul 23 13:53:03 CEST 1999 Werner Koch <wk@isil.d.shuttle.de>
+
+
+ * ringedit.c (enum_keyblocks): Removed annoying error message in cases
+ when we have no keyring at all to enum.
+
+ * getkey.c (classify_user_id): Rewrote to relax the recognition of
+ keyIDs and fingerprints (Michael).
+
+ * mainproc.c (check_sig_and_print): Print status NO_PUBKEY.
+ (print_failed_pkenc): Print status NO_SECKEY.
+
+ * import.c (mark_non_selfsigned_uids_valid): New.
+ * g10.c: New option --allow-non-selfsigned-uid.
+
+ * pkclist.c (print_fpr): New.
+ (do_we_trust_pre): Print the fpr before asking whether to use the key
+ anyway.
+ (do_edit_ownertrust): Likewise.
+
+Thu Jul 22 20:03:03 CEST 1999 Werner Koch <wk@isil.d.shuttle.de>
+
+
+ * ringedit.c (enum_keyblocks): Removed annoying error message in cases
+ when we have no keyring at all to enum.
+
+ * getkey.c (classify_user_id): Rewrote to relax the recognition of
+ keyIDs and fingerprints (Michael).
+
+ * mainproc.c (check_sig_and_print): Print status NO_PUBKEY.
+ (print_failed_pkenc): Print status NO_SECKEY.
+
+ * import.c (mark_non_selfsigned_uids_valid): New.
+ * g10.c: New option --allow-non-selfsigned-uid.
+
+Thu Jul 15 10:15:35 CEST 1999 Werner Koch <wk@isil.d.shuttle.de>
+
+ * g10.c: New options --disable-{cipher,pubkey}-algo.
+
+Wed Jul 14 19:42:08 CEST 1999 Werner Koch <wk@isil.d.shuttle.de>
+
+ * status.h (STATUS_IMPORTED): New.
+ * import.c (import): Print some status information (Holger Schurig).
+
+ * g10.c (main): Make --no-greeting work again. Add a warning when
+ --force-mds is used.
+
+Tue Jul 13 17:39:25 CEST 1999 Werner Koch <wk@isil.d.shuttle.de>
+
+ * pkclist.c (do_edit_ownertrust): Changed the way help works.
+ (build_pk_list): Implemented default recipient stuff.
+ * g10.c: New options --default-recipient[-self]
+ (main): Suppress greeting in most cases, entering a passphrase or
+ a missing value is not considered to be interactive use.
+ Merged --print-md and --print-mds; the latter is now obsolete.
+ Changed the way --gen-random works and documented it.
+ Changed the way --gen-prime works and add a man entry.
+ * g10.c (MAINTAINER_OPTIONS): Removed.
+
+Mon Jul 12 18:45:57 CEST 1999 Werner Koch <wk@isil.d.shuttle.de>
+
+ * keyedit.c (keyedit_menu): Add arg sign_mode and changed callers
+ * g10.c (main): New command --lsign-key.
+
+Mon Jul 12 14:55:34 CEST 1999 Werner Koch <wk@isil.d.shuttle.de>
+
+ * mainproc.c (kidlist_item): New.
+ (release_list): Release failed pk-enc-list.
+ (print_failed_pkenc): New
+ (proc_encrypted): Print info about failed PK enc.
+
+ * openfile.c (make_outfile_name): s/error/info/
+
+ * passphrase.c (passphrase_to_dek): Return an empty passphrase when
+ in batch mode and don't make the warning message fatal
+ * seckey-cert.c (check_secret_key): Try only once when in batch mode.
+
+ * g10.c (make_username): New.
+
+Thu Jul 8 16:21:27 CEST 1999 Werner Koch <wk@isil.d.shuttle.de>
+
+
+ * packet.h (PKT_ring_trust): New
+ * parse-packet.c (parse_trust): Store trust value
+ * build-packet (build_packet): Ignore ring trust packets.
+ * mainproc.c (add_ring_trust): New.
+ (list_node): Print "rtv" records.
+ * g10.c: New option --with-fingerprint.
+
+ * trustdb.c (verify_own_keys): Don't insert if we are dry running
+ (check_trust): Ditto.
+
+Wed Jul 7 13:08:40 CEST 1999 Werner Koch <wk@isil.d.shuttle.de>
+
+ * Makefile.am: Support for libtool.
+
+ * keygen.c (ask_expire_interval): Hack to allow for an expire date.
+
+ * trustdb.c (do_update_trust_record,update_trust_record): Splitted.
+ (check_trust_record): New.
+ (check_trust,build_cert_tree): Check the dir record as needed.
+ (upd_pref_record): Removed.
+ (make_pref_record): New.
+ (propagate_validity): Stop as soon as we have enough validity.
+
+ * tbdio.c (MAX_CACHE_ENTRIES_HARD): Increased the limit.
+
+
+Fri Jul 2 11:45:54 CEST 1999 Werner Koch <wk@isil.d.shuttle.de>
+
+ * g10.c (g10_exit): Dump random stats.
+
+ * sig-check.c (check_key_signature,check_key_signature2): Enhanced
+ version and wrapper for old function.
+ (do_signature_check,signature_check): Ditto.
+
+Thu Jul 1 12:47:31 CEST 1999 Werner Koch <wk@isil.d.shuttle.de>
+
+
+ * keyedit.c (show_key_with_all_names): Print a notice for disabled keys.
+ (enable_disable_keys): Add functionality
+ * pkclist.c (edit_ownertrust): preserve disabled state.
+ (build_pk_list): Skip disabled keys.
+ * trustdb.c (upd_one_ownertrust): Ditto.
+ (build_cert_tree): Mask the ownertrust.
+ (trust_letter): Mask the value.
+ (do_check): Take disabled flag into account.
+
+ * passphrase.c (passphrase_to_dek): Add a pubkey_algo arg and changed
+ all callers.
+
+ * g10.c (utf8_strings): 2 new options.
+
+ * trustdb.c (insert_trust_record_by_pk): New, replaces the next one.
+ (insert_trust_record): Now takes a keyblock as arg. Changed all
+ callers to use the appropritae function.
+
+ * openfile.c (ask_outfile_name): New.
+ * plaintext.c (handle_plaintext): Ask for filename if there is
+ no valid syntax. Don't use fname varbatim but filter it.
+
+Tue Jun 29 21:44:25 CEST 1999 Werner Koch <wk@isil.d.shuttle.de>
+
+
+ * trustdb.h (TRUST_FLAG_DISABLED): New.
+
+ * status.c (USE_CAPABILITIES): Capabilities support (Remi).
+
+ * tdbio.c : Added new fields to the DIR record.
+ (tdbio_write_record): Fixed the update of the hash tables.
+ (tdbio_delete_record): Drop the record from the hash tables.
+ (drop_from_hashtbl): New.
+
+ * status.c (cpr_get): Special online help mode.
+ * helptext.c ("keyedit.cmd"): Removed.
+ * keyedit.c (keyedit_menu): Use only help system.
+ (enable_disable_key): New bit doies not yet work.
+
+Sat Jun 26 12:15:59 CEST 1999 Werner Koch <wk@isil.d.shuttle.de>
+
+
+ * dearmor.c (enarmor_file): Fixed comment string.
+ * tdbdump.c (export_ownertrust): Text fix.
+ * tbio.c (tdbio_invalid): Ditto.
+
+ * parse-packet.c (parse_key): Made temp buffer larger.
+
+ * Makefile.am (install-data-local): Add missing backslashes
+
+Tue Jun 15 12:21:08 CEST 1999 Werner Koch <wk@isil.d.shuttle.de>
+
+ * g10.c (main): Made iterated+salted the default S2K method.
+
+ * Makefile.am (install-data-local): Use DESTDIR.
+
+ * passphrase.c (passphrase_to_dek): Emit missing-passphrase while in
+ batchmode.
+
+ * parse-packet.c (parse_pubkeyenc): Fixed a SEGV.
+
+Mon Jun 14 21:18:54 CEST 1999 Michael Roth <mroth@nessie.de>
+
+ * g10.c: New options --openpgp, --no-tty, --emit-version,
+ --default-comment and --lock-multiple
+
+Thu Jun 10 14:18:23 CEST 1999 Werner Koch <wk@isil.d.shuttle.de>
+
+ * free-packet.c (free_encrypted): Fixed EOF case (Remi).
+ (free_plaintext): Ditto.
+
+ * helptext.c (keyedit.delsig.unknown): New (Remi).
+ * keyedit.c (print_and_check_one_sig): Add arg print_without_key and
+ changed all callers to make use of it (Remi):
+
+Tue Jun 8 13:36:25 CEST 1999 Werner Koch <wk@isil.d.shuttle.de>
+
+ * keylist.c (print_key_data): New and called elsewhere.
+ * g10.c: New option --with-key-data
+
+Wed Jun 2 14:17:19 CEST 1999 Werner Koch <wk@isil.d.shuttle.de>
+
+ * mainproc.c (proc_tree): Yet another bad hack to cope with
+ broken pgp2 created detached messages in textmode.
+
+Tue Jun 1 16:01:46 CEST 1999 Werner Koch <wk@isil.d.shuttle.de>
+
+ * openfile.c (make_outfile_name): New.
+ * plaintext.c (handle_plaintext): Outputfile is now the inputfile
+ without the suffix.
+ * g10.c: New option --use-embedded-filename
+
+Mon May 31 19:41:10 CEST 1999 Werner Koch <wk@isil.d.shuttle.de>
+
+ * g10.c (main): Fix for SHM init (Michael).
+
+ * compress.c, encr-data.c, mdfilter.c,
+ plaintext.c, free-packet.c: Speed patches (Rémi).
+
+Thu May 27 09:40:55 CEST 1999 Werner Koch <wk@isil.d.shuttle.de>
+
+ * status.c (cpr_get_answer_yes_no_quit): New.
+ * keyedit.c (menu_delsig): New.
+ (check_all_keysigs): Splitted.
+ (print_and_check_one_sig): New.
+
+Wed May 26 14:36:29 CEST 1999 Werner Koch <wk@isil.d.shuttle.de>
+
+ * build-packet.c (build_sig_subpkt): Support large packets.
+ * parse-packet.c (enum_sig_subpkt): Replaces parse_sig_subpkt.
+ * mainproc.c (print_notation_data): Print all notation packets.
+ * g10.c (add_notation_data): Add a way to specify the critical flag.
+ (main): Add option --set-policy-url.
+ (check_policy_url): Basic checks.
+ * sign.c (mk_notation_and_policy): Replaces mk_notation.
+
+ * parse-packet.c (can_handle_critical): Moved decision whether we can
+ handle critical subpacket to an extra function.
+
+Tue May 25 19:50:32 CEST 1999 Werner Koch <wk@isil.d.shuttle.de>
+
+ * sign.c (sign_file): Always use compression algo 1 for signed
+ onyl file becuase we can´ be sure the the verifier supports other
+ algorithms.
+
+ * build-packet.c (build_sig_subpkt): Support for notation data.
+ * sign.c (sign_file,clearsign_file,make_keysig_packet): Ditto.
+ (mk_notation): New.
+ * g10.c (add_notation_data): New and add option -N
+ * mainproc.c (print_notation_data): New.
+ (check_sig_and_print): Print any notation data of the signed text.
+
+Sun May 23 14:20:22 CEST 1999 Werner Koch <wk@isil.d.shuttle.de>
+
+ * pkclist.c (check_signatures_trust): Print a warning and return
+ immediateley if opt.always_trust is true.
+
+ * g10.c (main): Corrected handling of no-default-keyring
+
+ * pkclist.c (algo_available): Disable Twofish until we have settled
+ how to do the MDC.
+
+ * hkp.c: Disable everything for mingw32
+
+Sat May 22 22:47:26 CEST 1999 Werner Koch <wk@isil.d.shuttle.de>
+
+ * mainproc.c (check_sig_and_print): Add sig creation time to the
+ VALIDSIG status output. Add more info to the ERRSIG output.
+ * sig-check.c (signature_check): Add sig time after epoch to SIG_ID.
+
+ * import.c (import_one): Merge duplicate user IDs.
+ (collapse_uids): New.
+ * kbnode.c (move_kbnode): New.
+ (remove_kbnode): New.
+ * keyedit.c (keyedit_menu): Call collapse_uids.
+
+ * g10.c: new option --logger-fd.
+
+ * import.c: s/log_*_f/log_*/
+
+Thu May 20 14:04:08 CEST 1999 Werner Koch <wk@isil.d.shuttle.de>
+
+ * misc.c (pull_in_libs): do the volatile only for gcc
+
+ * sig-check (signature_check): Emit SIG_iD only for classes 0 and 1.
+
+ * armor.c (armor_filter): Add detection of PGP2 created clearsigs.
+ (fake_packet): A tab is not a WS for pgp2 - handle this.
+ * textfilter.c (len_without_trailing_chars): New.
+ (copy_clearsig_text): Add pgp2mode arg.
+ * sign.c (clearsign_file): pass old_style to the above fnc.
+
+
+Wed May 19 16:04:30 CEST 1999 Werner Koch <wk@isil.d.shuttle.de>
+
+ * g10.c: New option --interactive.
+
+ * mainproc.c (proc_plaintext): Add workaround for pgp2 bug
+ (do_check_sig): Ditto.
+ (proc_tree): Ditto.
+ * plaintext.c (do_hash): Ditto.
+ (hash_datafiles): Ditto, add an arg, changed all callers.
+ * mdfilter.c (md_filter): Add support for the alternate hash context.
+
+Mon May 17 21:54:43 CEST 1999 Werner Koch <wk@isil.d.shuttle.de>
+
+ * parse-packet.c (parse_encrypted): Support for PKT_ENCRYPTED_MDC.
+ * build-packet.c (do_encrypted_mdc): Ditto.
+ * cipher.c (write_header): Add mdc hashing.
+ (cipher_filter): write out the hash.
+ * mainproc.c (do_proc_packets): Add PKT_ENCRYPTED_MDC.
+ * encr-data.c (decrypt_data): Add mdc hashing.
+ (mdc_decode_filter): New.
+
+ * parse-packet.c (parse_sig_subpkt): Fixed stupid bug for subpkt
+ length calculation
+ (parse_signature): Fixed even more stupid bug.
+
+Sat May 8 19:28:08 CEST 1999 Werner Koch <wk@isil.d.shuttle.de>
+
+ * build-packet.c (do_signature): Removed MDC hack.
+ * encode.c (encode_crypt_mdc): Removed.
+ * mainproc.c (do_check_sig): Removed MDC hack.
+ (check_sig_and_print): Ditto.
+ * parse-packet.c (parse_signature): Ditto.
+ * sig-check.c (mdc_kludge_check): Ditto.
+ * free-packte.c (copy_signature, free_seckey_enc): Ditto.
+
+ * parse-packet.c (parse_signature,parse_key): Store data of
+ unknown algorithms with mpi_set_opaque inseatd of the old
+ faked data stuff.
+ (read_rest): Removed.
+ (read_rest2): Renamed to read_rest
+ * build-packet.c (write_fake_data): Use mpi_get_opaque.
+ * free-packet.c (cp_fake_data): Removed and cahnged all callers
+ to use mpi_copy.
+ (free_pubkey_enc,free_seckey_enc,release_public_key_parts,
+ release_secret_key_parts): Use mpi_free for opaque data.
+
+Thu May 6 14:18:17 CEST 1999 Werner Koch <wk@isil.d.shuttle.de>
+
+ * trustdb.c (check_trust): Check for revoked subkeys.
+ * pkclist.c (do_we_trust): Handled revoked subkeys.
+ (do_we_trust_pre): Ditto.
+ (check_signatures_trust): Ditto.
+
+ * build-packet.c (hash_public_key): Fix for ancient g10 keys.
+
+ * mainproc.c (do_proc_packets): Return EOF if no data has been read.
+ * g10.c (main): Catch errors for default operation.
+
+Thu Apr 29 12:29:22 CEST 1999 Werner Koch <wk@isil.d.shuttle.de>
+
+ * sign.c (sign_file): Fixed hashing in case of no subpackets.
+ (clearsign_file): Ditto.
+ (make_keysig_packet): Ditto.
+
+Wed Apr 28 13:03:03 CEST 1999 Werner Koch <wk@isil.d.shuttle.de>
+
+ * keyedit.c (keyedit_menu): Add new command revkey.
+ * (menu_revkey): New.
+
+
+Mon Apr 26 17:48:15 CEST 1999 Werner Koch <wk@isil.d.shuttle.de>
+
+ * parse-packet.c (parse_signature): Add the MDC hack.
+ * build-packet.c (do_signature): Ditto.
+ * free-packet.c (free_seckey_enc,copy_signature,cmp_signatures): Ditto.
+ * mainproc.c (do_check_sig): Ditto.
+ * sig-check.c (mdc_kludge_check): New.
+ * encode.c (encrypt_mdc_file): New.
+
+ * keyedit.c (check_all_keysigs): List revocations.
+ * (menu_revsig): New.
+ * sign (make_keysig_packet): Support for class 0x30.
+
+Sun Apr 18 20:48:15 CEST 1999 Werner Koch <wk@isil.d.shuttle.de>
+
+ * pkclist.c (select_algo_from_prefs): Fixed the case that one key
+ has no preferences (Remi Guyomarch).
+
+ keylist.c (list_keyblock): ulti_hack to propagate trust to all uids.
+
+Sun Apr 18 10:11:28 CEST 1999 Werner Koch <wk@isil.d.shuttle.de>
+
+ * seckey-cert.c (do_check): Use real IV instead of a 0 one, so that
+ it works even if the length of the IV doesn't match the blocksize.
+ Removed the save_iv stuff.
+ (protect_secret_key): Likewise. Create the IV here.
+ * packet.h (PKT_secret_key): Increased size of IV field and add a
+ ivlen field.
+ * parse-packet.c (parse_key): Use the len protect.ivlen.
+ * build-packet.c (do_secret_key). Ditto.
+
+ * getkey.c (key_byname): Close keyblocks.
+
+ * Makefile.am (gpgm): Removed this
+ * g10.c: Merged gpg and gpgm
+
+ * import.c (import): Utilize option quiet.
+ * tdbio.c (tdbio_set_dbname): Ditto.
+ * ringedit.c (add_keyblock_resource,keyring_copy): Ditto.
+
+ * keyedit.c (sign_uids): Add some batch support.
+
+ * g10.c (main): add call to tty_batchmode.
+
+Fri Apr 9 12:26:25 CEST 1999 Werner Koch <wk@isil.d.shuttle.de>
+
+ * status.c (write_status_text): Some more status codes.
+ * passphrase_to_dek (passphrase_to_dek): add a status code.
+ * seckey_cert.c (check_secret_key): Likewise.
+
+ * encr-data.c (decrypt_data): Reverse the last changes
+ * cipher.c (write_header): Ditto.
+
+ * parse-packet.c (parse_key): Dropped kludge for ancient blowfish mode.
+
+Thu Apr 8 09:35:53 CEST 1999 Werner Koch <wk@isil.d.shuttle.de>
+
+ * mainproc.c (proc_encrypted): Add a new status output
+ * passphrase.c (passphrase_to_dek): Ditto.
+ * status.h status.c: Add new status tokens.
+
+Wed Apr 7 20:51:39 CEST 1999 Werner Koch <wk@isil.d.shuttle.de>
+
+ * encr-data.c (decrypt_data): Fixes for 128 bit blocksize
+ * cipher.c (write_header): Ditto.
+ * seckey-cert.c (do_check): Ditto.
+ (protect_secret_key). Ditto.
+ * misc.c (print_cipher_algo_note): Twofish is now a standard algo.
+
+ * keygen.c (do_create): Fixed spelling (Gaël Quéri)
+ (ask_keysize): Only allow keysizes up to 4096
+
+ * ringedit.c (add_keyblock_resource): chmod newly created secrings.
+
+ * import.c (delete_inv_parts): Fixed accidently deleted subkeys.
+
+Tue Apr 6 19:58:12 CEST 1999 Werner Koch <wk@isil.d.shuttle.de>
+
+ * armor.c: Removed duped include (John Bley)
+ * mainproc.c: Ditto.
+
+ * build-packet.c (hash_public_key): Fixed hashing of the header.
+
+ * import.c (delete_inv_parts): Allow import of own non-exportable sigs.
+
+Sat Mar 20 13:59:47 CET 1999 Werner Koch <wk@isil.d.shuttle.de>
+
+ * armor.c (fake_packet): Fix for not not-dash-escaped
+
+Sat Mar 20 11:44:21 CET 1999 Werner Koch <wk@isil.d.shuttle.de>
+
+ * g10.c (main): Added command --recv-keys
+ * hkp.c (hkp_import): New.
+
+Wed Mar 17 13:09:03 CET 1999 Werner Koch <wk@isil.d.shuttle.de>
+
+ * trustdb.c (check_trust): add new arg add_fnc and changed all callers.
+ (do_check): Ditto.
+ (verify_key): Ditto.
+ (propagate_validity): Use the new add_fnc arg.
+ (print_user_id): Add the FILE arg.
+ (propagate_ownertrust): New.
+ * pkclist.c (add_ownertrust_cb): New and changed the add_ownertrust
+ logic.
+
+ * getkey.c (get_keyblock_bylid): New.
+ * trustdb.c (print_uid_from_keyblock): New.
+ (dump_tn_tree_with_colons): New.
+ (list_trust_path): Add colon print mode.
+
+ * trustdb.c (insert_trust_record): Always use the primary key.
+
+ * encode.c (encode_simple): Added text_mode filter (Rémi Guyomarch)
+ (encode_crypt): Ditto.
+
+ * mainproc.c (proc_pubkey_enc): Added status ENC_TO.
+ * armor.c (armor_filter): Added status NODATA.
+ * passphrase.c (passphrase_to_dek): Always print NEED_PASSPHRASE
+ * seckey_cert.c (check_secret_key): Added BAD_PASS status.
+
+ * g10.c (main): Set g10_opt_homedir.
+
+Sun Mar 14 19:34:36 CET 1999 Werner Koch <wk@isil.d.shuttle.de>
+
+ * keygen.c (do_create): Changed wording of the note (Hugh Daniel)
+
+Thu Mar 11 16:39:46 CET 1999 Werner Koch <wk@isil.d.shuttle.de>
+
+ * tdbdump.c: New
+
+ * trustdb.c (walk_sigrecs,do_list_sigs,list_sigs,
+ list_records,list_trustdb,export_ownertrust,import_ownertrust): Moved
+ to tdbdump.c
+ (init_trustdb): renamed to setup_trustdb. Changed all callers.
+ (do_init_trustdb): renamed to init_trustdb().
+ * trustdb.c (die_invalid_db): replaced by tdbio_invalid.
+ * tdbio.c (tdbio_invalid): New.
+
+ * import.c (delete_inv_parts): Skip non exportable signatures.
+ * keyedit.c (sign_uid_mk_attrib): New.
+ (sign_uids): Add the local argument.
+ (keyedit_menu): New "lsign" command.
+ * trustdb.c (register_trusted_key): Removed this and all related stuff.
+ * g10.c (oTrustedKey): Removed option.
+
+ * tdbio.h (dir.valcheck): New trustdb field.
+ * tdbio.c: Add support for this field
+ (tdbio_read_modify_stamp): New.
+ (tdbio_write_modify_stamp): New.
+ * trustdb.c (do_check): Check against this field. Removed cache update.
+ (verify_key): Add cache update.
+ (upd_uid_record): Some functional changes.
+ (upd_cert_record): Ditto
+
+Wed Mar 10 11:26:18 CET 1999 Werner Koch <wk@isil.d.shuttle.de>
+
+ * keylist.c (list_keyblock): Fixed segv in uid. Print 'u' as
+ validity of sks.
+
+Mon Mar 8 20:47:17 CET 1999 Werner Koch <wk@isil.d.shuttle.de>
+
+ * getkey.c (classify_user_id): Add new mode 12 (#<lid>).
+
+ * seckey-cert.c (check_secret_key): replaced error by info.
+
+ * trustdb.c (query_trust_info): Add another arg, changed all callers.
+ (check_trust): Ditto.
+ (do_check): Ditto.
+ (verify_key): Handle namehash.
+ * keylist.c (list_keyblock): print trust info for user ids.
+
+ * sig-check.c (signature_check): Add sig-created to status output.
+
+Tue Mar 2 16:44:57 CET 1999 Werner Koch <wk@isil.d.shuttle.de>
+
+ * textfilter.c (copy_clearsig_text): New.
+ (clearsign): Removed.
+ * sign.c (clearsign_file): does not use textfiler anymore.
+
+ * keygen.c (ask_user_id): print a note about the used charset.
+
+Tue Mar 2 10:38:42 CET 1999 Werner Koch <wk@isil.d.shuttle.de>
+
+ * sig-check.c (signature_check): sig-id now works for all algos.
+
+ * armor.c (armor_filter): Fixed armor bypassing.
+
+Sun Feb 28 19:11:00 CET 1999 Werner Koch <wk@isil.d.shuttle.de>
+
+ * keygen.c (ask_user_id): Don't change the case of email addresses.
+ (has_invalid_email_chars): Adjusted.
+
+ * keylist.c (list_one): Really list serect keys (Remi Guyomarch)
+
+ * keyedit.c (menu_select_uid): Add some braces to make egcs happy.
+ (menu_select_key): Ditto.
+
+ * mainproc.c (do_proc_packets): List sym-enc packets (Remi Guyomarch)
+
+Fri Feb 26 17:55:41 CET 1999 Werner Koch <wk@isil.d.shuttle.de>
+
+ * pkclist.c (build_pk_list): Return error if there are no recipients.
+
+ * sig-check.c (signature_check): New signature id feature.
+ * armor.c (make_radic64_string): New.
+
+ * mainproc.c (proc_pubkey_enc): early check for seckey availability.
+
+ * pkclist.c (do_we_trust_pre): print user id before asking.
+
+ * ringedit.c (add_keyblock_resource,get_keyblock_handle): Cleaner
+ handling of default resource.
+
+
+Thu Feb 25 18:47:39 CET 1999 Werner Koch <wk@isil.d.shuttle.de>
+
+ * pkclist.c (algo_available): New.
+ (select_algo_from_prefs): Check whether algo is available.
+
+ * ringedit.c (keyring_copy): Take care of opt.dry_run.
+ (do_gdbm_store): Ditto.
+ * openfile.c (open_outfile). Ditto.
+ (copy_options_file): Ditto.
+ * trustdb.c (update_trustdb): Ditto.
+ (clear_trust_checked_flag): Ditto.
+ (update_trust_record): Ditto.
+ (insert_trust_record): Ditto.
+
+Wed Feb 24 11:07:27 CET 1999 Werner Koch <wk@isil.d.shuttle.de>
+
+ * keylist.c (secret_key_list): Now really list the secret key.
+
+ * trustdb.c (do_init_trustdb): New. Init is now deferred.
+
+Mon Feb 22 20:04:00 CET 1999 Werner Koch <wk@isil.d.shuttle.de>
+
+ * getkey.c (lookup_sk): Return G10ERR_NO_SECKEY and not x_PUBKEY.
+
+Fri Feb 19 15:49:15 CET 1999 Werner Koch <wk@isil.d.shuttle.de>
+
+ * pkclist.c (select_algo_from_prefs): retrieve LID if not there.
+
+ * armor.c (fake_packet): Replaced ugly lineending handling.
+
+ * g10.c (oNoEncryptTo): New.
+ * pkclist.c (build_pk_list): Implemented this option.
+
+ * g10.c (main): Greeting is now printed to stderr and not to tty.
+ Use add_to_strlist() instead of direct coding.
+
+ * import.c (import): Use iobuf_push_filter2.
+
+ * mainproc.c (check_sig_and_print): Print all user ids
+ for good signatures.
+ * getkey.c (get_pubkeyblock): New.
+
+ * import.c (chk_self_sigs): Fixed SEGV for unbounded class 0x18 keys.
+ (delete_inv_parts): Delete special marked packets.
+
+Tue Feb 16 14:10:02 CET 1999 Werner Koch <wk@isil.d.shuttle.de>
+
+ * g10.c (main): New option --encrypt-to
+
+ * pkclist.c (build_pk_list): Implemented encrypt-to.
+
+ * parse-packet.c (parse_user_id): Removed the hack to work with
+ utf-8 strings.
+
+ * g10.c (main): Install lockfile cleanup handler.
+ * tdbio.c (cleanup): Removed: this is now handled by dotlock.
+
+Sat Feb 13 14:13:04 CET 1999 Werner Koch <wk@isil.d.shuttle.de>
+
+ * tdbio.c (tdbio_set_dbname): Init lockhandle for a new trustdb
+
+Wed Feb 10 17:15:39 CET 1999 Werner Koch <wk@isil.d.shuttle.de>
+
+ * g10.c (main): check for development version now in configure
+
+ * tdbio.c (tdbio_write_record): Add uid.validity
+ (tdbio_read_record) : Ditto.
+ (tdbio_dump_record) : Ditto.
+
+ * keygen.c (keygen_add_std_prefs): Replaced Blowfish by Twofish,
+ removed MD5 and Tiger.
+ * pubkey-enc.c (get_it): Suppress warning about missing Blowfish
+ in preferences in certain cases.
+
+ * ringedit.c (lock_rentry,unlock_rentry): New.
+
+ * getkey.c (key_byname): Pass ret_kb down to lookup_xx.
+
+ * armor.c (armor_filter): No output of of empty comment lines.
+ Add option --no-version to suppress the output of the version string.
+
+ * getkey.c: Release the getkey context for auto context variables.
+
+Sun Jan 24 18:16:26 CET 1999 Werner Koch <wk@isil.d.shuttle.de>
+
+ * getkey.c: Changed the internal design to allow simultaneous
+ lookup of multible user ids
+ (get_pubkey_bynames): New.
+ (get_seckey_bynames): New.
+ (get_seckey_next): New.
+ (get_seckey_end): New.
+ * keylist.c (list_one): Use the new functions.
+
+ * keylist.c (list_keyblock): add a newline for normal listings.
+
+ * g10.c (--recipient): New option name to replace --remote-user
+
+
+Wed Jan 20 18:59:49 CET 1999 Werner Koch <wk@isil.d.shuttle.de>
+
+ * textfilter.c: Mostly rewritten
+ * plaintext.c (handle_plaintext): Use now text_filter semantics.
+
+Tue Jan 19 19:34:58 CET 1999 Werner Koch <wk@isil.d.shuttle.de>
+
+ * export.c (export_pubkeys_stream): New.
+ (do_export_stream): New.
+ * g10.c (aSendKeys): New command.
+ * hkp.c (hkp_export): New.
+
+ * compress.c (do_uncompress): Hack for algo 1 and 1.1.3
+
+Sun Jan 17 11:04:33 CET 1999 Werner Koch <wk@isil.d.shuttle.de>
+
+ * textfilter.c (text_filter): Now uses iobuf_read_line().
+ (read_line): Removed.
+
+ * armor.c (trim_trailing_spaces): Removed and replaced
+ by trim_trailing_ws from libutil
+
+Sat Jan 16 12:03:27 CET 1999 Werner Koch <wk@isil.d.shuttle.de>
+
+ * hkp.c (hkp_ask_import): Use only the short keyid
+
+Sat Jan 16 09:27:30 CET 1999 Werner Koch <wk@isil.d.shuttle.de>
+
+ * import.c (import_key_stream): New
+ (import): New, moved most of import_keys here.
+ * g10.c: New option --keyserver
+ * mainproc.c (check_sig_and_print): Hook to import a pubkey.
+
+ * pref.c pref.h : Removed
+
+ * hkp.c hkp.h: New
+
+Wed Jan 13 14:10:15 CET 1999 Werner Koch <wk@isil.d.shuttle.de>
+
+ * armor.c (radix64_read): Print an error if a bad armor was detected.
+
+Wed Jan 13 12:49:36 CET 1999 Werner Koch <wk@isil.d.shuttle.de>
+
+ * armor.c (radix64_read): Now handles malformed armors produced
+ by some buggy MUAs.
+
+Tue Jan 12 11:17:18 CET 1999 Werner Koch <wk@isil.d.shuttle.de>
+
+ * ringedit.c (find_keyblock_bysk): New.
+
+ * skc_list.c (is_insecure): New.
+ (build_sk_list): usage check for insecure keys.
+
+ * import.c (chk_self_sigs): Add handling for subkeys.
+ (delete_inv_parts): Skip unsigned subkeys
+
+ * sig-check.c (do_check): Print info if the signature is older
+ than the key.
+ * keygen.c (generate_subkeypair): Fail on time warp.
+ * sign.c (do_sign): Ditto.
+
+Sun Jan 10 15:10:02 CET 1999 Werner Koch <wk@isil.d.shuttle.de>
+
+ * armor.c (fake_packet): Fixed not-dash-escaped bug.
+
+Sat Jan 9 16:02:23 CET 1999 Werner Koch <wk@isil.d.shuttle.de>
+
+ * sig-check.c (do_check): Output time diff on error
+
+ * status.c (STATUS_VALIDSIG): New.
+ (is_status_enabled): New.
+ * mainproc.c (check_sig_and_print): Issue that status message.
+
+ * plaintext.c (special_md_putc): Removed
+
+ * armor.c (armor_filter): print error for truncated lines.
+
+ * free-packet.c (free_encrypted): Revomed call to set_block_mode.
+ (free_plaintext): Ditto.
+
+Thu Jan 7 18:00:58 CET 1999 Werner Koch <wk@isil.d.shuttle.de>
+
+ * pkclist.c (add_ownertrust): Fixed return value.
+
+ * encr-data.c (decrypt_data): Disabled iobuf_set_limit and
+ iobuf_pop_filter stuff.
+ * compress.c (handle_compressed): Disabled iobuf_pop_filter.
+
+ * packet.h (PKT_secret_key): Add is_primary flag.
+ * parse-packet.c (parse_key): Set this flag.
+ * passphrase.c (passphrase_to_dek): Kludge to print the primary
+ keyid - changed the API: keyid must now hold 2 keyids.
+ * getkey.c (get_primary_seckey): New.
+ * seckey-cert.c (do_check): pass primary keyid to passphrase query
+
+ * tbdio.c (open_db): removed the atexit
+ (tdbio_set_dbname): and moved it to here.
+
+ * armor.c: Rewrote large parts.
+
+Tue Dec 29 19:55:38 CET 1998 Werner Koch <wk@isil.d.shuttle.de>
+
+ * revoke.c (gen_revoke): Removed compression.
+
+ * pkclist.c (do_we_trust_pre): special check for revoked keys
+
+ * trustdb.c (update_trust_record): Fixed revoke flag.
+
+Tue Dec 29 14:41:47 CET 1998 Werner Koch <wk@isil.d.shuttle.de>
+
+ * misc.c (disable_core_dumps): Check for EINVAL (Atari)
+
+ * getkey (merge_one_pk_and_selfsig): Fixed search of expiredate.
+ (merge_keys_and_selfsig): Ditto.
+
+ * free-packet.c (cmp_public_keys): cmp expire only for v3 packets
+ (cmp_secret_keys): Ditto.
+ (cmp_public_secret_key): Ditto.
+
+Wed Dec 23 17:12:24 CET 1998 Werner Koch <wk@isil.d.shuttle.de>
+
+ * armor.c (find_header): Reset not_dashed at every header
+
+Wed Dec 23 13:18:14 CET 1998 Werner Koch <wk@isil.d.shuttle.de>
+
+ * pkclist.c (add_ownertrust): Refresh validity values.
+
+ * trustdb.c (enum_cert_paths_print): New arg refresh.
+
+ * ringedit.c: Fixed problems fix keyrings
+ * parse-packet.c (dbg_parse_packet): New debug functions.
+
+ * getkey.c (getkey_disable_caches): New.
+ * import.c (import_keys): Disable caches.
+
+Thu Dec 17 18:31:15 CET 1998 Werner Koch <wk@isil.d.shuttle.de>
+
+ * misc.c (trap_unaligned): Only for glibc 1
+
+ * sign.c (write_dash_escaped): Now escapes "From " lines
+ * g10.c: New option --escape-from-lines
+
+ * trustdb.c (sort_tsl_list): New
+ (list_trust_path): Now prints sorted list.
+ (enum_cert_paths): Likewise.
+ (enum_cert_paths_print): New.
+ (print_paths): New printing format.
+ * pkclist.c (add_ownertrust): New arg quit.
+ (edit_ownertrust): New quit selection and does not query
+ the recipients ownertrust anymore.
+ (add_ownertrust): Print the ceritficate path.
+
+
+Mon Dec 14 21:18:49 CET 1998 Werner Koch <wk@isil.d.shuttle.de>
+
+ * parse-packet.c (parse_signature): Now checks for critical bit
+ (parse_sig_subpkt): Splitted.
+ (parse_one_sig_subpkt): New.
+ * sig-check.c (do_check): handle critical bit.
+
+Sun Dec 13 14:10:56 CET 1998 Werner Koch <wk@isil.d.shuttle.de>
+
+ * pcklist.c (select_algo_from_prefs): Preferences should
+ now work (lost the != ? )
+
+Thu Dec 10 20:15:36 CET 1998 Werner Koch <wk@isil.d.shuttle.de>
+
+ * ringedit.c (gdbm_store): Fix for inserts
+
+ * g10.c (main): New option --export-all
+ * export.c (export_pubkeys): New arg.
+ (do_export): Now may skip old keys.
+
+ * status.c: Minor patches for Sun's cc
+
+ * keygen.c (ask_algo): Disabled v3 ElGamal choice, rearranged
+ the numbers. Add a warning question when a sign+encrypt key
+ is selected.
+
+ * g10.c (do_not_use_RSA): Removed.
+ * misc.c (print_pubkey_algo_note): New as replacement for the
+ do_not_use_RSA() and chnaged all callers.
+ (print_cipher_algo_note): New.
+ (print_hash_algo_note): New.
+
+ * cipher.c (write_header): Add a call to print_cipher_algo_note.
+ * seckey-cert.c (protect_secret_key): Ditto
+ * sign.c (do_sign): Add a call to print_digest_algo_note.
+
+ * getkey.c (get_long_user_id_string): New.
+ * mainproc.c (check_sig_and_print): Changed the format of the
+ status output.
+
+ * encrypt.c (write_pubkey_enc_from_list): print used symmetric cipher.
+
+ * pkclist.c (do_we_trust): Changed a message.
+
+Wed Dec 9 13:41:06 CET 1998 Werner Koch <wk@isil.d.shuttle.de>
+
+ * misc.c (trap_unaligned) [ALPHA]: Only if UAC_SIGBUS is defined.
+
+ * sign.c (write_dash_escaped): Add the forgotten patch by Brian Moore.
+
+ * compress.c (do_uncompress): Fixed the inflating bug.
+
+
+Tue Dec 8 13:15:16 CET 1998 Werner Koch <wk@isil.d.shuttle.de>
+
+ * trustdb.c (upd_uid_record): Now uses the newest self-signature
+ (insert_trust_record): Now calls update with recheck set to true.
+ (register_trusted_key): New.
+ (verify_own_keys): Enhanced by list of trusted keys.
+
+ * g10.c (main): Print a warning when a devel version is used.
+ (main): New option --trusted-key
+
+ * import.c (merge_blocks): Fixed merging of new user ids and
+ added merging of subkeys.
+ (append_uid): Ditto.
+ (merge_keysig): New.
+ (append_key): New.
+ * getkey.c (merge_one_pk_and_selfsig): Get the expiration time
+ from the newest self-signature.
+ (merge_keys_and_selfsig): Ditto.
+
+ * free-packet.c (cmp_secret_key): New.
+
+
+Fri Nov 27 21:37:41 CET 1998 Werner Koch <wk@isil.d.shuttle.de>
+
+ * g10.c: New option --lock-once
+ * tdbio.c (open_db): Add an atexit
+ (cleanup): New.
+ (tdbio_sync): Add locking.
+ (tdbio_end_transaction): Ditto.
+ (put_record_into_cache): Ditto.
+ * ringedit.c (keyring_copy): Ditto.
+ (cleanup): New.
+ (add_keyblock_resource): Add an atexit.
+
+Fri Nov 27 15:30:24 CET 1998 Werner Koch <wk@isil.d.shuttle.de>
+
+ * armor.c (find_header): Another fix for clearsigs.
+
+Fri Nov 27 12:39:29 CET 1998 Werner Koch <wk@isil.d.shuttle.de>
+
+
+ * status.c (display_help): Removed.
+ * helptext.c: New and removed the N_() from all cpr_gets.
+
+
+Fri Nov 20 16:54:52 1998 Werner Koch (wk@isil.d.shuttle.de)
+
+ * g10.c (main): New option --not-dash-escaped
+ * sign.c (write_dashed_escaped): Ditto.
+ * armor.c (find_header): Support for NotDashEscaped header.
+
+ * getkey.c: print "disabled cache.." only if verbose is used.
+
+Thu Nov 19 07:17:31 1998 Werner Koch <werner.koch@guug.de>
+
+ * parse-packet.c (dump_sig_subpkt): Fixed expire listing
+ * getkey.c (merge_keys_and_selfsig): Fixed expire calculation.
+ (merge_one_pk_and_selfsig): Ditto.
+ * keyedit.c (menu_expire). Ditto.
+ * keygen.c (keygen_add_key_expire): Ditto.
+ (ask_expire_interval): New and changed all local function to use
+ this instead.
+ (keygen_add_key_expire): Opaque should now be a public key;
+ changed all callers.
+
+ * parse.packet.c (parse): use skip_rest to skip packets.
+
+ * keyedit.c (keyedit_menu): New arg for cmdline cmds.
+
+Wed Nov 18 20:33:50 1998 Werner Koch (wk@isil.d.shuttle.de)
+
+ * trustdb.c (check_trustdb): Now rechecks all gived userids.
+ (collect_paths): Some fixes.
+ (upd_pref_records): Skips empty items, evaluate all items.
+
+ * parse-packet.c (dump_sig_subpkt): Better listing of prefs.
+ (skip_packet): Now knows about marker packet
+
+ * g10.c: removed cmd "--edit-sig".
+
+ * pubring.asc: Updated.
+
+Sat Nov 14 14:01:29 1998 Werner Koch (wk@isil.d.shuttle.de)
+
+ * g10.c (main): Changed syntax of --list-trust-path
+ * trustdb.c (list_trust_path): Replaced max_depth by
+ opt.max_cert_depth
+
+Fri Nov 13 07:39:58 1998 Werner Koch <werner.koch@guug.de>
+
+ * trustdb.c (collect_paths): Removed a warning message.
+ (enum_trust_web): Removed.
+ (enum_cert_paths): New.
+ * pkclist.c (add_ownertrust): Changed to use enum_cert_paths.
+ (edit_ownertrust): Now list ceritficates on request.
+ (show_paths): New.
+
+Wed Nov 11 18:05:44 1998 Werner Koch <werner.koch@guug.de>
+
+ * g10.c (main): New option --max-cert-depth
+ * tdbio.h: add new fields to ver and dir record.
+ * tdbio.c: read/write/dump of these fields.
+ (tdbio_db_matches_options): New.
+ * trustdb.c: replaced MAC_CERT_DEPTH by opt.max_cert_depth.
+ (do_check): cache validity and changed other functions
+ to reset the cached value.
+
+ * keylist.c (list_one): Now lists the ownertrust.
+ * mainproc.c (list_node): Ditto.
+
+Tue Nov 10 10:08:59 1998 Werner Koch (wk@isil.d.shuttle.de)
+
+ * g10.c (g10_exit): Now looks at the new g10_errors_seen.
+ * mainproc.c (check_sig_and_print): Sets g10_errors_seen.
+
+ * *.c : i18n many more strings.
+
+ * ringedit.c (locate_keyblock_by_keyid): Add HAVE_LIBGDBM
+ (locate_keyblock_by_fpr): Ditto.
+
+ * g10.c (main): removed unsused "int errors".
+ (main): Add new option --charset.
+
+ * g10.c (main): special message for the unix newbie.
+
+Mon Nov 9 07:17:42 1998 Werner Koch <werner.koch@guug.de>
+
+ * getkey.c (finish_lookup): Kludge to prefere algo 16.
+
+ * trustdb.c (new_lid_table): Clear cached item.
+
+ * status.c (cpr_get_utf8): New.
+ * pkclist.c (build_pk_list): Uses this.
+
+Sun Nov 8 17:20:39 1998 Werner Koch (wk@isil.d.shuttle.de)
+
+ * mainproc.c (check_sig_and_print): Why did I use strlen()-1
+ in the printf? - This truncated the TZ.
+
+Sat Nov 7 15:57:28 1998 me,,, (wk@tobold)
+
+ * getkey.c (lookup): Changes to support a read_next.
+ (get_pubkey): Fixed a memory leak.
+
+ * keylist.c (list_one): Now lists all matching user IDs.
+
+Tue Nov 3 16:19:21 1998 Werner Koch (wk@isil.d.shuttle.de)
+
+ * keygen.c (ask_user_id): Now converted to UTF-8
+
+ * g10.c (main): Kludge for pgp clearsigs and textmode.
+
+Fri Oct 30 16:40:39 1998 me,,, (wk@tobold)
+
+ * signal.c (block_all_signals): New.
+ (unblock_all_signals): New
+ * tdbio.c (tdbio_end_transaction): Now blocks all signals.
+
+ * trustdb.c (new_lid_table): Changed the representation of the
+ former local_lid_info stuff.
+
+ * trustdb.c (update_trust_record): Reorganized the whole thing.
+ * sig-check.c (check_key_signature): Now handles class 0x28
+
+
+Wed Oct 28 18:56:33 1998 me,,, (wk@tobold)
+
+ * export.c (do_export): Takes care of the exportable sig flag.
+
+Tue Oct 27 14:53:04 1998 Werner Koch (wk@isil.d.shuttle.de)
+
+ * trustdb.c (update_trust_record): New "fast" parameter.
+
+Sun Oct 25 19:32:05 1998 Werner Koch (wk@isil.d.shuttle.de)
+
+ * openfile.c (copy_options_File): New.
+ * ringedit.c (add_keyblock_resource): Creates options file
+ * tdbio.c (tdbio_set_dbname): Ditto.
+
+Sat Oct 24 14:10:53 1998 brian moore <bem@cmc.net>
+
+ * mainproc.c (proc_pubkey_enc): Don't release the DEK
+ (do_proc_packets): Ditto.
+
+Fri Oct 23 06:49:38 1998 me,,, (wk@tobold)
+
+ * keyedit.c (keyedit_menu): Comments are now allowed
+
+ * trustdb.c: Rewrote large parts.
+
+
+Thu Oct 22 15:56:45 1998 Michael Roth (mroth@nessie.de)
+
+ * encode.c: (encode_simple): Only the plain filename without
+ a given directory is stored in generated packets.
+ (encode_crypt): Ditto.
+
+ * sign.c: (sign_file) Ditto.
+
+
+Thu Oct 22 10:53:41 1998 Werner Koch (wk@isil.d.shuttle.de)
+
+ * trustdb.c (update_trust_record): Add new optional arg.
+
+ * import.c (import_keys): Add statistics output
+ * trustdb.c (update_trustdb): Ditto.
+ (insert_trustdb): Ditto.
+
+ * tdbio.c (tdbio_begin_transaction): New.
+ (tdbio_end_transaction): New.
+ (tdbio_cancel_transaction): New.
+
+ * g10.c (main): New option --quit.
+
+ * trustdb.c (check_hint_sig): No tests for user-id w/o sig.
+ This caused an assert while checking the sigs.
+
+ * trustdb.c (upd_sig_record): Splitted into several functions.
+
+ * import.c (import_keys): New arg "fast".
+ * g10.c (main): New command --fast-import.
+
+Wed Oct 21 18:19:36 1998 Michael Roth <mroth@nessie.de>
+
+ * ringedit.c (add_keyblock_resource): Directory is now created.
+ * tdbio.c (tdbio_set_dbname): New info message.
+
+Wed Oct 21 11:52:04 1998 Werner Koch (wk@isil.d.shuttle.de)
+
+ * trustdb.c (update_trustdb): released keyblock in loop.
+
+ * keylist.c (list_block): New.
+ (list_all): Changed to use list_block.
+
+ * trustdb.c: Completed support for GDBM
+
+ * sign.c (only_old_style): Changed the way force_v3 is handled
+ (sign_file): Ditto.
+ (clearsign_file): Ditto.
+
+ * keygen.c (has_invalid_email_chars): Splitted into mailbox and
+ host part.
+
+ * keylist.c (list_one): Add a merge_keys_and_selfsig.
+ * mainproc.c (proc_tree): Ditto.
+
+Sun Oct 18 11:49:03 1998 Werner Koch (wk@isil.d.shuttle.de)
+
+ * sign.c (only_old_style): Add option force_v3_sigs
+ (sign_file): Fixed a bug in sig->version
+ (clearsign_file): Ditto.
+
+ * parse-packet.c (dump_sig_subpkt): New
+
+ * keyedit.c (menu_expire): New.
+ * free-packet.c (cmp_signatures): New
+
+
+Sat Oct 17 10:22:39 1998 Werner Koch (wk@isil.d.shuttle.de)
+
+ * armor.c: changed output line length from 72 to 64.
+
+ * keyedit.c (fix_keyblock): New.
+
+Fri Oct 16 10:24:47 1998 Werner Koch (wk@isil.d.shuttle.de)
+
+ * trustdb.c: Rewrote most.
+ * tdbio.c: Add cache and generalized hash tables.
+
+ * options.h (ENABLE_COMMENT_PACKETS): New but undef'ed.
+ * encode.c, sign.c, keygen.c: Disabled comment packets.
+ * export.c (do_export): Comment packets are never exported,
+ except for those in the secret keyring.
+
+ * g10.c (main): Removed option do-no-export-rsa; should be
+ be replaced by a secpial tool.
+ * export.c (do_export): Removed the code for the above option.
+
+ * armor.c (find_header): Support for new only_keyblocks.
+ * import.c (import_keys): Only looks for keyblock armors.
+
+ * packet.h: replaced valid_days by expiredate and changed all users.
+ * build-packet.c (do_public_key): calculates valid-days
+ (do_secret_key): Ditto.
+ * parse-packet.c (parse_key): expiredate is calucated from the
+ valid_period in v3 packets.
+ * keyid.c (do_fingerprint_md): calculates valid_dates.
+
+ * keygen.c (add_key_expire): fixed key expiration time for v4 packets.
+
+ * armor.c (find_header): A LF in the first 28 bytes
+ was skipped for non-armored data.
+
+Thu Oct 8 11:35:51 1998 Werner Koch (wk@isil.d.shuttle.de)
+
+ * armor.c (is_armored): Add test on old comment packets.
+
+ * tdbio.c (tdbio_search_dir_bypk): fixed memory leak.
+
+ * getkey.c: Changed the caching algorithms.
+
+Wed Oct 7 19:33:28 1998 Werner Koch (wk@isil.d.shuttle.de)
+
+ * kbnodes.c (unused_nodes): New.
+
+Wed Oct 7 11:15:36 1998 Werner Koch (wk@isil.d.shuttle.de)
+
+ * keyedit.c (sign_uids): Fixed a problem with SK which could caused
+ a save of an unprotected key.
+ (menu_adduid): Ditto.
+
+ * keyedit.c (keyedit_menu): Prefs are now correctly listed for
+ new user ids.
+
+ * trustdb.c (update_trust_record): New.
+ (insert_trust_record): Now makes use of update_trust_record.
+
+Tue Oct 6 16:18:03 1998 Werner Koch (wk@isil.d.shuttle.de)
+
+ * trustdb.c (read_record): replaces most of the tdbio_read_records.
+ (write_record): Ditto.
+
+Sat Oct 3 11:01:21 1998 Werner Koch (wk@isil.d.shuttle.de)
+
+ * keygen.c (ask_alogo): enable ElGamal enc-only only for addmode.
+
+Wed Sep 30 10:15:33 1998 Werner Koch (wk@isil.d.shuttle.de)
+
+ * import.c (import_one): Fixed update of wrong keyblock.
+
+Tue Sep 29 08:32:08 1998 me,,, (wk@tobold)
+
+ * mainproc.c (proc_plaintext): Display note for special filename.
+ * plaintext.c (handle_plaintext): Suppress output of special file.
+
+Mon Sep 28 12:57:12 1998 Werner Koch (wk@isil.d.shuttle.de)
+
+ * g10.c (verify_own_keys): Add warning if a key is not protected.
+
+ * passphrase (hash_passphrase): Fixed iterated+salted mode and
+ setup for keysizes > hashsize.
+
+ * g10.c (main): New options: --s2k-{cipher,digest,mode}.
+
+Fri Sep 25 09:34:23 1998 Werner Koch (wk@isil.d.shuttle.de)
+
+ * g10.c: Chnaged some help texts.
+
+Tue Sep 22 19:34:39 1998 Werner Koch (wk@isil.d.shuttle.de)
+
+ * passphrase.c (read_passphrase_from_fd): fixed bug for long
+ passphrases.
+
+Mon Sep 21 11:28:05 1998 Werner Koch (wk@(none))
+
+ * getkey.c (lookup): Add code to use the sub key if the primary one
+ does not match the usage.
+
+ * armor.c (armor_filter): New error message: no valid data found.
+ (radix64_read): Changes to support multiple messages.
+ (i18n.h): New.
+ * mainproc.c (add_onepass_sig): bug fix.
+
+Mon Sep 21 08:03:16 1998 Werner Koch (wk@isil.d.shuttle.de)
+
+ * pkclist.c (do_we_trust): Add keyid to most messages.
+
+ * passphrase.c (read_passphrase_from_fd): New.
+ (have_static_passphrase): New
+ (get_passphrase_fd): Removed.
+ (set_passphrase_fd): Removed.
+ * g10.c (main): passphrase is now read here.
+
+ * keyedit.c (keyedit_menu): "help" texts should now translate fine.
+
+Mon Sep 21 06:40:02 1998 Werner Koch (wk@isil.d.shuttle.de)
+
+ * encode.c (encode_simple): Now disables compression
+ when --rfc1991 is used.
+ (encode_crypt): Ditto.
+
+Fri Sep 18 16:50:32 1998 Werner Koch (wk@isil.d.shuttle.de)
+
+ * getkey.c (merge_key_and_selfsig): New.
+
+Fri Sep 18 10:20:11 1998 Werner Koch (wk@isil.d.shuttle.de)
+
+ * pkclist.c (select_algo_from_prefs): Removed 3DES kludge.
+
+ * seskey.c (make_session_key): Fixed SERIOUS bug introduced
+ by adding the weak key detection code.
+
+ * sign.c (sign_file): Changed aremor header in certain cases.
+
+Tue Sep 15 17:52:55 1998 Werner Koch (wk@isil.d.shuttle.de)
+
+ * mainproc.c (check_sig_and_print): Replaced ascime by asctimestamp.
+
+Mon Sep 14 11:40:52 1998 Werner Koch (wk@isil.d.shuttle.de)
+
+ * seskey.c (make_session_key): Now detects weak keys.
+
+ * trustdb (clear_trust_checked_flag): New.
+
+ * plaintext.c (handle_plaintext): Does no anymore suppress CR from
+ cleartext signed messages.
+
+Sun Sep 13 12:54:29 1998 Werner Koch (wk@isil.d.shuttle.de)
+
+ * trustdb.c (insert_trust_record): Fixed a stupid bug in the free
+ liunked list loops.
+
+Sat Sep 12 15:49:16 1998 Werner Koch (wk@isil.d.shuttle.de)
+
+ * status.c (remove_shmid): New.
+ (init_shm_comprocess): Now sets permission to the real uid.
+
+Wed Sep 9 11:15:03 1998 Werner Koch (wk@isil.d.shuttle.de)
+
+ * packet.h (PKT_pubkey_enc): New flah throw_keyid, and add logic to
+ implement it.
+ * g10.c (main): New Option --throw-keyid
+
+ * getkey.c (enum_secret_keys): Add new ar and changed all callers.
+
+Tue Sep 8 20:04:09 1998 Werner Koch (wk@isil.d.shuttle.de)
+
+ * delkey.c (delete_key): Moved from keyedit.c.
+
+Mon Sep 7 16:37:52 1998 Werner Koch (wk@isil.d.shuttle.de)
+
+ * build-packet.c (calc_length_header): New arg new_ctb to correctly
+ calculate the length of new style packets.
+
+ * armor.c (is_armored): Checks for symkey_enc packets.
+
+ * pkclist.c (select_algo_from_prefs): 3DEs substitute is now CAST5.
+
+Tue Aug 11 17:54:50 1998 Werner Koch (wk@isil.d.shuttle.de)
+
+ * build-packet.c (do_secret_key): Fixed handling of old keys.
+
+ * getkey.c (compare_name): Fixed exact and email matching
+
+ * openfile.c (open_outfile): Changed arguments and all callers.
+
+Tue Aug 11 09:14:35 1998 Werner Koch (wk@isil.d.shuttle.de)
+
+ * encode.c (encode_simple): Applied option set-filename and comment.
+ (encode_crypt): Ditto.
+ * sign.c (sign_file): Ditto.
+ * armor.c (armor_filter): Applied option comment.
+
+ * encode.c (encode_crypt): Moved init_packet to the begin.
+ (encode_simple): add an init_packet().
+
+ * comment (write_comment): Now enforces a hash sign as the 1st byte.
+
+ * import.c (import_one): Add explanation for "no user ids".
+
+ * compress.c (do_uncompress): Applied Brian Warner's patch to support
+ zlib 1.1.3 etc.
+
+ * trustdb.c (check_trust): Fixed a problem after inserting new keys.
+
+ * getkey (lookup): do not return the primary key if usage is given
+ (lookup_sk): Ditto and take usage into account.
+
+ * status.c (cpr_get_answer_is_yes): add display_help.
+
+Mon Aug 10 10:11:28 1998 Werner Koch (wk@isil.d.shuttle.de)
+
+ * getkey.c (lookup_sk): Now always returns the primary if arg
+ primary is true.
+ (lookup): Likewise.
+ (get_pubkey_byname): Now returns the primary key
+ (get_seckey_byname): Ditto.
+
+
+Mon Aug 10 08:34:03 1998 Werner Koch (wk@isil.d.shuttle.de)
+
+ * keyid.c (pubkey_letter): ELG_E is now a small g.
+
+Sat Aug 8 17:26:12 1998 Werner Koch (wk@isil.d.shuttle.de)
+
+ * openfile (overwrite_filep): Changed semantics and all callers.
+
+Sat Aug 8 12:17:07 1998 Werner Koch (wk@isil.d.shuttle.de)
+
+ * status.c (display_help): New.
+
+Thu Aug 6 16:30:41 1998 Werner Koch,mobil,,, (wk@tobold)
+
+ * seskey.c (encode_session_key): Now uses get_random_bits().
+
+Thu Aug 6 07:34:56 1998 Werner Koch,mobil,,, (wk@tobold)
+
+ * ringedit.c (keyring_copy): No more backupfiles for
+ secret keyrings and add additional warning in case of
+ a failed secret keyring operation.
+
+Wed Aug 5 11:54:37 1998 Werner Koch (wk@isil.d.shuttle.de)
+
+ * g10.c (check_opts): Moved to main. Changed def_cipher_algo
+ semantics and chnaged all users.
+
+ * pubkey-enc.c (get_sssion_key): New informational output
+ about preferences.
+
+ * parse-packet.c (parse_symkeyenc): Fixed salted+iterated S2K
+ (parse_key): Ditto.
+ * build-packet.c (do_secret_key): Ditto.
+ (do_symkey_enc): Ditto.
+
+Tue Aug 4 08:59:10 1998 Werner Koch (wk@isil.d.shuttle.de)
+
+ * getkey.c (enum_secret_keys): Now returns only primary keys.
+
+ * getkey (lookup): Now sets the new namehash field.
+
+ * parse-packet.c (parse_sig_subpkt2): New.
+
+ * sign.c (sign_file): one-pass sigs are now emiited reverse.
+ Preference data is considered when selecting the compress algo.
+
+Wed Jul 29 12:53:03 1998 Werner Koch (wk@isil.d.shuttle.de)
+
+ * free-packet.c (copy_signature): New.
+
+ * keygen.c (generate_subkeypair): rewritten
+ * g10.c (aKeyadd): Removed option --add-key
+
+Mon Jul 27 10:37:28 1998 Werner Koch (wk@isil.d.shuttle.de)
+
+ * seckey-cert.c (do_check): Additional check on cipher blocksize.
+ (protect_secret_key): Ditto.
+ * encr-data.c: Support for other blocksizes.
+ * cipher.c (write_header): Ditto.
+
+Fri Jul 24 16:47:59 1998 Werner Koch (wk@isil.d.shuttle.de)
+
+ * kbnode.c (insert_kbnode): Changed semantics and all callers.
+ * keyedit.c : More or less a complete rewrite
+
+Wed Jul 22 17:10:04 1998 Werner Koch (wk@isil.d.shuttle.de)
+
+ * build-packet.c (write_sign_packet_header): New.
+
+Tue Jul 21 14:37:09 1998 Werner Koch (wk@isil.d.shuttle.de)
+
+ * import.c (import_one): Now creates a trustdb record.
+
+ * g10.c (main): New command --check-trustdb
+
+Mon Jul 20 11:15:07 1998 Werner Koch (wk@isil.d.shuttle.de)
+
+ * genkey.c (generate_keypair): Default key is now DSA with
+ encryption only ElGamal subkey.
+
+Thu Jul 16 10:58:33 1998 Werner Koch (wk@isil.d.shuttle.de)
+
+ * keyid.c (keyid_from_fingerprint): New.
+ * getkey.c (get_pubkey_byfprint): New.
+
+Tue Jul 14 18:09:51 1998 Werner Koch (wk@isil.d.shuttle.de)
+
+ * keyid.c (fingerprint_from_pk): Add argument and changed all callers.
+ (fingerprint_from_sk): Ditto.
+
+Tue Jul 14 10:10:03 1998 Werner Koch (wk@isil.d.shuttle.de)
+
+ * plaintext.c (handle_plaintext): Now returns create error if
+ the file could not be created or the user responded not to overwrite
+ the file.
+ * mainproc.c (proc_plaintext): Tries again if the file could not
+ be created to check the signature without output.
+
+ * misc.c (disable_core_dumps): New.
+ * g10.c (main): disable coredumps for gpg
+
+ * g10.c (MAINTAINER_OPTIONS): New to disable some options
+
+Mon Jul 13 16:47:54 1998 Werner Koch (wk@isil.d.shuttle.de)
+
+ * plaintext.c (hash_datafiles): New arg for better support of
+ detached sigs. Changed all callers.
+ * mainproc.c (proc_signature_packets): Ditto.
+
+ * g10.c (main): New option "compress-sigs"
+ * sig.c (sign_file): detached signatures are not anymore compressed
+ unless the option --compress-sigs is used.
+
+Thu Jul 9 19:54:54 1998 Werner Koch (wk@isil.d.shuttle.de)
+
+ * armor.c: Fixes to allow zero length cleartext signatures
+
+Thu Jul 9 14:52:47 1998 Werner Koch (wk@isil.d.shuttle.de)
+
+ * g10.c (build_list): Now drops setuid.
+ (main): Changed the way keyrings and algorithms are registered .
+
+Wed Jul 8 14:17:30 1998 Werner Koch (wk@isil.d.shuttle.de)
+
+ * packet.h (PKT_public_key): Add field keyid.
+ * parse-packet.c (parse_key): Reset the above field.
+ * keyid.c (keyid_from_pk): Use above field as cache.
+
+ * tdbio.c, tdbio.h: New
+ * trustdb.c: Moved some functions to tdbio.c.
+ (print_keyid): New.
+
+ * pkclist.c (check_signatures_trust): New.
+
+Wed Jul 8 10:45:28 1998 Werner Koch (wk@isil.d.shuttle.de)
+
+ * plaintext.c (special_md_putc): New.
+ (handle_plaintext): add clearsig argument
+ * mainproc.c (proc_plaintext): detection of clearsig
+ * sign.c (write_dased_escaped): Changed clearsig format
+
+Tue Jul 7 18:56:19 1998 Werner Koch (wk@isil.d.shuttle.de)
+
+ * armor.c (find_header): Now makes sure that there is only one
+ empty line for clearsigs, as this is what OP now says.
+
+Mon Jul 6 13:09:07 1998 Werner Koch (wk@isil.d.shuttle.de)
+
+ * g10.c (main): New option default-secret-key
+ * getkey.c (get_seckey_byname): support for this option.
+
+Mon Jul 6 09:03:49 1998 Werner Koch (wk@isil.d.shuttle.de)
+
+ * getkey.c (add_keyring): Keyrings are now added to end of the
+ list of keyrings. The first added keyringwill be created.
+ (add_secret_keyring): Likewise.
+
+ * ringedit.c (add_keyblock_resource): Files are created here.
+
+ * g10.c (aNOP): Removed
+
+ * getkey.c (lookup): Add checking of usage for name lookups
+ * packet.h (pubkey_usage): Add a field which may be used to store
+ usage capabilities.
+ * pkclist.c (build_pk_list): getkey now called with usage arg.
+ * skclist.c (build_sk_list): Ditto.
+
+ * sign.c (clearsign_file): Fixed "Hash:" headers
+
+Sat Jul 4 13:33:31 1998 Werner Koch (wk@isil.d.shuttle.de)
+
+ * trustdb.c (list_ownertrust): New.
+ * g10.c (aListOwnerTrust): New.
+
+ * g10.c (def_pubkey_algo): Removed.
+
+ * trustdb.c (verify_private_data): Removed and also the call to it.
+ (sign_private_data): Removed.
+
+Fri Jul 3 13:26:10 1998 Werner Koch (wk@isil.d.shuttle.de)
+
+ * g10.c (aEditKey): was aEditSig. Changed usage msg.
+
+ * keyedit.c: Done some i18n stuff.
+
+ * g10.c (do_not_use_RSA): New.
+ * sign.c (do_sign): Add call to above function.
+ * encode.c (write_pubkey_enc_from_list): Ditto.
+
+Thu Jul 2 21:01:25 1998 Werner Koch (wk@isil.d.shuttle.de)
+
+ * parse-packet.c: Now is able sto store data of unknown
+ algorithms.
+ * free-packet.c: Support for this.
+ * build-packet.c: Can write data of packet with unknown algos.
+
+Thu Jul 2 11:46:36 1998 Werner Koch (wk@isil.d.shuttle.de)
+
+ * parse-packet.c (parse): fixed 4 byte length header
+
+Wed Jul 1 12:36:55 1998 Werner Koch (wk@isil.d.shuttle.de)
+
+ * packet.h (new_ctb): New field for some packets
+ * build-packet.c (build_packet): Support for new_ctb
+ * parse-packet.c (parse): Ditto.
+
+Mon Jun 29 12:54:45 1998 Werner Koch (wk@isil.d.shuttle.de)
+
+ * packet.h: changed all "_cert" to "_key", "subcert" to "subkey".
+
+ * free-packet.c (free_packet): Removed memory leak for subkeys.
+
+Sun Jun 28 18:32:27 1998 Werner Koch (wk@isil.d.shuttle.de)
+
+ * import.c (import_keys): Renamed from import_pubkeys.
+ (import_secret_one): New.
+
+ * g10.c (aExportSecret): New.
+
+ * export.c (export_seckeys): New.
+
+ * parse-packet.c (parse_certificate): Cleaned up.
+ (parse_packet): Trust packets are now considered as unknown.
+ (parse_pubkey_warning): New.
+
+Fri Jun 26 10:37:35 1998 Werner Koch (wk@isil.d.shuttle.de)
+
+ * keygen.c (has_invalid_email_chars): New.
+
+Wed Jun 24 16:40:22 1998 Werner Koch (wk@isil.d.shuttle.de)
+
+ * armor.c (armor_filter): Now creates valid onepass_sig packets
+ with all detected hash algorithms.
+ * mainproc.c (proc_plaintext): Now uses the hash algos as specified
+ in the onepass_sig packets (if there are any)
+
+Mon Jun 22 11:54:08 1998 Werner Koch (wk@isil.d.shuttle.de)
+
+ * plaintext.c (handle_plaintext): add arg to disable outout
+ * mainproc.c (proc_plaintext): disable output when in sigs_only mode.
+
+Thu Jun 18 13:17:27 1998 Werner Koch (wk@isil.d.shuttle.de)
+
+ * keygen.c: Removed all rsa packet stuff, chnaged defaults
+ for key generation.
+
+Sun Jun 14 21:28:31 1998 Werner Koch (wk@isil.d.shuttle.de)
+
+ * misc.c (checksum_u16): Fixed a stupid bug which caused a
+ wrong checksum calculation for the secret key protection and
+ add a backward compatibility option.
+ * g10.c (main): Add option --emulate-checksum-bug.
+
+Thu Jun 11 13:26:44 1998 Werner Koch (wk@isil.d.shuttle.de)
+
+ * packet.h: Major changes to the structure of public key material
+ which is now stored in an array and not anaymore in a union of
+ algorithm specific structures. These is needed to make the system
+ more extendable and makes a lot of stuff much simpler. Changed
+ all over the system.
+
+ * dsa.c, rsa.c, elg.c: Removed.
+
+Wed Jun 10 07:22:02 1998 Werner Koch,mobil,,, (wk@tobold)
+
+ * g10.c ("load-extension"): New option.
+
+Mon Jun 8 22:23:37 1998 Werner Koch (wk@isil.d.shuttle.de)
+
+ * seckey-cert.c (do_check): Removed cipher constants
+ (protect_secret_key): Ditto.
+
+Fri May 29 10:00:28 1998 Werner Koch (wk@isil.d.shuttle.de)
+
+ * trustdb.c (query_trust_info): New.
+ * keylist.c (list_one): Add output of trust info
+ * mainproc (list_node): ditto.
+ * g10.c (main): full trustdb init if -with-colons and any of the
+ key list modes.
+
+Thu May 28 10:34:42 1998 Werner Koch (wk@isil.d.shuttle.de)
+
+ * status.c (STATUS_RSA_OR_IDEA): New.
+ * sig-check.c (check_signature): Output special status message.
+ * pubkey-enc.c (get_session_key): Ditto.
+
+ * mainproc.c (check_sig_and_print): Changed format of output.
+ * passpharse.c (passphrase_to_dek): Likewise.
+
+Wed May 27 13:46:48 1998 Werner Koch (wk@isil.d.shuttle.de)
+
+ * g10.c (aListSecretKeys): New option --list-secret-keys
+ * keylist.c (std_key_list): Renamed to public_key_list.
+ (secret_key_list): New
+ (list_one, list_all): Add support for secret keys.
+ * getkey.c (get_secret_keyring): New.
+ * mainproc.c (list_node): Add option --with-colons for secret keys
+
+ * sig-check.c (check_key_signature): detection of selfsigs
+ * mainproc.c (list_node): fixed listing.
+
+ * g10.c (aListSecretKeys): New option --always-trust
+ * pkclist.c (do_we_trust): Override per option added
+
+ * status.c (write_status_text): Add a prefix to every output line.
+
+Wed May 27 07:49:21 1998 Werner Koch (wk@isil.d.shuttle.de)
+
+ * g10 (--compress-keys): New.
+ * options.h (compress_keys): New.
+ * export.c (export_pubkeys): Only compresses with the new option.
+
+Tue May 26 11:24:33 1998 Werner Koch (wk@isil.d.shuttle.de)
+
+ * passphrase.c (get_last_passphrase): New
+ (set_next_passphrase): New.
+ (passphrase_to_dek): add support for the above functions.
+ * keyedit.c (make_keysig_packet): Add sigclass 0x18,
+ changed all callers due to a new argument.
+ * keygen.c (write_keybinding): New
+ (generate_subkeypair): Add functionality
+ (ask_algo, ask_keysize, ask_valid_days): Broke out of generate_keypair
+ (ask_user_id, ask_passphrase): Ditto.
+
+Thu May 21 11:26:13 1998 Werner Koch (wk@isil.d.shuttle.de)
+
+ * g10.c,gpgd.c (main): Does now return an int, so that egcs does
+ not complain.
+
+ * armor.c (fake_packet): Removed erro message and add a noticed
+ that this part should be fixed.
+
+ * sign.c (sign_file): Compression now comes in front of encryption.
+ * encode.c (encode_simple): Ditto.
+ (encode_crypt): Ditto.
+
+Tue May 19 16:18:19 1998 Werner Koch (wk@isil.d.shuttle.de)
+
+ * armor.c (fake_packet): Changed assertion to log_error
+
+Sat May 16 16:02:06 1998 Werner Koch (wk@isil.d.shuttle.de)
+
+ * build-packet.c (build_packet): Add SUBKEY packets.
+
+Fri May 15 17:57:23 1998 Werner Koch (wk@isil.d.shuttle.de)
+
+ * sign.c (hash_for): New and used in all places here.
+ * main.h (DEFAULT_): new macros.
+ * g10.c (opt.def_digest_algo): Now set to 0
+
+ * compress.c (init_compress): Add support for algo 1
+ * options.h (def_compress_algo): New
+ * g10.c (main): New option --compress-algo
+
+Fri May 15 13:23:59 1998 Werner Koch (wk@isil.d.shuttle.de)
+
+ * g10.c (print_mds): New feature to print only one hash,
+ chnaged formatting.
+
+Thu May 14 15:36:24 1998 Werner Koch (wk@isil.d.shuttle.de)
+
+ * misc.c (trap_unaligned) [__alpha__]: New
+ * g10.c (trap_unaligned): Add call to this to track down SIGBUS
+ on Alphas (to avoid the slow emulation code).
+
+Wed May 13 11:48:27 1998 Werner Koch (wk@isil.d.shuttle.de)
+
+ * build-packet.c (do_signature): Support for v4 pakets.
+ * keyedit.c (make_keysig_packet): Ditto.
+ * build-packet.c (build_sig_subpkt_from_sig): New.
+ (build_sig_subpkt): New.
+
+ * elg.c (g10_elg_sign): removed keyid_from_skc.
+ * dsa.c (g10_dsa_sign): Ditto.
+ * rsa.c (g10_rsa_sign): Ditto.
+ * keyedit.c (make_keysig_packet): Add call to keyid_from_skc
+
+ * sign.c (clearsign_file): Support for v4 signatures.
+ (sign_file): Ditto.
+
+Wed May 6 09:31:24 1998 Werner Koch (wk@isil.d.shuttle.de)
+
+ * parse-packet.c (do_parse): add support for 5 byte length leader.
+ (parse_subpkt): Ditto.
+ * build-packet.c (write_new_header): Ditto.
+
+ * packet.h (SIGSUBPKT_): New constants.
+ * parse-packet.c (parse_sig_subpkt): Changed name, made global,
+ and arg to return packet length, chnaged all callers
+
+
+Tue May 5 22:11:59 1998 Werner Koch (wk@isil.d.shuttle.de)
+
+ * keygen.c (gen_dsa): New.
+ * build_packet.c (do_secret_cert): Support for DSA
+
+Mon May 4 19:01:25 1998 Werner Koch (wk@isil.d.shuttle.de)
+
+ * compress.c: doubled buffer sizes
+ * parse-packet.c (do_plaintext): now uses iobuf_read/write.
+
+Mon May 4 09:35:53 1998 Werner Koch (wk@isil.d.shuttle.de)
+
+ * seskey.c (encode_md_value): Add optional argument hash_algo,
+ changed all callers.
+
+ * passphrase.c (make_dek_from_passphrase): Removed
+ * (get_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 <https://www.gnu.org/licenses/>.
+
+## 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 <https://www.gnu.org/licenses/>.
+
+# cmacros.am - C macro definitions
+# Copyright (C) 2004 Free Software Foundation, Inc.
+#
+# This file is part of GnuPG.
+#
+# GnuPG is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# GnuPG is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses/>.
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+@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 <http://www.gnu.org/licenses/>.
+
+(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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <time.h>
+#ifdef HAVE_LOCALE_H
+#include <locale.h>
+#endif
+
+#include "gpg.h"
+#include <assuan.h>
+#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
+ * <keygrip> <type> <serialno> <idstr> <cached> <protection>
+ */
+ 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 <https://www.gnu.org/licenses/>.
+ */
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <time.h>
+#ifdef HAVE_LOCALE_H
+# include <locale.h>
+#endif
+
+#include "gpg.h"
+#include <assuan.h>
+#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 <https://www.gnu.org/licenses/>.
+ */
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#ifdef HAVE_LIBREADLINE
+# define GNUPG_LIBREADLINE_H_INCLUDED
+# include <readline/readline.h>
+#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 <https://www.gnu.org/licenses/>.
+ * SPDX-License-Identifier: GPL-3.0+
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <string.h>
+#include <stdio.h> /* Early versions of bzlib (1.0) require stdio.h */
+
+#include "gpg.h"
+#include "../common/util.h"
+#include <bzlib.h>
+
+#include "packet.h"
+#include "filter.h"
+#include "main.h"
+#include "options.h"
+
+/* Note that the code in compress.c is nearly identical to the code
+ here, so if you fix a bug here, look there to see if a matching bug
+ needs to be fixed. I tried to have one set of functions that could
+ do ZIP, ZLIB, and BZIP2, but it became dangerously unreadable with
+ #ifdefs and if(algo) -dshaw */
+
+static void
+init_compress( compress_filter_context_t *zfx, bz_stream *bzs )
+{
+ int rc;
+ int level;
+
+ if( opt.bz2_compress_level >= 1 && opt.bz2_compress_level <= 9 )
+ level = opt.bz2_compress_level;
+ else if( opt.bz2_compress_level == -1 )
+ level = 6; /* no particular reason, but it seems reasonable */
+ else
+ {
+ log_error("invalid compression level; using default level\n");
+ level = 6;
+ }
+
+ if((rc=BZ2_bzCompressInit(bzs,level,0,0))!=BZ_OK)
+ log_fatal("bz2lib problem: %d\n",rc);
+
+ zfx->outbufsize = 8192;
+ zfx->outbuf = xmalloc( zfx->outbufsize );
+}
+
+static int
+do_compress(compress_filter_context_t *zfx, bz_stream *bzs, int flush, IOBUF a)
+{
+ int 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 <https://www.gnu.org/licenses/>.
+ */
+
+/* Note that the code in compress-bz2.c is nearly identical to the
+ code here, so if you fix a bug here, look there to see if a
+ matching bug needs to be fixed. I tried to have one set of
+ functions that could do ZIP, ZLIB, and BZIP2, but it became
+ dangerously unreadable with #ifdefs and if(algo) -dshaw */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#ifdef HAVE_ZIP
+# include <zlib.h>
+# 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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#ifdef HAVE_SIGNAL_H
+# include <signal.h>
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#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 <https://www.gnu.org/licenses/>.
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+
+#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
--- /dev/null
+++ b/g10/distsigkey.gpg
Binary files 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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+/*
+ FIXME: We should replace most code in this module by our
+ spawn implementation from common/exechelp.c.
+ */
+
+
+#include <config.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#ifndef EXEC_TEMPFILE_ONLY
+#include <sys/wait.h>
+#endif
+#ifdef HAVE_DOSISH_SYSTEM
+# ifdef HAVE_WINSOCK2_H
+# include <winsock2.h>
+# endif
+# include <windows.h>
+#endif
+#include <fcntl.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+
+#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="<Wimp$ScrapDir>.GnuPG";
+ mkdir(tmp,0700); /* Error checks occur later on */
+#else
+ tmp="/tmp";
+#endif
+ }
+ }
+#endif
+ }
+
+ info->tempdir=xmalloc(strlen(tmp)+strlen(DIRSEP_S)+10+1);
+
+ sprintf(info->tempdir,"%s" DIRSEP_S "gpg-XXXXXX",tmp);
+
+#if defined (_WIN32)
+ xfree(tmp);
+#endif
+
+ if (!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 <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef _EXEC_H_
+#define _EXEC_H_
+
+#include <unistd.h>
+#include <stdio.h>
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+
+#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 "<foo@example.org>" 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 "<foo@example.org>" 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 <https://www.gnu.org/licenses/>.
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include <config.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <unistd.h>
+#ifdef HAVE_STAT
+#include <sys/stat.h> /* for stat() */
+#endif
+#include <fcntl.h>
+#ifdef HAVE_W32_SYSTEM
+# ifdef HAVE_WINSOCK2_H
+# include <winsock2.h>
+# endif
+# include <windows.h>
+#endif
+
+#define INCLUDED_BY_MAIN_MODULE 1
+#include "gpg.h"
+#include <assuan.h>
+#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;i<argc;i++)
+ {
+ len+=strlen(argv[i])+2;
+ str=xrealloc(str,len);
+ if(first)
+ {
+ str[0]='\0';
+ first=0;
+ }
+ else
+ strcat(str," ");
+
+ strcat(str,argv[i]);
+ }
+
+ return str;
+}
+
+
+#ifndef NO_TRUST_MODELS
+static void
+parse_trust_model(const char *model)
+{
+ if(ascii_strcasecmp(model,"pgp")==0)
+ opt.trust_model=TM_PGP;
+ else if(ascii_strcasecmp(model,"classic")==0)
+ opt.trust_model=TM_CLASSIC;
+ else if(ascii_strcasecmp(model,"always")==0)
+ opt.trust_model=TM_ALWAYS;
+ else if(ascii_strcasecmp(model,"direct")==0)
+ opt.trust_model=TM_DIRECT;
+#ifdef USE_TOFU
+ else if(ascii_strcasecmp(model,"tofu")==0)
+ opt.trust_model=TM_TOFU;
+ else if(ascii_strcasecmp(model,"tofu+pgp")==0)
+ opt.trust_model=TM_TOFU_PGP;
+#endif /*USE_TOFU*/
+ else if(ascii_strcasecmp(model,"auto")==0)
+ opt.trust_model=TM_AUTO;
+ else
+ log_error("unknown trust model '%s'\n",model);
+}
+#endif /*NO_TRUST_MODELS*/
+
+
+static int
+parse_tofu_policy (const char *policystr)
+{
+#ifdef USE_TOFU
+ struct { const char *keyword; int policy; } list[] = {
+ { "auto", TOFU_POLICY_AUTO },
+ { "good", TOFU_POLICY_GOOD },
+ { "unknown", TOFU_POLICY_UNKNOWN },
+ { "bad", TOFU_POLICY_BAD },
+ { "ask", TOFU_POLICY_ASK }
+ };
+ int i;
+
+ if (!ascii_strcasecmp (policystr, "help"))
+ {
+ log_info (_("valid values for option '%s':\n"), "--tofu-policy");
+ for (i=0; i < DIM (list); i++)
+ log_info (" %s\n", list[i].keyword);
+ g10_exit (1);
+ }
+
+ for (i=0; i < DIM (list); i++)
+ if (!ascii_strcasecmp (policystr, list[i].keyword))
+ return list[i].policy;
+#endif /*USE_TOFU*/
+
+ log_error (_("unknown TOFU policy '%s'\n"), policystr);
+ if (!opt.quiet)
+ log_info (_("(use \"help\" to list choices)\n"));
+ g10_exit (1);
+}
+
+
+static struct gnupg_compliance_option compliance_options[] =
+ {
+ { "gnupg", oGnuPG },
+ { "openpgp", oOpenPGP },
+ { "rfc4880bis", oRFC4880bis },
+ { "rfc4880", oRFC4880 },
+ { "rfc2440", oRFC2440 },
+ { "pgp6", oPGP6 },
+ { "pgp7", oPGP7 },
+ { "pgp8", oPGP8 },
+ { "de-vs", oDE_VS }
+ };
+
+
+/* Helper to set compliance related options. This is a separate
+ * function so that it can also be used by the --compliance option
+ * parser. */
+static void
+set_compliance_option (enum cmd_and_opt_values option)
+{
+ switch (option)
+ {
+ case oRFC4880bis:
+ opt.flags.rfc4880bis = 1;
+ /* fall through. */
+ case oOpenPGP:
+ case oRFC4880:
+ /* This is effectively the same as RFC2440, but with
+ "--enable-dsa2 --no-rfc2440-text --escape-from-lines
+ --require-cross-certification". */
+ opt.compliance = CO_RFC4880;
+ opt.flags.dsa2 = 1;
+ opt.flags.require_cross_cert = 1;
+ opt.rfc2440_text = 0;
+ opt.allow_non_selfsigned_uid = 1;
+ opt.allow_freeform_uid = 1;
+ opt.escape_from = 1;
+ opt.not_dash_escaped = 0;
+ opt.def_cipher_algo = 0;
+ opt.def_digest_algo = 0;
+ opt.cert_digest_algo = 0;
+ opt.compress_algo = -1;
+ opt.s2k_mode = 3; /* iterated+salted */
+ opt.s2k_digest_algo = DIGEST_ALGO_SHA1;
+ opt.s2k_cipher_algo = CIPHER_ALGO_3DES;
+ break;
+ case oRFC2440:
+ opt.compliance = CO_RFC2440;
+ opt.flags.dsa2 = 0;
+ opt.rfc2440_text = 1;
+ opt.allow_non_selfsigned_uid = 1;
+ opt.allow_freeform_uid = 1;
+ opt.escape_from = 0;
+ opt.not_dash_escaped = 0;
+ opt.def_cipher_algo = 0;
+ opt.def_digest_algo = 0;
+ opt.cert_digest_algo = 0;
+ opt.compress_algo = -1;
+ opt.s2k_mode = 3; /* iterated+salted */
+ opt.s2k_digest_algo = DIGEST_ALGO_SHA1;
+ opt.s2k_cipher_algo = CIPHER_ALGO_3DES;
+ break;
+ case oPGP6: opt.compliance = CO_PGP6; break;
+ case oPGP7: opt.compliance = CO_PGP7; break;
+ case oPGP8: opt.compliance = CO_PGP8; break;
+ case oGnuPG: opt.compliance = CO_GNUPG; break;
+
+ case oDE_VS:
+ set_compliance_option (oOpenPGP);
+ opt.compliance = CO_DE_VS;
+ /* We divert here from the backward compatible rfc4880 algos. */
+ opt.s2k_digest_algo = DIGEST_ALGO_SHA256;
+ opt.s2k_cipher_algo = CIPHER_ALGO_AES256;
+ break;
+
+ default:
+ BUG ();
+ }
+}
+
+
+static void
+gen_key_forbidden (void)
+{
+ write_status_failure ("gen-key", gpg_error (GPG_ERR_NOT_ENABLED));
+ log_error (_("This command is not allowed while in %s mode.\n"),
+ "forbid-gen-key");
+}
+
+
+/* This function called to initialized a new control object. It is
+ assumed that this object has been zeroed out before calling this
+ function. */
+static void
+gpg_init_default_ctrl (ctrl_t ctrl)
+{
+ ctrl->magic = 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 <user-id>");
+ 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 <user-id>");
+ 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 <user-ids>");
+ for( ; argc; argc--, argv++ ) {
+ username = make_username( *argv );
+ list_trust_path( username );
+ xfree(username);
+ }
+ break;
+
+ case aExportOwnerTrust:
+ if( argc )
+ wrong_args("--export-ownertrust");
+ export_ownertrust (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;i<n;i++,p++)
+ {
+ if(n==16)
+ {
+ if(count+2>79)
+ {
+ 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;i<strlen(string);i++)
+ if( !isascii (string[i]) || iscntrl(string[i]))
+ break;
+
+ if(i==0 || i<strlen(string))
+ {
+ if(which)
+ log_error(_("the given certification policy URL is invalid\n"));
+ else
+ log_error(_("the given signature policy URL is invalid\n"));
+ }
+
+ if(which)
+ sl=add_to_strlist( &opt.cert_policy_url, string );
+ else
+ sl=add_to_strlist( &opt.sig_policy_url, string );
+
+ if(critical)
+ sl->flags |= 1;
+}
+
+static void
+add_keyserver_url( const char *string, int which )
+{
+ unsigned int i,critical=0;
+ strlist_t sl;
+
+ if(*string=='!')
+ {
+ string++;
+ critical=1;
+ }
+
+ for(i=0;i<strlen(string);i++)
+ if( !isascii (string[i]) || iscntrl(string[i]))
+ break;
+
+ if(i==0 || i<strlen(string))
+ {
+ if(which)
+ BUG();
+ else
+ log_error(_("the given preferred keyserver URL is invalid\n"));
+ }
+
+ if(which)
+ BUG();
+ else
+ sl=add_to_strlist( &opt.sig_keyserver_url, string );
+
+ if(critical)
+ sl->flags |= 1;
+}
+
+
+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 <https://www.gnu.org/licenses/>.
+ */
+#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 <gpg-error.h>
+#include <gcrypt.h>
+
+
+/* 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 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
+<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
+<description>GNU Privacy Guard (OpenPGP tool)</description>
+<assemblyIdentity
+ type="win32"
+ name="GnuPG.gpg"
+ version="@BUILD_VERSION@"
+ />
+<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
+ <application>
+ <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/><!-- 10 -->
+ <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/><!-- 8.1 -->
+ <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/><!-- 8 -->
+ <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/><!-- 7 -->
+ <supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/><!-- Vista -->
+ </application>
+</compatibility>
+</assembly>
diff --git a/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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <errno.h>
+
+#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) <email@example.org>\"" },
+ { 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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef GNUPG_GPGSQL_H
+#define GNUPG_GPGSQL_H
+
+#include <sqlite3.h>
+
+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 <https://www.gnu.org/licenses/>.
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include <config.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <unistd.h>
+#ifdef HAVE_DOSISH_SYSTEM
+#include <fcntl.h> /* for setmode() */
+#endif
+#ifdef HAVE_LIBREADLINE
+#define GNUPG_LIBREADLINE_H_INCLUDED
+#include <readline/readline.h>
+#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 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
+<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
+<description>GNU Privacy Guard (OpenPGP verify tool)</description>
+<assemblyIdentity
+ type="win32"
+ name="GnuPG.gpgv"
+ version="@BUILD_VERSION@"
+ />
+<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
+ <application>
+ <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/><!-- 10 -->
+ <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/><!-- 8.1 -->
+ <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/><!-- 8 -->
+ <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/><!-- 7 -->
+ <supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/><!-- Vista -->
+ </application>
+</compatibility>
+</assembly>
diff --git a/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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#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;i<fprlen;i++,p++)
+ sprintf(username+2*i,"%02X",*p);
+ add_to_strlist(&locusr,username);
+
+ append_to_strlist(&sl,"updpref");
+ append_to_strlist(&sl,"save");
+
+ keyedit_menu (ctrl, username, locusr, sl, 1, 1 );
+ free_strlist(sl);
+ free_strlist(locusr);
+ }
+ else if(!opt.quiet)
+ log_info(_("you can update your preferences with:"
+ " gpg --edit-key %s updpref save\n"),keystr_from_pk(pk));
+ }
+}
+
+
+/* Helper for apply_*_filter in import.c and export.c. */
+const char *
+impex_filter_getval (void *cookie, const char *propname)
+{
+ /* FIXME: Malloc our static buffers and access them via PARM. */
+ struct impex_filter_parm_s *parm = cookie;
+ ctrl_t ctrl = parm->ctrl;
+ 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;idx<sig->numrevkeys;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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#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 <https://www.gnu.org/licenses/>.
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "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-0x10<opt.min_cert_level)
+ continue; /* treat anything under our min_cert_level as an
+ invalid signature */
+ if (klist && !is_in_klist (klist, sig))
+ continue; /* no need to check it then */
+ if ((rc=check_key_signature (ctrl, keyblock, node, NULL)))
+ {
+ /* we ignore anything that won't verify, but tag the
+ no_pubkey case */
+ if (gpg_err_code (rc) == GPG_ERR_NO_PUBKEY)
+ node->flag |= 1<<12;
+ continue;
+ }
+ node->flag |= 1<<9;
+ }
+ /* Reset the remaining flags. */
+ for (; node; node = node->next)
+ node->flag &= ~(1<<8 | 1<<9 | 1<<10 | 1<<11 | 1<<12);
+
+ /* kbnode flag usage: bit 9 is here set for signatures to consider,
+ * bit 10 will be set by the loop to keep track of keyIDs already
+ * processed, bit 8 will be set for the usable signatures, and bit
+ * 11 will be set for usable revocations. */
+
+ /* For each cert figure out the latest valid one. */
+ for (node=uidnode->next; node; node = node->next)
+ {
+ KBNODE n, signode;
+ u32 kid[2];
+ u32 sigdate;
+
+ if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY
+ || 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 <https://www.gnu.org/licenses/>.
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+#ifdef HAVE_LIBREADLINE
+# define GNUPG_LIBREADLINE_H_INCLUDED
+# include <readline/readline.h>
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#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; i<nsym; i++)
+ {
+ opt.personal_cipher_prefs[i].type = PREFTYPE_SYM;
+ opt.personal_cipher_prefs[i].value = sym[i];
+ }
+
+ opt.personal_cipher_prefs[i].type = PREFTYPE_NONE;
+ opt.personal_cipher_prefs[i].value = 0;
+ }
+ }
+ else if(personal==PREFTYPE_HASH)
+ {
+ xfree(opt.personal_digest_prefs);
+
+ if(nhash==0)
+ opt.personal_digest_prefs=NULL;
+ else
+ {
+ int i;
+
+ opt.personal_digest_prefs=
+ xmalloc(sizeof(prefitem_t *)*(nhash+1));
+
+ for (i=0; i<nhash; i++)
+ {
+ opt.personal_digest_prefs[i].type = PREFTYPE_HASH;
+ opt.personal_digest_prefs[i].value = hash[i];
+ }
+
+ opt.personal_digest_prefs[i].type = PREFTYPE_NONE;
+ opt.personal_digest_prefs[i].value = 0;
+ }
+ }
+ else if(personal==PREFTYPE_ZIP)
+ {
+ xfree(opt.personal_compress_prefs);
+
+ if(nzip==0)
+ opt.personal_compress_prefs=NULL;
+ else
+ {
+ int i;
+
+ opt.personal_compress_prefs=
+ xmalloc(sizeof(prefitem_t *)*(nzip+1));
+
+ for (i=0; i<nzip; i++)
+ {
+ opt.personal_compress_prefs[i].type = PREFTYPE_ZIP;
+ opt.personal_compress_prefs[i].value = zip[i];
+ }
+
+ opt.personal_compress_prefs[i].type = PREFTYPE_NONE;
+ opt.personal_compress_prefs[i].value = 0;
+ }
+ }
+ }
+ else
+ {
+ memcpy (sym_prefs, sym, (nsym_prefs=nsym));
+ memcpy (hash_prefs, hash, (nhash_prefs=nhash));
+ memcpy (zip_prefs, zip, (nzip_prefs=nzip));
+ mdc_available = mdc;
+ ks_modify = modify;
+ prefs_initialized = 1;
+ }
+ }
+
+ return rc;
+}
+
+/* Return a fake user ID containing the preferences. Caller must
+ free. */
+PKT_user_id *
+keygen_get_std_prefs(void)
+{
+ int i,j=0;
+ PKT_user_id *uid=xmalloc_clear(sizeof(PKT_user_id));
+
+ if(!prefs_initialized)
+ keygen_set_std_prefs(NULL,0);
+
+ uid->ref=1;
+
+ uid->prefs=xmalloc((sizeof(prefitem_t *)*
+ (nsym_prefs+nhash_prefs+nzip_prefs+1)));
+
+ for(i=0;i<nsym_prefs;i++,j++)
+ {
+ uid->prefs[j].type=PREFTYPE_SYM;
+ uid->prefs[j].value=sym_prefs[i];
+ }
+
+ for(i=0;i<nhash_prefs;i++,j++)
+ {
+ uid->prefs[j].type=PREFTYPE_HASH;
+ uid->prefs[j].value=hash_prefs[i];
+ }
+
+ for(i=0;i<nzip_prefs;i++,j++)
+ {
+ uid->prefs[j].type=PREFTYPE_ZIP;
+ uid->prefs[j].value=zip_prefs[i];
+ }
+
+ uid->prefs[j].type=PREFTYPE_NONE;
+ uid->prefs[j].value=0;
+
+ uid->flags.mdc=mdc_available;
+ uid->flags.ks_modify=ks_modify;
+
+ return uid;
+}
+
+static void
+add_feature_mdc (PKT_signature *sig,int enabled)
+{
+ const byte *s;
+ size_t n;
+ int i;
+ char *buf;
+
+ s = parse_sig_subpkt (sig->hashed, SIGSUBPKT_FEATURES, &n );
+ /* Already set or cleared */
+ if (s && n &&
+ ((enabled && (s[0] & 0x01)) || (!enabled && !(s[0] & 0x01))))
+ return;
+
+ if (!s || !n) { /* create a new one */
+ n = 1;
+ buf = xmalloc_clear (n);
+ }
+ else {
+ buf = xmalloc (n);
+ memcpy (buf, s, n);
+ }
+
+ if(enabled)
+ buf[0] |= 0x01; /* MDC feature */
+ else
+ buf[0] &= ~0x01;
+
+ /* Are there any bits set? */
+ for(i=0;i<n;i++)
+ if(buf[i]!=0)
+ break;
+
+ if(i==n)
+ delete_sig_subpkt (sig->hashed, SIGSUBPKT_FEATURES);
+ else
+ build_sig_subpkt (sig, SIGSUBPKT_FEATURES, buf, n);
+
+ xfree (buf);
+}
+
+static void
+add_keyserver_modify (PKT_signature *sig,int enabled)
+{
+ const byte *s;
+ size_t n;
+ int i;
+ char *buf;
+
+ /* The keyserver modify flag is a negative flag (i.e. no-modify) */
+ enabled=!enabled;
+
+ s = parse_sig_subpkt (sig->hashed, SIGSUBPKT_KS_FLAGS, &n );
+ /* Already set or cleared */
+ if (s && n &&
+ ((enabled && (s[0] & 0x80)) || (!enabled && !(s[0] & 0x80))))
+ return;
+
+ if (!s || !n) { /* create a new one */
+ n = 1;
+ buf = xmalloc_clear (n);
+ }
+ else {
+ buf = xmalloc (n);
+ memcpy (buf, s, n);
+ }
+
+ if(enabled)
+ buf[0] |= 0x80; /* no-modify flag */
+ else
+ buf[0] &= ~0x80;
+
+ /* Are there any bits set? */
+ for(i=0;i<n;i++)
+ if(buf[i]!=0)
+ break;
+
+ if(i==n)
+ delete_sig_subpkt (sig->hashed, SIGSUBPKT_KS_FLAGS);
+ else
+ build_sig_subpkt (sig, SIGSUBPKT_KS_FLAGS, buf, n);
+
+ xfree (buf);
+}
+
+
+int
+keygen_upd_std_prefs (PKT_signature *sig, void *opaque)
+{
+ (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; i<idx; i++)
+ {
+ gcry_mpi_release (array[i]);
+ array[i] = NULL;
+ }
+ gcry_sexp_release (list);
+ }
+ return rc;
+}
+
+
+/* Create a keyblock using the given KEYGRIP. ALGO is the OpenPGP
+ algorithm of that keygrip. */
+static int
+do_create_from_keygrip (ctrl_t ctrl, int algo, const char *hexkeygrip,
+ kbnode_t pub_root, u32 timestamp, u32 expireval,
+ int is_subkey)
+{
+ int err;
+ PACKET *pkt;
+ PKT_public_key *pk;
+ gcry_sexp_t s_key;
+ const char *algoelem;
+
+ if (hexkeygrip[0] == '&')
+ hexkeygrip++;
+
+ switch (algo)
+ {
+ case PUBKEY_ALGO_RSA: algoelem = "ne"; break;
+ case PUBKEY_ALGO_DSA: algoelem = "pqgy"; break;
+ case PUBKEY_ALGO_ELGAMAL_E: algoelem = "pgy"; break;
+ case PUBKEY_ALGO_ECDH:
+ case PUBKEY_ALGO_ECDSA: algoelem = ""; break;
+ case PUBKEY_ALGO_EDDSA: algoelem = ""; break;
+ default: return gpg_error (GPG_ERR_INTERNAL);
+ }
+
+
+ /* Ask the agent for the public key matching HEXKEYGRIP. */
+ {
+ unsigned char *public;
+
+ err = agent_readkey (ctrl, 0, hexkeygrip, &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;
+ }
+
+ /* Build a public key packet. */
+ 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;
+}
+
+
+/* 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(nbits<min || nbits>max)
+ 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"
+ " <n> = key expires in n days\n"
+ " <n>w = key expires in n weeks\n"
+ " <n>m = key expires in n months\n"
+ " <n>y = key expires in n years\n"));
+ break;
+
+ case 1:
+ if(!def_expire)
+ BUG();
+ tty_printf(_("Please specify how long the signature should be valid.\n"
+ " 0 = signature does not expire\n"
+ " <n> = signature expires in n days\n"
+ " <n>w = signature expires in n weeks\n"
+ " <n>m = signature expires in n months\n"
+ " <n>y = signature expires in n years\n"));
+ break;
+
+ default:
+ BUG();
+ }
+
+ /* Note: The elgamal subkey for DSA has no expiration date because
+ * it must be signed with the DSA key and this one has the expiration
+ * date */
+
+ answer = NULL;
+ for(;;)
+ {
+ u32 curtime;
+
+ 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) <heinrichh@duesseldorf.de>\"\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;i<MAX_FINGERPRINT_LEN && *pn;i++,pn+=2)
+ {
+ int c=hextobyte(pn);
+ if(c==-1)
+ goto fail;
+
+ revkey.fpr[i]=c;
+ }
+
+ /* skip to the tag */
+ while(*pn && *pn!='s' && *pn!='S')
+ pn++;
+
+ if(ascii_strcasecmp(pn,"sensitive")==0)
+ revkey.class|=0x40;
+
+ memcpy(&r->u.revkey,&revkey,sizeof(struct revocation_key));
+
+ return 0;
+
+ fail:
+ log_error("%s:%d: invalid revocation key\n", fname, r->lnr );
+ return -1; /* error */
+}
+
+
+static u32
+get_parameter_u32( struct para_data_s *para, enum para_name key )
+{
+ struct para_data_s *r = get_parameter( para, key );
+
+ if( !r )
+ return 0;
+ if( r->key == 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, &timestamp,
+ 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, &timestamp, 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, &timestamp, 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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <time.h>
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#ifdef HAVE_DOSISH_SYSTEM
+# include <fcntl.h> /* 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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef _KEYSERVER_INTERNAL_H_
+#define _KEYSERVER_INTERNAL_H_
+
+#include <time.h>
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+
+#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;i<numdesc;i++)
+ {
+ if(desc[i].skipfncvalue)
+ {
+ struct keyserver_spec *keyserver=desc[i].skipfncvalue;
+
+ if (!opt.quiet)
+ log_info (_("refreshing %d key from %s\n"), 1, keyserver->uri);
+
+ /* 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;i<srvcount;i++)
+ {
+ hostlen+=strlen(srvlist[i].target)+1;
+ keyserver->host=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 <https://www.gnu.org/licenses/>.
+ */
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#if defined(__linux__) && defined(__alpha__) && __GLIBC__ < 2
+#include <asm/sysinfo.h>
+#include <asm/unistd.h>
+#endif
+#ifdef HAVE_SETRLIMIT
+#include <time.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#endif
+#ifdef ENABLE_SELINUX_HACKS
+#include <sys/stat.h>
+#endif
+
+#ifdef HAVE_W32_SYSTEM
+#include <time.h>
+#include <process.h>
+#ifdef HAVE_WINSOCK2_H
+# define WIN32_LEAN_AND_MEAN 1
+# include <winsock2.h>
+#endif
+#include <windows.h>
+#include <shlobj.h>
+#ifndef CSIDL_APPDATA
+#define CSIDL_APPDATA 0x001a
+#endif
+#ifndef CSIDL_LOCAL_APPDATA
+#define CSIDL_LOCAL_APPDATA 0x001c
+#endif
+#ifndef CSIDL_FLAG_CREATE
+#define CSIDL_FLAG_CREATE 0x8000
+#endif
+#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+8<maxlen)
+ {
+ sprintf(&ret[idx],"%08lX",(ulong)sk_keyid[1]);
+ idx+=8;
+ done=1;
+ }
+ break;
+
+ case 'S': /* long key id */
+ if(idx+16<maxlen)
+ {
+ sprintf(&ret[idx],"%08lX%08lX",
+ (ulong)sk_keyid[0],(ulong)sk_keyid[1]);
+ idx+=16;
+ done=1;
+ }
+ break;
+
+ case 'k': /* short key id */
+ if(idx+8<maxlen)
+ {
+ sprintf(&ret[idx],"%08lX",(ulong)pk_keyid[1]);
+ idx+=8;
+ done=1;
+ }
+ break;
+
+ case 'K': /* long key id */
+ if(idx+16<maxlen)
+ {
+ sprintf(&ret[idx],"%08lX%08lX",
+ (ulong)pk_keyid[0],(ulong)pk_keyid[1]);
+ idx+=16;
+ done=1;
+ }
+ break;
+
+ case 'U': /* z-base-32 encoded user id hash. */
+ if (args->namehash)
+ {
+ 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+10<maxlen)
+ {
+ sprintf (&ret[idx],"%lu", get_signature_count (args->pksk));
+ 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)<maxlen)
+ {
+ for(i=0;i<len;i++)
+ {
+ sprintf(&ret[idx],"%02X",array[i]);
+ idx+=2;
+ }
+ done=1;
+ }
+ }
+ break;
+
+ case 'v': /* validity letters */
+ if(args->validity_info && idx+1<maxlen)
+ {
+ ret[idx++]=args->validity_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)<maxlen)
+ {
+ strcpy(&ret[idx],str);
+ idx+=strlen(str);
+ done=1;
+ }
+ }
+ break;
+
+ case '%':
+ if(idx+1<maxlen)
+ {
+ ret[idx++]='%';
+ ret[idx]='\0';
+ done=1;
+ }
+ break;
+
+ /* Any unknown %-keys (like %i, %o, %I, and %O) are
+ passed through for later expansion. Note this also
+ handles the case where the last character in the
+ string is a '%' - the terminating \0 will end up here
+ and properly terminate the string. */
+ default:
+ if(idx+2<maxlen)
+ {
+ ret[idx++]='%';
+ ret[idx++]=*(ch+1);
+ ret[idx]='\0';
+ done=1;
+ }
+ break;
+ }
+
+ if(done)
+ ch++;
+ }
+ else
+ {
+ if(idx+1<maxlen)
+ {
+ ret[idx++]=*ch;
+ ret[idx]='\0';
+ done=1;
+ }
+ }
+
+ if(done)
+ ch++;
+ }
+
+ return ret;
+
+ fail:
+ xfree(ret);
+ return NULL;
+}
+
+void
+deprecated_warning(const char *configname,unsigned int configlineno,
+ const char *option,const char *repl1,const char *repl2)
+{
+ if(configname)
+ {
+ if(strncmp("--",option,2)==0)
+ option+=2;
+
+ if(strncmp("--",repl1,2)==0)
+ repl1+=2;
+
+ log_info(_("%s:%d: deprecated option \"%s\"\n"),
+ configname,configlineno,option);
+ }
+ else
+ log_info(_("WARNING: \"%s\" is a deprecated option\n"),option);
+
+ log_info(_("please use \"%s%s\" instead\n"),repl1,repl2);
+}
+
+
+void
+deprecated_command (const char *name)
+{
+ log_info(_("WARNING: \"%s\" is a deprecated command - do not use it\n"),
+ name);
+}
+
+
+void
+obsolete_scdaemon_option (const char *configname, unsigned int configlineno,
+ const char *name)
+{
+ if (configname)
+ log_info (_("%s:%u: \"%s\" is obsolete in this file"
+ " - it only has effect in %s\n"),
+ configname, configlineno, name, SCDAEMON_NAME EXTSEP_S "conf");
+ else
+ log_info (_("WARNING: \"%s%s\" is an obsolete option"
+ " - it has no effect except on %s\n"),
+ "--", name, SCDAEMON_NAME);
+}
+
+
+/*
+ * Wrapper around gcry_cipher_map_name to provide a fallback using the
+ * "Sn" syntax as used by the preference strings.
+ */
+int
+string_to_cipher_algo (const char *string)
+{
+ int val;
+
+ val = map_cipher_gcry_to_openpgp (gcry_cipher_map_name (string));
+ if (!val && string && (string[0]=='S' || string[0]=='s'))
+ {
+ char *endptr;
+
+ string++;
+ val = strtol (string, &endptr, 10);
+ if (!*string || *endptr || openpgp_cipher_test_algo (val))
+ val = 0;
+ }
+
+ return val;
+}
+
+/*
+ * Wrapper around gcry_md_map_name to provide a fallback using the
+ * "Hn" syntax as used by the preference strings.
+ */
+int
+string_to_digest_algo (const char *string)
+{
+ int val;
+
+ /* FIXME: We should make use of our wrapper function and not assume
+ that there is a 1 to 1 mapping between OpenPGP and Libgcrypt. */
+ val = gcry_md_map_name (string);
+ if (!val && string && (string[0]=='H' || string[0]=='h'))
+ {
+ char *endptr;
+
+ string++;
+ val = strtol (string, &endptr, 10);
+ if (!*string || *endptr || openpgp_md_test_algo (val))
+ val = 0;
+ }
+
+ return val;
+}
+
+
+
+const char *
+compress_algo_to_string(int algo)
+{
+ const char *s=NULL;
+
+ switch(algo)
+ {
+ case COMPRESS_ALGO_NONE:
+ s=_("Uncompressed");
+ break;
+
+ case COMPRESS_ALGO_ZIP:
+ s="ZIP";
+ break;
+
+ case COMPRESS_ALGO_ZLIB:
+ s="ZLIB";
+ break;
+
+#ifdef HAVE_BZIP2
+ case COMPRESS_ALGO_BZIP2:
+ s="BZIP2";
+ break;
+#endif
+ }
+
+ return s;
+}
+
+int
+string_to_compress_algo(const char *string)
+{
+ /* TRANSLATORS: See doc/TRANSLATE about this string. */
+ if(match_multistr(_("uncompressed|none"),string))
+ return 0;
+ else if(ascii_strcasecmp(string,"uncompressed")==0)
+ return 0;
+ else if(ascii_strcasecmp(string,"none")==0)
+ return 0;
+ else if(ascii_strcasecmp(string,"zip")==0)
+ return 1;
+ else if(ascii_strcasecmp(string,"zlib")==0)
+ return 2;
+#ifdef HAVE_BZIP2
+ else if(ascii_strcasecmp(string,"bzip2")==0)
+ return 3;
+#endif
+ else if(ascii_strcasecmp(string,"z0")==0)
+ return 0;
+ else if(ascii_strcasecmp(string,"z1")==0)
+ return 1;
+ else if(ascii_strcasecmp(string,"z2")==0)
+ return 2;
+#ifdef HAVE_BZIP2
+ else if(ascii_strcasecmp(string,"z3")==0)
+ return 3;
+#endif
+ else
+ return -1;
+}
+
+int
+check_compress_algo(int algo)
+{
+ switch (algo)
+ {
+ case 0: return 0;
+#ifdef HAVE_ZIP
+ case 1:
+ case 2: return 0;
+#endif
+#ifdef HAVE_BZIP2
+ case 3: return 0;
+#endif
+ default: return GPG_ERR_COMPR_ALGO;
+ }
+}
+
+int
+default_cipher_algo(void)
+{
+ if(opt.def_cipher_algo)
+ return opt.def_cipher_algo;
+ else if(opt.personal_cipher_prefs)
+ return opt.personal_cipher_prefs[0].value;
+ else
+ return opt.s2k_cipher_algo;
+}
+
+/* There is no default_digest_algo function, but see
+ sign.c:hash_for() */
+
+int
+default_compress_algo(void)
+{
+ if(opt.compress_algo!=-1)
+ return opt.compress_algo;
+ else if(opt.personal_compress_prefs)
+ return opt.personal_compress_prefs[0].value;
+ else
+ return DEFAULT_COMPRESS_ALGO;
+}
+
+
+void
+compliance_failure(void)
+{
+ char *ver="???";
+
+ switch(opt.compliance)
+ {
+ case CO_GNUPG:
+ ver="GnuPG";
+ break;
+
+ case CO_RFC4880:
+ ver="OpenPGP";
+ break;
+
+ case CO_RFC2440:
+ ver="OpenPGP (older)";
+ break;
+
+ case CO_PGP6:
+ ver="PGP 6.x";
+ break;
+
+ case CO_PGP7:
+ ver="PGP 7.x";
+ break;
+
+ case CO_PGP8:
+ ver="PGP 8.x";
+ break;
+
+ case CO_DE_VS:
+ /* For de-vs we do not allow any kind of fallback. */
+ write_status_failure ("compliance-check", gpg_error (GPG_ERR_FORBIDDEN));
+ log_error (_("operation forced to fail due to"
+ " unfulfilled compliance rules\n"));
+ g10_errors_seen = 1;
+ return;
+ }
+
+ log_info(_("this message may not be usable by %s\n"),ver);
+ opt.compliance=CO_GNUPG;
+}
+
+/* Break a string into successive option pieces. Accepts single word
+ options and key=value argument options. */
+char *
+optsep(char **stringp)
+{
+ char *tok,*end;
+
+ tok=*stringp;
+ if(tok)
+ {
+ end=strpbrk(tok," ,=");
+ if(end)
+ {
+ int sawequals=0;
+ char *ptr=end;
+
+ /* what we need to do now is scan along starting with *end,
+ If the next character we see (ignoring spaces) is an =
+ sign, then there is an argument. */
+
+ while(*ptr)
+ {
+ if(*ptr=='=')
+ sawequals=1;
+ else if(*ptr!=' ')
+ break;
+ ptr++;
+ }
+
+ /* There is an argument, so grab that too. At this point,
+ ptr points to the first character of the argument. */
+ if(sawequals)
+ {
+ /* Is it a quoted argument? */
+ if(*ptr=='"')
+ {
+ ptr++;
+ end=strchr(ptr,'"');
+ if(end)
+ end++;
+ }
+ else
+ end=strpbrk(ptr," ,");
+ }
+
+ if(end && *end)
+ {
+ *end='\0';
+ *stringp=end+1;
+ }
+ else
+ *stringp=NULL;
+ }
+ else
+ *stringp=NULL;
+ }
+
+ return tok;
+}
+
+/* Breaks an option value into key and value. Returns NULL if there
+ is no value. Note that "string" is modified to remove the =value
+ part. */
+char *
+argsplit(char *string)
+{
+ char *equals,*arg=NULL;
+
+ equals=strchr(string,'=');
+ if(equals)
+ {
+ char *quote,*space;
+
+ *equals='\0';
+ arg=equals+1;
+
+ /* Quoted arg? */
+ quote=strchr(arg,'"');
+ if(quote)
+ {
+ arg=quote+1;
+
+ quote=strchr(arg,'"');
+ if(quote)
+ *quote='\0';
+ }
+ else
+ {
+ size_t spaces;
+
+ /* Trim leading spaces off of the arg */
+ spaces=strspn(arg," ");
+ arg+=spaces;
+ }
+
+ /* Trim tailing spaces off of the tag */
+ space=strchr(string,' ');
+ if(space)
+ *space='\0';
+ }
+
+ return arg;
+}
+
+/* Return the length of the initial token, leaving off any
+ argument. */
+static size_t
+optlen(const char *s)
+{
+ char *end=strpbrk(s," =");
+
+ if(end)
+ return end-s;
+ else
+ return strlen(s);
+}
+
+int
+parse_options(char *str,unsigned int *options,
+ struct parse_options *opts,int noisy)
+{
+ char *tok;
+
+ if (str && !strcmp (str, "help"))
+ {
+ int i,maxlen=0;
+
+ /* Figure out the longest option name so we can line these up
+ neatly. */
+ for(i=0;opts[i].name;i++)
+ if(opts[i].help && maxlen<strlen(opts[i].name))
+ maxlen=strlen(opts[i].name);
+
+ for(i=0;opts[i].name;i++)
+ if(opts[i].help)
+ es_printf("%s%*s%s\n",opts[i].name,
+ maxlen+2-(int)strlen(opts[i].name),"",_(opts[i].help));
+
+ g10_exit(0);
+ }
+
+ while((tok=optsep(&str)))
+ {
+ int i,rev=0;
+ char *otok=tok;
+
+ if(tok[0]=='\0')
+ continue;
+
+ if(ascii_strncasecmp("no-",tok,3)==0)
+ {
+ rev=1;
+ tok+=3;
+ }
+
+ for(i=0;opts[i].name;i++)
+ {
+ size_t toklen=optlen(tok);
+
+ if(ascii_strncasecmp(opts[i].name,tok,toklen)==0)
+ {
+ /* We have a match, but it might be incomplete */
+ if(toklen!=strlen(opts[i].name))
+ {
+ int j;
+
+ for(j=i+1;opts[j].name;j++)
+ {
+ if(ascii_strncasecmp(opts[j].name,tok,toklen)==0)
+ {
+ if(noisy)
+ log_info(_("ambiguous option '%s'\n"),otok);
+ return 0;
+ }
+ }
+ }
+
+ if(rev)
+ {
+ *options&=~opts[i].bit;
+ if(opts[i].value)
+ *opts[i].value=NULL;
+ }
+ else
+ {
+ *options|=opts[i].bit;
+ if(opts[i].value)
+ *opts[i].value=argsplit(tok);
+ }
+ break;
+ }
+ }
+
+ if(!opts[i].name)
+ {
+ if(noisy)
+ log_info(_("unknown option '%s'\n"),otok);
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+
+/* Similar to access(2), but uses PATH to find the file. */
+int
+path_access(const char *file,int mode)
+{
+ char *envpath;
+ int ret=-1;
+
+ envpath=getenv("PATH");
+
+ if(!envpath
+#ifdef HAVE_DRIVE_LETTERS
+ || (((file[0]>='A' && file[0]<='Z')
+ || (file[0]>='a' && file[0]<='z'))
+ && file[1]==':')
+#else
+ || file[0]=='/'
+#endif
+ )
+ return access(file,mode);
+ else
+ {
+ /* At least as large as, but most often larger than we need. */
+ char *buffer=xmalloc(strlen(envpath)+1+strlen(file)+1);
+ char *split,*item,*path=xstrdup(envpath);
+
+ split=path;
+
+ while((item=strsep(&split,PATHSEP_S)))
+ {
+ strcpy(buffer,item);
+ strcat(buffer,"/");
+ strcat(buffer,file);
+ ret=access(buffer,mode);
+ if(ret==0)
+ break;
+ }
+
+ xfree(path);
+ xfree(buffer);
+ }
+
+ return ret;
+}
+
+
+
+/* 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 <x> <y> */
+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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+#ifndef G10_OPTIONS_H
+#define G10_OPTIONS_H
+
+#include <sys/types.h>
+#include "../common/types.h"
+#include <stdint.h>
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#ifdef HAVE_LOCALE_H
+#include <locale.h>
+#endif
+#ifdef HAVE_LANGINFO_CODESET
+#include <langinfo.h>
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#ifdef _WIN32
+# ifdef HAVE_WINSOCK2_H
+# include <winsock2.h>
+# endif
+# include <windows.h>
+# 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;i<count;i++)
+ if(attrs[i].type==ATTRIB_IMAGE &&
+ parse_image_header(&attrs[i],&args.imagetype,&len))
+ {
+ char *command,*name;
+ struct exec_info *spawn;
+ int offset=attrs[i].len-len;
+
+#ifdef FIXED_PHOTO_VIEWER
+ opt.photo_viewer=FIXED_PHOTO_VIEWER;
+#else
+ if(!opt.photo_viewer)
+ opt.photo_viewer=get_default_photo_command();
+#endif
+
+ /* make command grow */
+ command=pct_expando(opt.photo_viewer,&args);
+ if(!command)
+ goto fail;
+ if (!*command)
+ {
+ xfree (command);
+ goto fail;
+ }
+
+ name=xmalloc(16+strlen(EXTSEP_S)+
+ strlen(image_type_to_string(args.imagetype,0))+1);
+
+ /* Make the filename. Notice we are not using the image
+ encoding type for more than cosmetics. Most external image
+ viewers can handle a multitude of types, and even if one
+ cannot understand a particular type, we have no way to know
+ which. The spec permits this, by the way. -dms */
+
+#ifdef USE_ONLY_8DOT3
+ sprintf(name,"%08lX" EXTSEP_S "%s",(ulong)kid[1],
+ image_type_to_string(args.imagetype,0));
+#else
+ sprintf(name,"%08lX%08lX" EXTSEP_S "%s",(ulong)kid[0],(ulong)kid[1],
+ image_type_to_string(args.imagetype,0));
+#endif
+
+ if(exec_write(&spawn,NULL,command,name,1,1)!=0)
+ {
+ xfree(name);
+ goto fail;
+ }
+
+#ifdef __riscos__
+ riscos_set_filetype_by_mimetype(spawn->tempfile_in,
+ image_type_to_string(args.imagetype,2));
+#endif
+
+ xfree(name);
+
+ fwrite(&attrs[i].data[offset],attrs[i].len-offset,1,spawn->tochild);
+
+ if(exec_read(spawn)!=0)
+ {
+ exec_finish(spawn);
+ goto fail;
+ }
+
+ if(exec_finish(spawn)!=0)
+ goto fail;
+ }
+
+ return;
+
+ fail:
+ log_error(_("unable to display photo ID!\n"));
+#endif /*!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 <https://www.gnu.org/licenses/>.
+ */
+
+/* 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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#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]<best
+ && (bits[i/32] & (1<<(i%32)))
+ && algo_available(preftype,i,hint))
+ {
+ best=scores[i];
+ result=i;
+ }
+ }
+ }
+
+ return result;
+}
+
+/*
+ * Select the MDC flag from the pk_list. We can only use MDC if all
+ * recipients support this feature.
+ */
+int
+select_mdc_from_pklist (PK_LIST pk_list)
+{
+ PK_LIST pkr;
+
+ if ( !pk_list )
+ return 0;
+
+ for (pkr = pk_list; pkr; pkr = pkr->next)
+ {
+ int mdc;
+
+ if (pkr->pk->user_id) /* selected by user ID */
+ mdc = pkr->pk->user_id->flags.mdc;
+ else
+ mdc = pkr->pk->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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/types.h>
+#ifdef HAVE_DOSISH_SYSTEM
+# include <fcntl.h> /* 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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <stdio.h>
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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:
+ * <algo-id>:<even-number-of-hex-digits>
+ */
+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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+
+#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;i<pk->numrevkeys;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;j<signode->pkt->pkt.signature->numrevkeys;j++)
+ {
+ if(pk->revkey[i].class==
+ signode->pkt->pkt.signature->revkey[j].class &&
+ pk->revkey[i].algid==
+ signode->pkt->pkt.signature->revkey[j].algid &&
+ memcmp(pk->revkey[i].fpr,
+ signode->pkt->pkt.signature->revkey[j].fpr,
+ MAX_FINGERPRINT_LEN)==0)
+ {
+ revkey=signode->pkt->pkt.signature;
+ break;
+ }
+ }
+ }
+ }
+
+ if(!revkey)
+ BUG();
+
+ rc=export_minimal_pk(out,keyblock,sig,revkey);
+ if(rc)
+ goto leave;
+
+ /* and issue a usage notice */
+ tty_printf(_("Revocation certificate created.\n"));
+ break;
+ }
+ }
+
+ if(!any)
+ log_error(_("no revocation keys found for \"%s\"\n"),uname);
+
+ leave:
+ 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 <https://www.gnu.org/licenses/>.
+ */
+
+/* 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 <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <ctype.h>
+#include <unistd.h>
+
+
+#include "gpg.h"
+#include <assuan.h>
+#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] <userID>
+ RECIPIENT [--hidden] --file <filename>
+
+ Set the recipient for the encryption. <userID> 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 <userID>
+
+ Set the signer's keys for the signature creation. <userID> should
+ be the internal representation of the key; the server may accept
+ any other way of specification. 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[=<n>]
+
+ 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 [<patterns>]
+ LISTSECRETKEYS [<patterns>]
+
+ 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 <what>
+
+ 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 <userID>\n"
+ "\n"
+ "Change the passphrase of the secret key for USERID.";
+static gpg_error_t
+cmd_passwd (assuan_context_t ctx, char *line)
+{
+ /* ctrl_t ctrl = assuan_get_pointer (ctx); */
+ gpg_error_t err;
+
+ (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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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 <x> <y> */
+ 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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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;i<pk->numrevkeys;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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#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
--- /dev/null
+++ b/g10/t-keydb-get-keyblock.gpg
Binary files differ
diff --git a/g10/t-keydb-keyring.kbx b/g10/t-keydb-keyring.kbx
new file mode 100644
index 0000000..a1d3af0
--- /dev/null
+++ b/g10/t-keydb-keyring.kbx
Binary files 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 <https://www.gnu.org/licenses/>.
+ */
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+/* 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 <config.h>
+#include <errno.h>
+#include <ctype.h>
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <unistd.h>
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+/* TODO:
+
+ - Format the fingerprints nicely when printing (similar to gpg
+ --list-keys)
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <sys/stat.h>
+#include <stdarg.h>
+#include <sqlite3.h>
+#include <time.h>
+
+#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 <fingerprint, email> 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 <key: %s, user id: %s>): %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"
+ " <key: %s, user id: %s> 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"
+ " <key: %s, user id: %s> 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 <key: %s, user id: %s> = %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:
+
+ <fingerprint, policy, time ago, count>. */
+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 <fingerprint,
+ email> (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 <email,
+ * fingerprint> 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 <key: %s, email: %s> 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 <FINGERPRINT, EMAIL>
+ * (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 <FINGERPRINT, EMAIL> 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 <key: %s, user id: %s>, 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 <key: %s, user id: %s> 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
+ * <FINGERPRINT, EMAIL> (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 <key: %s, user id: %s>'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 <key: %s, user id: %s>'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: <key: %s, user id: %s>",
+ 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 <fingerprint, USER_ID>,
+ 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"
+ " <key: %s, user id: %s>\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 <fingerprint, email, sig_time, sig_digest> is the
+ primary key! */
+ log_debug ("SIGNATURES DB contains duplicate records"
+ " <key: %s, email: %s, time: 0x%lx, sig: %s,"
+ " origin: %s>."
+ " 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"
+ " <key: %s, email: %s, time: 0x%lx, sig: %s,"
+ " origin: %s>\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"
+ " <key: %s, user id: %s, sig: %s>\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
+ <FINGERPRINT, USER_ID>, 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 <key: %s, user id: %s>: %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 <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef G10_TOFU_H
+#define G10_TOFU_H
+
+#include <config.h>
+
+/* 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 <PK, USER_ID>, 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 <PK, USER_ID>, 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 <PK,
+ USER_ID>. 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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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<len;start++)
+ {
+ if(!escaped && old[start]=='\\')
+ escaped=1;
+ else if (!escaped && strchr (REGEXP_OPERATOR_CHARS, old[start]))
+ new[idx++]='\\';
+ else
+ escaped=0;
+
+ new[idx++]=old[start];
+ }
+
+ new[idx]='\0';
+
+ /* Note that the (sub)string we look at might end with a bare "\".
+ If it does, leave it that way. If the regexp actually ended with
+ ">$", 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->ownertrust<min)
+ {
+ if(DBG_TRUST)
+ log_debug("key %08lX%08lX:"
+ " overriding ownertrust '%s' with '%s'\n",
+ (ulong)k->kid[0],(ulong)k->kid[1],
+ trust_value_to_string(k->ownertrust),
+ trust_value_to_string(min));
+
+ k->ownertrust=min;
+ }
+
+ if (k->ownertrust == TRUST_UNKNOWN)
+ ot_unknown++;
+ else if (k->ownertrust == TRUST_UNDEFINED)
+ ot_undefined++;
+ else if (k->ownertrust == TRUST_NEVER)
+ ot_never++;
+ else if (k->ownertrust == TRUST_MARGINAL)
+ ot_marginal++;
+ else if (k->ownertrust == TRUST_FULLY)
+ ot_full++;
+ else if (k->ownertrust == TRUST_ULTIMATE)
+ ot_ultimate++;
+
+ valids++;
+ }
+
+ /* Find all keys which are signed by a key in kdlist */
+ keys = validate_key_list (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 <https://www.gnu.org/licenses/>.
+ */
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#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 <file - normal
+ * 2. gpg file - normal (or detached)
+ * 3. gpg file <file2 - detached
+ * 4. gpg file file2 - detached
+ * The question is how decide between case 2 and 3? The only way
+ * we can do it is by reading one byte from stdin and then unget
+ * it; the problem here is that we may be reading from the
+ * terminal (which could be detected using isatty() but won't work
+ * when under contol of a pty using program (e.g. expect)) and
+ * might get us in trouble when stdin is used for another purpose
+ * (--passphrase-fd 0). So we have to break with the behaviour
+ * prior to gpg 1.0.4 by assuming that case 3 is a normal
+ * signature (where file2 is ignored and require for a detached
+ * signature to indicate signed material comes from stdin by using
+ * case 4 with a file2 of "-".
+ *
+ * Actually we don't have to change anything here but can handle
+ * that all quite easily in mainproc.c
+ */
+
+ sigfile = nfiles? *files : NULL;
+
+ /* open the signature file */
+ fp = iobuf_open(sigfile);
+ if (fp && is_secured_file (iobuf_get_fd (fp)))
+ {
+ iobuf_close (fp);
+ fp = NULL;
+ gpg_err_set_errno (EPERM);
+ }
+ if( !fp ) {
+ rc = gpg_error_from_syserror ();
+ log_error(_("can't open '%s': %s\n"),
+ print_fname_stdin(sigfile), gpg_strerror (rc));
+ goto leave;
+ }
+ handle_progress (pfx, fp, sigfile);
+
+ if ( !opt.no_armor && use_armor_filter( fp ) )
+ {
+ afx = new_armor_context ();
+ push_armor_filter (afx, fp);
+ }
+
+ sl = NULL;
+ for(i=nfiles-1 ; i > 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;
+}