diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 16:14:06 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 16:14:06 +0000 |
commit | eee068778cb28ecf3c14e1bf843a95547d72c42d (patch) | |
tree | 0e07b30ddc5ea579d682d5dbe57998200d1c9ab7 /g10/sign.c | |
parent | Initial commit. (diff) | |
download | gnupg2-eee068778cb28ecf3c14e1bf843a95547d72c42d.tar.xz gnupg2-eee068778cb28ecf3c14e1bf843a95547d72c42d.zip |
Adding upstream version 2.2.40.upstream/2.2.40upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | g10/sign.c | 1819 |
1 files changed, 1819 insertions, 0 deletions
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; +} |