diff options
Diffstat (limited to '')
-rw-r--r-- | g10/sign.c | 1358 |
1 files changed, 1358 insertions, 0 deletions
diff --git a/g10/sign.c b/g10/sign.c new file mode 100644 index 000000000..73286fcb3 --- /dev/null +++ b/g10/sign.c @@ -0,0 +1,1358 @@ +/* sign.c - sign data + * Copyright (C) 1998, 1999, 2000, 2001, 2002, + * 2003 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <assert.h> +#include <unistd.h> /* need sleep() */ + +#include "options.h" +#include "packet.h" +#include "errors.h" +#include "iobuf.h" +#include "keydb.h" +#include "memory.h" +#include "util.h" +#include "main.h" +#include "filter.h" +#include "ttyio.h" +#include "trustdb.h" +#include "status.h" +#include "i18n.h" + + +#ifdef HAVE_DOSISH_SYSTEM +#define LF "\r\n" +void __stdcall Sleep(ulong); +#define sleep(a) Sleep((a)*1000) +#else +#define LF "\n" +#endif + +static int recipient_digest_algo=0; + +/**************** + * Create a notation. 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_and_policy( PKT_signature *sig, + PKT_public_key *pk, PKT_secret_key *sk ) +{ + const char *string; + char *s=NULL; + byte *buf; + unsigned n1, n2; + STRLIST nd=NULL,pu=NULL; + struct expando_args args; + + memset(&args,0,sizeof(args)); + args.pk=pk; + args.sk=sk; + + /* notation data */ + if(IS_SIG(sig) && opt.sig_notation_data) + { + if(sig->version<4) + log_info("can't put notation data into v3 signatures\n"); + else + nd=opt.sig_notation_data; + } + else if( IS_CERT(sig) && opt.cert_notation_data ) + { + if(sig->version<4) + log_info("can't put notation data into v3 key signatures\n"); + else + nd=opt.cert_notation_data; + } + + for( ; nd; nd = nd->next ) { + char *expanded; + + string = nd->d; + s = strchr( string, '=' ); + if( !s ) + BUG(); /* we have already parsed this */ + n1 = s - string; + s++; + + expanded=pct_expando(s,&args); + if(!expanded) + { + log_error(_("WARNING: unable to %%-expand notation " + "(too large). Using unexpanded.\n")); + expanded=m_strdup(s); + } + + n2 = strlen(expanded); + buf = m_alloc( 8 + n1 + n2 ); + buf[0] = 0x80; /* human readable */ + buf[1] = buf[2] = buf[3] = 0; + buf[4] = n1 >> 8; + buf[5] = n1; + buf[6] = n2 >> 8; + buf[7] = n2; + memcpy(buf+8, string, n1 ); + memcpy(buf+8+n1, expanded, n2 ); + build_sig_subpkt( sig, SIGSUBPKT_NOTATION + | ((nd->flags & 1)? SIGSUBPKT_FLAG_CRITICAL:0), + buf, 8+n1+n2 ); + m_free(expanded); + m_free(buf); + } + + if(opt.list_options&LIST_SHOW_NOTATION) + show_notation(sig,0,0); + + /* set policy URL */ + if( IS_SIG(sig) && opt.sig_policy_url ) + { + if(sig->version<4) + log_info("can't put a policy URL into v3 signatures\n"); + else + pu=opt.sig_policy_url; + } + else if( IS_CERT(sig) && opt.cert_policy_url ) + { + if(sig->version<4) + log_info("can't put a policy URL into v3 key signatures\n"); + else + pu=opt.cert_policy_url; + } + + for(;pu;pu=pu->next) + { + string = pu->d; + + s=pct_expando(string,&args); + if(!s) + { + log_error(_("WARNING: unable to %%-expand policy url " + "(too large). Using unexpanded.\n")); + s=m_strdup(string); + } + + build_sig_subpkt(sig,SIGSUBPKT_POLICY| + ((pu->flags & 1)?SIGSUBPKT_FLAG_CRITICAL:0), + s,strlen(s)); + + m_free(s); + } + + if(opt.list_options&LIST_SHOW_POLICY) + show_policy_url(sig,0,0); +} + + +/* + * Helper to hash a user ID packet. + */ +static void +hash_uid (MD_HANDLE md, int sigversion, const PKT_user_id *uid) +{ + if ( sigversion >= 4 ) { + byte buf[5]; + + if(uid->attrib_data) { + buf[0] = 0xd1; /* indicates an attribute packet */ + buf[1] = uid->attrib_len >> 24; /* always use 4 length bytes */ + buf[2] = uid->attrib_len >> 16; + buf[3] = uid->attrib_len >> 8; + buf[4] = uid->attrib_len; + } + else { + buf[0] = 0xb4; /* indicates a userid packet */ + buf[1] = uid->len >> 24; /* always use 4 length bytes */ + buf[2] = uid->len >> 16; + buf[3] = uid->len >> 8; + buf[4] = uid->len; + } + md_write( md, buf, 5 ); + } + + if(uid->attrib_data) + md_write (md, uid->attrib_data, uid->attrib_len ); + else + md_write (md, uid->name, uid->len ); +} + + +/* + * Helper to hash some parts from the signature + */ +static void +hash_sigversion_to_magic (MD_HANDLE md, const PKT_signature *sig) +{ + if (sig->version >= 4) + md_putc (md, sig->version); + md_putc (md, sig->sig_class); + if (sig->version < 4) { + u32 a = sig->timestamp; + md_putc (md, (a >> 24) & 0xff ); + md_putc (md, (a >> 16) & 0xff ); + md_putc (md, (a >> 8) & 0xff ); + md_putc (md, a & 0xff ); + } + else { + byte buf[6]; + size_t n; + + md_putc (md, sig->pubkey_algo); + md_putc (md, sig->digest_algo); + if (sig->hashed) { + n = sig->hashed->len; + md_putc (md, (n >> 8) ); + md_putc (md, n ); + md_write (md, sig->hashed->data, n ); + n += 6; + } + else { + md_putc (md, 0); /* always hash the length of the subpacket*/ + md_putc (md, 0); + n = 6; + } + /* add some magic */ + buf[0] = sig->version; + buf[1] = 0xff; + buf[2] = n >> 24; /* hmmm, n is only 16 bit, so this is always 0 */ + buf[3] = n >> 16; + buf[4] = n >> 8; + buf[5] = n; + md_write (md, buf, 6); + } +} + + +static int +do_sign( PKT_secret_key *sk, PKT_signature *sig, + MD_HANDLE md, int digest_algo ) +{ + MPI frame; + byte *dp; + int rc; + + if( sk->timestamp > sig->timestamp ) { + ulong d = sk->timestamp - sig->timestamp; + log_info( d==1 ? _("key has been created %lu second " + "in future (time warp or clock problem)\n") + : _("key has been created %lu seconds " + "in future (time warp or clock problem)\n"), d ); + if( !opt.ignore_time_conflict ) + return G10ERR_TIME_CONFLICT; + } + + + print_pubkey_algo_note(sk->pubkey_algo); + + if( !digest_algo ) + digest_algo = md_get_algo(md); + + print_digest_algo_note( digest_algo ); + dp = md_read( md, digest_algo ); + sig->digest_algo = digest_algo; + sig->digest_start[0] = dp[0]; + sig->digest_start[1] = dp[1]; + frame = encode_md_value( sk->pubkey_algo, md, + digest_algo, mpi_get_nbits(sk->skey[0]), 0 ); + if (!frame) + return G10ERR_GENERAL; + rc = pubkey_sign( sk->pubkey_algo, sig->data, frame, sk->skey ); + mpi_free(frame); + if (!rc && !opt.no_sig_create_check) { + /* check that the signature verification worked and nothing is + * fooling us e.g. by a bug in the signature create + * code or by deliberately introduced faults. */ + PKT_public_key *pk = m_alloc_clear (sizeof *pk); + + if( get_pubkey( pk, sig->keyid ) ) + rc = G10ERR_NO_PUBKEY; + else { + frame = encode_md_value (pk->pubkey_algo, md, + sig->digest_algo, + mpi_get_nbits(pk->pkey[0]), 0); + if (!frame) + rc = G10ERR_GENERAL; + else + rc = pubkey_verify (pk->pubkey_algo, frame, + sig->data, pk->pkey, + NULL, NULL ); + mpi_free (frame); + } + if (rc) + log_error (_("checking created signature failed: %s\n"), + g10_errstr (rc)); + free_public_key (pk); + } + if( rc ) + log_error(_("signing failed: %s\n"), g10_errstr(rc) ); + else { + if( opt.verbose ) { + char *ustr = get_user_id_string_printable (sig->keyid); + log_info(_("%s/%s signature from: \"%s\"\n"), + pubkey_algo_to_string(sk->pubkey_algo), + digest_algo_to_string(sig->digest_algo), + ustr ); + m_free(ustr); + } + } + return rc; +} + + + +int +complete_sig( PKT_signature *sig, PKT_secret_key *sk, MD_HANDLE md ) +{ + int rc=0; + + if( !(rc=check_secret_key( sk, 0 )) ) + rc = do_sign( sk, sig, md, 0 ); + return rc; +} + +static int +hash_for(int pubkey_algo, int packet_version ) +{ + if( opt.def_digest_algo ) + return opt.def_digest_algo; + else if( recipient_digest_algo ) + return recipient_digest_algo; + else if(PGP2 && pubkey_algo == PUBKEY_ALGO_RSA && packet_version < 4 ) + { + /* Old-style PGP only understands MD5 */ + return DIGEST_ALGO_MD5; + } + else if( pubkey_algo == PUBKEY_ALGO_DSA ) + { + /* We need a 160-bit hash for DSA, so we can't just take the first + in the pref list */ + + if(opt.personal_digest_prefs) + { + prefitem_t *prefs; + + for(prefs=opt.personal_digest_prefs;prefs->type;prefs++) + if(md_digest_length(prefs->value)==20) + 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 int +only_old_style( SK_LIST sk_list ) +{ + SK_LIST sk_rover = NULL; + int old_style = 0; + + /* if there are only old style capable key we use the old sytle */ + for( sk_rover = sk_list; sk_rover; sk_rover = sk_rover->next ) { + PKT_secret_key *sk = sk_rover->sk; + if( sk->pubkey_algo == PUBKEY_ALGO_RSA && sk->version < 4 ) + old_style = 1; + else + return 0; + } + return old_style; +} + + +static void +print_status_sig_created ( PKT_secret_key *sk, PKT_signature *sig, int what ) +{ + byte array[MAX_FINGERPRINT_LEN], *p; + char buf[100+MAX_FINGERPRINT_LEN*2]; + size_t i, n; + + sprintf(buf, "%c %d %d %02x %lu ", + what, sig->pubkey_algo, sig->digest_algo, sig->sig_class, + (ulong)sig->timestamp ); + + fingerprint_from_sk( sk, array, &n ); + p = buf + strlen(buf); + for(i=0; i < n ; i++ ) + sprintf(p+2*i, "%02X", array[i] ); + + write_status_text( STATUS_SIG_CREATED, buf ); +} + + +/* + * Loop over the secret certificates in SK_LIST and build the one pass + * signature packets. OpenPGP says that the data should be bracket by + * the onepass-sig and signature-packet; so we build these onepass + * packet here in reverse order + */ +static int +write_onepass_sig_packets (SK_LIST sk_list, IOBUF out, int sigclass ) +{ + int skcount; + SK_LIST sk_rover; + + for (skcount=0, sk_rover=sk_list; sk_rover; sk_rover = sk_rover->next) + skcount++; + + for (; skcount; skcount--) { + PKT_secret_key *sk; + PKT_onepass_sig *ops; + PACKET pkt; + int i, rc; + + for (i=0, sk_rover = sk_list; sk_rover; sk_rover = sk_rover->next ) { + if (++i == skcount) + break; + } + + sk = sk_rover->sk; + ops = m_alloc_clear (sizeof *ops); + ops->sig_class = sigclass; + ops->digest_algo = hash_for (sk->pubkey_algo, sk->version); + ops->pubkey_algo = sk->pubkey_algo; + keyid_from_sk (sk, ops->keyid); + ops->last = (skcount == 1); + + init_packet(&pkt); + pkt.pkttype = PKT_ONEPASS_SIG; + pkt.pkt.onepass_sig = ops; + rc = build_packet (out, &pkt); + free_packet (&pkt); + if (rc) { + log_error ("build onepass_sig packet failed: %s\n", + g10_errstr(rc)); + return rc; + } + } + + return 0; +} + +/* + * Helper to write the plaintext (literal data) packet + */ +static int +write_plaintext_packet (IOBUF out, IOBUF inp, const char *fname, int ptmode) +{ + PKT_plaintext *pt = NULL; + u32 filesize; + int rc = 0; + + if (!opt.no_literal) { + if (fname || opt.set_filename) { + char *s = make_basename (opt.set_filename? opt.set_filename + : fname, + iobuf_get_real_fname(inp)); + pt = m_alloc (sizeof *pt + strlen(s) - 1); + pt->namelen = strlen (s); + memcpy (pt->name, s, pt->namelen); + m_free (s); + } + else { /* no filename */ + pt = m_alloc (sizeof *pt - 1); + pt->namelen = 0; + } + } + + /* try to calculate the length of the data */ + if (fname && *fname && !(*fname=='-' && !fname[1])) { + if( !(filesize = iobuf_get_filelength(inp)) ) + log_info (_("WARNING: `%s' is an empty file\n"), fname); + + /* we can't yet encode the length of very large files, + * so we switch to partial length encoding in this case */ + if (filesize >= IOBUF_FILELENGTH_LIMIT) + filesize = 0; + + /* because the text_filter modifies the length of the + * data, it is not possible to know the used length + * without a double read of the file - to avoid that + * we simple use partial length packets. + */ + if ( ptmode == 't' ) + filesize = 0; + } + else { + filesize = opt.set_filesize? opt.set_filesize : 0; /* stdin */ + } + + if (!opt.no_literal) { + PACKET pkt; + + pt->timestamp = make_timestamp (); + pt->mode = ptmode; + pt->len = filesize; + pt->new_ctb = !pt->len && !RFC1991; + pt->buf = inp; + init_packet(&pkt); + pkt.pkttype = PKT_PLAINTEXT; + pkt.pkt.plaintext = pt; + /*cfx.datalen = filesize? calc_packet_length( &pkt ) : 0;*/ + if( (rc = build_packet (out, &pkt)) ) + log_error ("build_packet(PLAINTEXT) failed: %s\n", + g10_errstr(rc) ); + pt->buf = NULL; + } + else { + byte copy_buffer[4096]; + int bytes_copied; + + while ((bytes_copied = iobuf_read(inp, copy_buffer, 4096)) != -1) + if (iobuf_write(out, copy_buffer, bytes_copied) == -1) { + rc = G10ERR_WRITE_FILE; + log_error ("copying input to output failed: %s\n", + g10_errstr(rc)); + break; + } + wipememory(copy_buffer,4096); /* burn buffer */ + } + /* fixme: it seems that we never freed pt/pkt */ + + return rc; +} + +/* + * Write the signatures from the SK_LIST to OUT. HASH must be a non-finalized + * hash which will not be changes here. + */ +static int +write_signature_packets (SK_LIST sk_list, IOBUF out, MD_HANDLE hash, + int sigclass, u32 timestamp, u32 duration, + int status_letter) +{ + SK_LIST sk_rover; + + /* loop over the secret certificates */ + for (sk_rover = sk_list; sk_rover; sk_rover = sk_rover->next) { + PKT_secret_key *sk; + PKT_signature *sig; + MD_HANDLE md; + int rc; + + sk = sk_rover->sk; + + /* build the signature packet */ + sig = m_alloc_clear (sizeof *sig); + if(opt.force_v3_sigs || RFC1991) + sig->version=3; + else if(duration || opt.sig_policy_url || opt.sig_notation_data) + sig->version=4; + else + sig->version=sk->version; + keyid_from_sk (sk, sig->keyid); + sig->digest_algo = hash_for (sk->pubkey_algo, sk->version); + sig->pubkey_algo = sk->pubkey_algo; + if(timestamp) + sig->timestamp = timestamp; + else + sig->timestamp = make_timestamp(); + if(duration) + sig->expiredate = sig->timestamp+duration; + sig->sig_class = sigclass; + + md = md_copy (hash); + + if (sig->version >= 4) + build_sig_subpkt_from_sig (sig); + mk_notation_and_policy (sig, NULL, sk); + + hash_sigversion_to_magic (md, sig); + md_final (md); + + rc = do_sign( sk, sig, md, hash_for (sig->pubkey_algo, sk->version) ); + md_close (md); + + if( !rc ) { /* and write it */ + PACKET pkt; + + init_packet(&pkt); + pkt.pkttype = PKT_SIGNATURE; + pkt.pkt.signature = sig; + rc = build_packet (out, &pkt); + if (!rc && is_status_enabled()) { + print_status_sig_created ( sk, sig, status_letter); + } + free_packet (&pkt); + if (rc) + log_error ("build signature packet failed: %s\n", + g10_errstr(rc) ); + } + if( rc ) + return rc;; + } + + return 0; +} + +/**************** + * Sign the files whose names are in FILENAME. + * If DETACHED has the value true, + * make a detached signature. If FILENAMES->d is NULL read from stdin + * and ignore the detached mode. Sign the file with all secret keys + * which can be taken from LOCUSR, if this is NULL, use the default one + * If ENCRYPTFLAG is true, use REMUSER (or ask if it is NULL) to encrypt the + * signed data for these users. + * If OUTFILE is not NULL; this file is used for output and the function + * does not ask for overwrite permission; output is then always + * uncompressed, non-armored and in binary mode. + */ +int +sign_file( STRLIST filenames, int detached, STRLIST locusr, + int encryptflag, STRLIST remusr, const char *outfile ) +{ + const char *fname; + armor_filter_context_t afx; + compress_filter_context_t zfx; + md_filter_context_t mfx; + text_filter_context_t tfx; + progress_filter_context_t pfx; + encrypt_filter_context_t efx; + IOBUF inp = NULL, out = NULL; + PACKET pkt; + int rc = 0; + PK_LIST pk_list = NULL; + SK_LIST sk_list = NULL; + SK_LIST sk_rover = NULL; + int multifile = 0; + u32 duration=0; + + memset( &afx, 0, sizeof afx); + memset( &zfx, 0, sizeof zfx); + memset( &mfx, 0, sizeof mfx); + memset( &efx, 0, sizeof efx); + init_packet( &pkt ); + + if( filenames ) { + fname = filenames->d; + multifile = !!filenames->next; + } + else + fname = NULL; + + if( fname && filenames->next && (!detached || encryptflag) ) + log_bug("multiple files can only be detached signed"); + + if(opt.ask_sig_expire && !opt.force_v3_sigs && !opt.batch && !RFC1991) + duration=ask_expire_interval(1); + + if( (rc=build_sk_list( locusr, &sk_list, 1, PUBKEY_USAGE_SIG )) ) + goto leave; + + if(PGP2 && !only_old_style(sk_list)) + { + log_info(_("you can only detach-sign with PGP 2.x style keys " + "while in --pgp2 mode\n")); + compliance_failure(); + } + + if(encryptflag && (rc=build_pk_list( remusr, &pk_list, PUBKEY_USAGE_ENC ))) + goto leave; + + /* prepare iobufs */ + if( multifile ) /* have list of filenames */ + inp = NULL; /* we do it later */ + else { + if( !(inp = iobuf_open(fname)) ) { + log_error("can't open %s: %s\n", fname? fname: "[stdin]", + strerror(errno) ); + rc = G10ERR_OPEN_FILE; + goto leave; + } + + handle_progress (&pfx, inp, fname); + } + + if( outfile ) { + if( !(out = iobuf_create( outfile )) ) { + log_error(_("can't create %s: %s\n"), outfile, strerror(errno) ); + rc = G10ERR_CREATE_FILE; + goto leave; + } + else if( opt.verbose ) + log_info(_("writing to `%s'\n"), outfile ); + } + else if( (rc = open_outfile( fname, opt.armor? 1: detached? 2:0, &out ))) + goto leave; + + /* prepare to calculate the MD over the input */ + if( opt.textmode && !outfile && !multifile ) + { + memset( &tfx, 0, sizeof tfx); + iobuf_push_filter( inp, text_filter, &tfx ); + } + + mfx.md = md_open(0, 0); + + /* If we're encrypting and signing, it is reasonable to pick the + hash algorithm to use out of the recepient key prefs. */ + 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(_("forcing digest algorithm %s (%d) " + "violates recipient preferences\n"), + digest_algo_to_string(opt.def_digest_algo), + opt.def_digest_algo); + } + else + { + int hashlen=0,algo; + + /* Of course, if the recipient asks for something + unreasonable (like a non-160-bit hash for DSA, for + example), then don't do it. Check all sk's - if any + are DSA, then the hash must be 160-bit. In the future + this can be more complex with different hashes for each + sk, but so long as there is only one signing algorithm + with hash restrictions, this is ok. -dms */ + + for( sk_rover = sk_list; sk_rover; sk_rover = sk_rover->next ) + if(sk_rover->sk->pubkey_algo==PUBKEY_ALGO_DSA) + hashlen=20; + + if((algo= + select_algo_from_prefs(pk_list,PREFTYPE_HASH,-1, + hashlen?&hashlen:NULL))>0) + recipient_digest_algo=algo; + } + } + + for( sk_rover = sk_list; sk_rover; sk_rover = sk_rover->next ) { + PKT_secret_key *sk = sk_rover->sk; + md_enable(mfx.md, hash_for(sk->pubkey_algo, sk->version )); + } + + if( !multifile ) + iobuf_push_filter( inp, md_filter, &mfx ); + + if( detached && !encryptflag && !RFC1991 ) + afx.what = 2; + + if( opt.armor && !outfile ) + iobuf_push_filter( out, armor_filter, &afx ); + + if( encryptflag ) { + efx.pk_list = pk_list; + /* fixme: set efx.cfx.datalen if known */ + iobuf_push_filter( out, encrypt_filter, &efx ); + } + + if( opt.compress && !outfile && ( !detached || opt.compress_sigs) ) + { + int compr_algo=opt.def_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(_("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 ) + { + zfx.algo = compr_algo; + iobuf_push_filter( out, compress_filter, &zfx ); + } + } + + /* Write the one-pass signature packets if needed */ + if (!detached && !RFC1991) { + rc = write_onepass_sig_packets (sk_list, out, + opt.textmode && !outfile ? 0x01:0x00); + if (rc) + goto leave; + } + + /* setup the inner packet */ + if( detached ) { + if( multifile ) { + STRLIST sl; + + if( opt.verbose ) + log_info(_("signing:") ); + /* must walk reverse trough this list */ + for( sl = strlist_last(filenames); sl; + sl = strlist_prev( filenames, sl ) ) { + if( !(inp = iobuf_open(sl->d)) ) { + log_error(_("can't open %s: %s\n"), + sl->d, strerror(errno) ); + rc = G10ERR_OPEN_FILE; + goto leave; + } + handle_progress (&pfx, inp, sl->d); + if( opt.verbose ) + fprintf(stderr, " `%s'", sl->d ); + if(opt.textmode) + { + memset( &tfx, 0, sizeof tfx); + iobuf_push_filter( inp, text_filter, &tfx ); + } + iobuf_push_filter( inp, md_filter, &mfx ); + while( iobuf_get(inp) != -1 ) + ; + iobuf_close(inp); inp = NULL; + } + if( opt.verbose ) + putc( '\n', stderr ); + } + else { + /* read, so that the filter can calculate the digest */ + while( iobuf_get(inp) != -1 ) + ; + } + } + else { + rc = write_plaintext_packet (out, inp, fname, + opt.textmode && !outfile ? 't':'b'); + } + + /* catch errors from above */ + if (rc) + goto leave; + + /* write the signatures */ + rc = write_signature_packets (sk_list, out, mfx.md, + opt.textmode && !outfile? 0x01 : 0x00, + 0, duration, detached ? 'D':'S'); + if( rc ) + goto leave; + + + leave: + if( rc ) + iobuf_cancel(out); + else { + iobuf_close(out); + if (encryptflag) + write_status( STATUS_END_ENCRYPTION ); + } + iobuf_close(inp); + md_close( mfx.md ); + release_sk_list( sk_list ); + release_pk_list( pk_list ); + recipient_digest_algo=0; + return rc; +} + + + +/**************** + * make a clear signature. note that opt.armor is not needed + */ +int +clearsign_file( const char *fname, STRLIST locusr, const char *outfile ) +{ + armor_filter_context_t afx; + progress_filter_context_t pfx; + MD_HANDLE textmd = NULL; + IOBUF inp = NULL, out = NULL; + PACKET pkt; + int rc = 0; + SK_LIST sk_list = NULL; + SK_LIST sk_rover = NULL; + int old_style = RFC1991; + int only_md5 = 0; + u32 duration=0; + + memset( &afx, 0, sizeof afx); + init_packet( &pkt ); + + if(opt.ask_sig_expire && !opt.force_v3_sigs && !opt.batch && !RFC1991) + duration=ask_expire_interval(1); + + if( (rc=build_sk_list( locusr, &sk_list, 1, PUBKEY_USAGE_SIG )) ) + goto leave; + + if( !old_style && !duration ) + old_style = only_old_style( sk_list ); + + if(PGP2 && !only_old_style(sk_list)) + { + log_info(_("you can only clearsign with PGP 2.x style keys " + "while in --pgp2 mode\n")); + compliance_failure(); + } + + /* prepare iobufs */ + if( !(inp = iobuf_open(fname)) ) { + log_error("can't open %s: %s\n", fname? fname: "[stdin]", + strerror(errno) ); + rc = G10ERR_OPEN_FILE; + goto leave; + } + handle_progress (&pfx, inp, fname); + + if( outfile ) { + if( !(out = iobuf_create( outfile )) ) { + log_error(_("can't create %s: %s\n"), outfile, strerror(errno) ); + rc = G10ERR_CREATE_FILE; + goto leave; + } + else if( opt.verbose ) + log_info(_("writing to `%s'\n"), outfile ); + } + else if( (rc = open_outfile( fname, 1, &out )) ) + goto leave; + + iobuf_writestr(out, "-----BEGIN PGP SIGNED MESSAGE-----" LF ); + + for( sk_rover = sk_list; sk_rover; sk_rover = sk_rover->next ) { + PKT_secret_key *sk = sk_rover->sk; + if( hash_for(sk->pubkey_algo, sk->version) == DIGEST_ALGO_MD5 ) + only_md5 = 1; + else { + only_md5 = 0; + break; + } + } + + if( !(old_style && only_md5) ) { + const char *s; + int any = 0; + byte hashs_seen[256]; + + memset( hashs_seen, 0, sizeof hashs_seen ); + iobuf_writestr(out, "Hash: " ); + for( sk_rover = sk_list; sk_rover; sk_rover = sk_rover->next ) { + PKT_secret_key *sk = sk_rover->sk; + int i = hash_for(sk->pubkey_algo, sk->version); + + if( !hashs_seen[ i & 0xff ] ) { + s = digest_algo_to_string( i ); + if( s ) { + hashs_seen[ i & 0xff ] = 1; + if( any ) + iobuf_put(out, ',' ); + iobuf_writestr(out, s ); + any = 1; + } + } + } + assert(any); + iobuf_writestr(out, LF ); + } + + if( opt.not_dash_escaped ) + iobuf_writestr( out, + "NotDashEscaped: You need GnuPG to verify this message" LF ); + iobuf_writestr(out, LF ); + + textmd = md_open(0, 0); + for( sk_rover = sk_list; sk_rover; sk_rover = sk_rover->next ) { + PKT_secret_key *sk = sk_rover->sk; + md_enable(textmd, hash_for(sk->pubkey_algo, sk->version)); + } + if ( DBG_HASHING ) + md_start_debug( textmd, "clearsign" ); + copy_clearsig_text( out, inp, textmd, !opt.not_dash_escaped, + opt.escape_from, (old_style && only_md5) ); + /* fixme: check for read errors */ + + /* now write the armor */ + afx.what = 2; + iobuf_push_filter( out, armor_filter, &afx ); + + /* write the signatures */ + rc=write_signature_packets (sk_list, out, textmd, 0x01, 0, duration, 'C'); + if( rc ) + goto leave; + + leave: + if( rc ) + iobuf_cancel(out); + else + iobuf_close(out); + iobuf_close(inp); + md_close( textmd ); + release_sk_list( sk_list ); + return rc; +} + +/* + * Sign and conventionally encrypt the given file. + * FIXME: Far too much code is duplicated - revamp the whole file. + */ +int +sign_symencrypt_file (const char *fname, STRLIST locusr) +{ + armor_filter_context_t afx; + progress_filter_context_t pfx; + compress_filter_context_t zfx; + md_filter_context_t mfx; + text_filter_context_t tfx; + cipher_filter_context_t cfx; + IOBUF inp = NULL, out = NULL; + PACKET pkt; + STRING2KEY *s2k = NULL; + int rc = 0; + SK_LIST sk_list = NULL; + SK_LIST sk_rover = NULL; + int algo; + u32 duration=0; + + memset( &afx, 0, sizeof afx); + memset( &zfx, 0, sizeof zfx); + memset( &mfx, 0, sizeof mfx); + memset( &tfx, 0, sizeof tfx); + memset( &cfx, 0, sizeof cfx); + init_packet( &pkt ); + + if(opt.ask_sig_expire && !opt.force_v3_sigs && !opt.batch && !RFC1991) + duration=ask_expire_interval(1); + + rc = build_sk_list (locusr, &sk_list, 1, PUBKEY_USAGE_SIG); + if (rc) + goto leave; + + /* prepare iobufs */ + inp = iobuf_open(fname); + if( !inp ) { + log_error("can't open %s: %s\n", fname? fname: "[stdin]", + strerror(errno) ); + rc = G10ERR_OPEN_FILE; + goto leave; + } + handle_progress (&pfx, inp, fname); + + /* prepare key */ + s2k = m_alloc_clear( sizeof *s2k ); + s2k->mode = RFC1991? 0:opt.s2k_mode; + s2k->hash_algo = opt.s2k_digest_algo; + + algo = default_cipher_algo(); + if (!opt.quiet || !opt.batch) + log_info (_("%s encryption will be used\n"), + cipher_algo_to_string(algo) ); + cfx.dek = passphrase_to_dek( NULL, 0, algo, s2k, 2, NULL, NULL); + + if (!cfx.dek || !cfx.dek->keylen) { + rc = G10ERR_PASSPHRASE; + log_error(_("error creating passphrase: %s\n"), g10_errstr(rc) ); + goto leave; + } + + /* now create the outfile */ + rc = open_outfile (fname, opt.armor? 1:0, &out); + if (rc) + goto leave; + + /* prepare to calculate the MD over the input */ + if (opt.textmode) + iobuf_push_filter (inp, text_filter, &tfx); + mfx.md = md_open(0, 0); + + for (sk_rover = sk_list; sk_rover; sk_rover = sk_rover->next) { + PKT_secret_key *sk = sk_rover->sk; + md_enable (mfx.md, hash_for (sk->pubkey_algo, sk->version )); + } + + iobuf_push_filter (inp, md_filter, &mfx); + + /* Push armor output filter */ + if (opt.armor) + iobuf_push_filter (out, armor_filter, &afx); + + /* Write the symmetric key packet */ + /*(current filters: armor)*/ + if (!RFC1991) { + PKT_symkey_enc *enc = m_alloc_clear( sizeof *enc ); + enc->version = 4; + enc->cipher_algo = cfx.dek->algo; + enc->s2k = *s2k; + pkt.pkttype = PKT_SYMKEY_ENC; + pkt.pkt.symkey_enc = enc; + if( (rc = build_packet( out, &pkt )) ) + log_error("build symkey packet failed: %s\n", g10_errstr(rc) ); + m_free(enc); + } + + /* Push the encryption filter */ + iobuf_push_filter( out, cipher_filter, &cfx ); + + /* Push the Zip filter */ + if (opt.compress && default_compress_algo()) + { + zfx.algo = default_compress_algo(); + iobuf_push_filter( out, compress_filter, &zfx ); + } + + /* Write the one-pass signature packets */ + /*(current filters: zip - encrypt - armor)*/ + if (!RFC1991) { + rc = write_onepass_sig_packets (sk_list, out, + opt.textmode? 0x01:0x00); + if (rc) + goto leave; + } + + /* Pipe data through all filters; i.e. write the signed stuff */ + /*(current filters: zip - encrypt - armor)*/ + rc = write_plaintext_packet (out, inp, fname, opt.textmode ? 't':'b'); + if (rc) + goto leave; + + /* Write the signatures */ + /*(current filters: zip - encrypt - armor)*/ + rc = write_signature_packets (sk_list, out, mfx.md, + opt.textmode? 0x01 : 0x00, + 0, duration, 'S'); + if( rc ) + goto leave; + + + leave: + if( rc ) + iobuf_cancel(out); + else { + iobuf_close(out); + write_status( STATUS_END_ENCRYPTION ); + } + iobuf_close(inp); + release_sk_list( sk_list ); + md_close( mfx.md ); + m_free(cfx.dek); + m_free(s2k); + return rc; +} + + +/**************** + * Create a signature packet for the given public key certificate and + * the user id and return it in ret_sig. User signature class SIGCLASS + * user-id is not used (and may be NULL if sigclass is 0x20) If + * DIGEST_ALGO is 0 the function selects an appropriate one. + * SIGVERSION gives the minimal required signature packet version; + * this is needed so that special properties like local sign are not + * applied (actually: dropped) when a v3 key is used. TIMESTAMP is + * the timestamp to use for the signature. 0 means "now" */ +int +make_keysig_packet( PKT_signature **ret_sig, PKT_public_key *pk, + PKT_user_id *uid, PKT_public_key *subpk, + PKT_secret_key *sk, + int sigclass, int digest_algo, + int sigversion, u32 timestamp, u32 duration, + int (*mksubpkt)(PKT_signature *, void *), void *opaque + ) +{ + PKT_signature *sig; + int rc=0; + MD_HANDLE md; + + assert( (sigclass >= 0x10 && sigclass <= 0x13) || sigclass == 0x1F + || sigclass == 0x20 || sigclass == 0x18 + || sigclass == 0x30 || sigclass == 0x28 ); + + if (opt.force_v4_certs) + sigversion = 4; + + if (sigversion < sk->version) + sigversion = sk->version; + + /* If you are making a signature on a v4 key using your v3 key, it + doesn't make sense to generate a v3 sig. After all, no v3-only + PGP implementation could understand the v4 key in the first + place. Note that this implies that a signature on an attribute + uid is usually going to be v4 as well, since they are not + generally found on v3 keys. */ + if (sigversion < pk->version) + sigversion = pk->version; + + if( !digest_algo ) + { + /* Basically, this means use SHA1 always unless it's a v3 RSA + key making a v3 cert (use MD5), or the user specified + something (use whatever they said). They still must use a + 160-bit hash with DSA, or the signature will fail. Note + that this still allows the caller of make_keysig_packet to + override the user setting if it must. */ + + if(opt.cert_digest_algo) + digest_algo=opt.cert_digest_algo; + else if((sk->pubkey_algo==PUBKEY_ALGO_RSA || + sk->pubkey_algo==PUBKEY_ALGO_RSA_S) && + pk->version<4 && sigversion < 4) + digest_algo = DIGEST_ALGO_MD5; + else + digest_algo = DIGEST_ALGO_SHA1; + } + + md = md_open( digest_algo, 0 ); + + /* hash the public key certificate and the user id */ + hash_public_key( md, pk ); + if( sigclass == 0x18 || sigclass == 0x28 ) { /* subkey binding/revocation*/ + hash_public_key( md, subpk ); + } + else if( sigclass != 0x1F && sigclass != 0x20 ) { + hash_uid (md, sigversion, uid); + } + /* and make the signature packet */ + sig = m_alloc_clear( sizeof *sig ); + sig->version = sigversion; + sig->flags.exportable=1; + sig->flags.revocable=1; + keyid_from_sk( sk, sig->keyid ); + sig->pubkey_algo = sk->pubkey_algo; + sig->digest_algo = digest_algo; + if(timestamp) + sig->timestamp=timestamp; + else + sig->timestamp=make_timestamp(); + if(duration) + sig->expiredate=sig->timestamp+duration; + sig->sig_class = sigclass; + if( sig->version >= 4 ) + build_sig_subpkt_from_sig( sig ); + mk_notation_and_policy( sig, pk, sk ); + + /* Crucial that the call to mksubpkt comes LAST before the calls + to finalize the sig as that makes it possible for the mksubpkt + function to get a reliable pointer to the subpacket area. */ + if( sig->version >= 4 && mksubpkt ) + rc = (*mksubpkt)( sig, opaque ); + + if( !rc ) { + hash_sigversion_to_magic (md, sig); + md_final(md); + + rc = complete_sig( sig, sk, md ); + } + + md_close( md ); + if( rc ) + free_seckey_enc( sig ); + else + *ret_sig = sig; + return rc; +} + + + +/**************** + * Create a new signature packet based on an existing one. + * Only user ID signatures are supported for now. + * TODO: Merge this with make_keysig_packet. + */ +int +update_keysig_packet( PKT_signature **ret_sig, + PKT_signature *orig_sig, + PKT_public_key *pk, + PKT_user_id *uid, + PKT_public_key *subpk, + PKT_secret_key *sk, + int (*mksubpkt)(PKT_signature *, void *), + void *opaque + ) +{ + PKT_signature *sig; + int rc=0; + MD_HANDLE md; + + if ((!orig_sig || !pk || !sk) + || (orig_sig->sig_class >= 0x10 && orig_sig->sig_class <= 0x13 && !uid) + || (orig_sig->sig_class == 0x18 && !subpk)) + return G10ERR_GENERAL; + + md = md_open( orig_sig->digest_algo, 0 ); + + /* hash the public key certificate and the user id */ + hash_public_key( md, pk ); + + if( orig_sig->sig_class == 0x18 ) + hash_public_key( md, subpk ); + else + hash_uid (md, orig_sig->version, uid); + + /* create a new signature packet */ + sig = copy_signature (NULL, orig_sig); + + /* We need to create a new timestamp so that new sig expiration + calculations are done correctly... */ + sig->timestamp=make_timestamp(); + + /* ... but we won't make a timestamp earlier than the existing + one. */ + while(sig->timestamp<=orig_sig->timestamp) + { + sleep(1); + sig->timestamp=make_timestamp(); + } + + /* Note that already expired sigs will remain expired (with a + duration of 0) since build-packet.c:build_sig_subpkt_from_sig + detects this case. */ + + if( sig->version >= 4 ) + { + /* Put the updated timestamp into the sig. Note that this + will automagically lower any sig expiration dates to + correctly correspond to the differences in the timestamps + (i.e. the duration will shrink). */ + build_sig_subpkt_from_sig( sig ); + + if (mksubpkt) + rc = (*mksubpkt)(sig, opaque); + } + + if (!rc) { + hash_sigversion_to_magic (md, sig); + md_final(md); + + rc = complete_sig( sig, sk, md ); + } + + md_close (md); + if( rc ) + free_seckey_enc (sig); + else + *ret_sig = sig; + return rc; +} |