[PATCH] Allow signing of files which are not present on the system

Steven J. Murdoch gnupg+Steven.Murdoch at cl.cam.ac.uk
Wed Jul 27 17:36:41 CEST 2011


For a particular use case of GnuPG, I needed to sign a number of files which
were not on the same machine as the private key. It would be insecure to upload
the private key to the machine with the files, and it would take a very long
time to download all the files to the machine with the private key.

I've written a patch which does this (attached), but I'd like to find out
whether this (after some cleaning up), would be considered for acceptance?

Options I looked at included forwarding gpg-agent over SSH, but gpg-agent
still requires having the private key on the machine doing the signing.

I therefore looked into signing a hash of the file, as suggested here:
http://lists.gnupg.org/pipermail/gnupg-devel/2004-May/021010.html

It turned out to be a bit more complicated than that mail suggested, because
GnuPG appends a footer to the file to be signed, before calculating the hash
which is actually signed. This footer includes a timestamp, so is different each
time. Therefore, I don't think it is possible to sign a file, given only the
hash of that file.

My solution was to modify --print-md to output the intermediate state of the
hash calculation, after hashing the file but before finalizing the hash. Then I
modified --sign so that it will accept this intermediate state as input.

To use it, on the system with the files you want to sign, run:
$ gpg --with-colons --print-md sha256 FILENAME | cut -f 4 -d ':'
where FILENAME is the name of the file you want to sign.
This will output the intermediate state of the message digest needed to
calculate a SHA256 signature.

On the system with your private key run:
$ gpg --use-agent --load-md-state STATE --personal-digest-preferences sha256
--detach-sign --output OUTPUT < /dev/null
where STATE is the intermediate state output by the command above, and OUTPUT is
where you want to save the signature.

The attached patch (on GnuPG 1.4.11) works, but needs some cleaning up before it
could be merged. My question is whether this feature would be considered for
acceptance by the GnuPG team?

I would also like feedback on both the feature design and patch code.

Steven.

-- 
http://www.cl.cam.ac.uk/users/sjm217/
-------------- next part --------------
>From b927f2e7fd426bd593eb5a2809ec9a44cf400e93 Mon Sep 17 00:00:00 2001
From: Steven Murdoch <Steven.Murdoch at cl.cam.ac.uk>
Date: Mon, 25 Jul 2011 22:18:12 +0100
Subject: [PATCH] Allow signing of files which are not present on the system

This change allows GnuPG to sign files which are not present on the
same system as where the keys are stored. This is achieved by:

- --print-md --with-colons outputs the message digest state as the 4th
  colon-delimited item.
- --load-md-state STATE used in combination with --detach-sign replaces
  the digest state with the value specified on the command line, before
  calculating the digital signature

Typical usage is:

$ gpg --with-colons --print-md SHA256 tmp.txt

$ gpg --load-md-state 67[...]00 --output tmp.sig \
  --personal-digest-preferences SHA256 --detach-sign < /dev/null
---
 cipher/md.c      |   64 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 g10/gpg.c        |   38 ++++++++++++++++++++++----------
 g10/main.h       |    3 +-
 g10/options.h    |    1 +
 g10/sign.c       |   13 ++++++----
 include/cipher.h |    2 +
 6 files changed, 103 insertions(+), 18 deletions(-)

diff --git a/cipher/md.c b/cipher/md.c
index 9ee0fa7..b2d6c16 100644
--- a/cipher/md.c
+++ b/cipher/md.c
@@ -237,6 +237,70 @@ md_enable( MD_HANDLE h, int algo )
     (*ac->init)( &ac->context.c );
 }
 
+static void
+md_load_string(byte *state, const char *replacement)
+{
+    int pos=0;
+    int shift;
+    char c;
+    byte *r = (byte *)state;
+
+    while (replacement[pos]) {
+        c = replacement[pos];
+        if (pos % 2 == 0) {
+            r[pos/2] = 0;
+            shift = 4;
+        } else {
+            shift = 0;
+        }
+        if (c >= '0' && c <= '9')
+            r[pos/2] |= (c - '0') << shift;
+        else if (c >= 'A' && c <= 'F')
+            r[pos/2] |= (c - 'A' + 10) << shift;
+        else if (c >= 'a' && c <= 'f')
+            r[pos/2] |= (c - 'a' + 10) << shift;
+        pos ++;
+    }
+}
+
+void
+md_load(MD_HANDLE a, int algo, const char *b)
+{
+    struct md_digest_list_s *r;
+
+    if( !algo ) {  /* return the first algorithm */
+	if( (r=a->list) ) {
+	    if( r->next )
+		log_debug("more than algorithm in md_load(0)\n");
+            md_load_string(r->context.c, b);
+            return;
+	}
+    }
+    else {
+	for(r=a->list; r; r = r->next ) {
+	    if( r->algo == algo ) {
+                md_load_string(r->context.c, b);
+                return;
+            }
+        }
+    }
+    BUG();
+}
+
+char *
+md_dump(MD_HANDLE a, int algo)
+{
+    struct md_digest_list_s *ar;
+
+    if( a->bufcount )
+	md_write( a, NULL, 0 );
+    for( ar=a->list; ar; ar = ar->next ) {
+	    if( ar->algo == algo )
+            return bin2hex((void *)ar->context.c, ar->contextsize,
+                    NULL);
+    }
+    BUG();
+}
 
 MD_HANDLE
 md_copy( MD_HANDLE a )
diff --git a/g10/gpg.c b/g10/gpg.c
index 0142168..4ef9f1b 100644
--- a/g10/gpg.c
+++ b/g10/gpg.c
@@ -372,6 +372,7 @@ enum cmd_and_opt_values
     oDisableDSA2,
     oAllowMultipleMessages,
     oNoAllowMultipleMessages,
+    oLoadMDState,
 
     oNoop
   };
@@ -719,6 +720,7 @@ static ARGPARSE_OPTS opts[] = {
     { oDisableDSA2, "disable-dsa2", 0, "@"},
     { oAllowMultipleMessages, "allow-multiple-messages", 0, "@"},
     { oNoAllowMultipleMessages, "no-allow-multiple-messages", 0, "@"},
+    { oLoadMDState, "load-md-state", 2, "@"},
 
     /* These two are aliases to help users of the PGP command line
        product use gpg with minimal pain.  Many commands are common
@@ -2887,6 +2889,8 @@ main (int argc, char **argv )
 	  case oNoAllowMultipleMessages:
 	    opt.flags.allow_multiple_messages=0;
 	    break;
+          case oLoadMDState:
+	    opt.load_md_state = pargs.r.ret_str; break;
 
 	  case oNoop: break;
 
@@ -3441,7 +3445,7 @@ main (int argc, char **argv )
 		strcpy(sl->d, fname);
 	    }
 	}
-	if( (rc = sign_file( sl, detached_sig, locusr, 0, NULL, NULL)) )
+	if( (rc = sign_file( sl, detached_sig, locusr, 0, NULL, NULL, opt.load_md_state)) )
 	    log_error("signing failed: %s\n", g10_errstr(rc) );
 	free_strlist(sl);
 	break;
@@ -3455,7 +3459,7 @@ main (int argc, char **argv )
 	}
 	else
 	    sl = NULL;
-	if( (rc = sign_file(sl, detached_sig, locusr, 1, remusr, NULL)) )
+	if( (rc = sign_file(sl, detached_sig, locusr, 1, remusr, NULL, opt.load_md_state)) )
 	    log_error("%s: sign+encrypt failed: %s\n",
 		      print_fname_stdin(fname), g10_errstr(rc) );
 	free_strlist(sl);
@@ -3479,7 +3483,7 @@ main (int argc, char **argv )
 	      }
 	    else
 	      sl = NULL;
-	    if( (rc = sign_file(sl, detached_sig, locusr, 2, remusr, NULL)) )
+	    if( (rc = sign_file(sl, detached_sig, locusr, 2, remusr, NULL, opt.load_md_state)) )
 	      log_error("%s: symmetric+sign+encrypt failed: %s\n",
 			print_fname_stdin(fname), g10_errstr(rc) );
 	    free_strlist(sl);
@@ -4107,7 +4111,7 @@ print_hex( MD_HANDLE md, int algo, const char *fname )
 }
 
 static void
-print_hashline( MD_HANDLE md, int algo, const char *fname )
+print_hashline( MD_HANDLE md, int algo, const char *fname, const char *md_state )
 {
     int i, n;
     const byte *p;
@@ -4126,6 +4130,10 @@ print_hashline( MD_HANDLE md, int algo, const char *fname )
     n = md_digest_length(algo);
     for(i=0; i < n ; i++, p++ ) 
         printf("%02X", *p );
+    if (md_state) {
+        putchar(':');
+        printf("%s", md_state);
+    }
     putchar(':');
     putchar('\n');
 }
@@ -4180,21 +4188,25 @@ print_mds( const char *fname, int algo )
     if( ferror(fp) )
 	log_error("%s: %s\n", fname?fname:"[stdin]", strerror(errno) );
     else {
+        char *md_state = NULL;
+	md_write(md, NULL, 0);
+        if (algo)
+            md_state = md_dump(md, algo);
 	md_final(md);
         if ( opt.with_colons ) {
             if ( algo ) 
-                print_hashline( md, algo, fname );
+                print_hashline( md, algo, fname, md_state);
             else {
-                print_hashline( md, DIGEST_ALGO_MD5, fname );
-                print_hashline( md, DIGEST_ALGO_SHA1, fname );
-                print_hashline( md, DIGEST_ALGO_RMD160, fname );
+                print_hashline( md, DIGEST_ALGO_MD5, fname, md_state );
+                print_hashline( md, DIGEST_ALGO_SHA1, fname, md_state );
+                print_hashline( md, DIGEST_ALGO_RMD160, fname, md_state );
 #ifdef USE_SHA256
-                print_hashline( md, DIGEST_ALGO_SHA224, fname );
-                print_hashline( md, DIGEST_ALGO_SHA256, fname );
+                print_hashline( md, DIGEST_ALGO_SHA224, fname, md_state );
+                print_hashline( md, DIGEST_ALGO_SHA256, fname, md_state );
 #endif
 #ifdef USE_SHA512
-		print_hashline( md, DIGEST_ALGO_SHA384, fname );
-		print_hashline( md, DIGEST_ALGO_SHA512, fname );
+		print_hashline( md, DIGEST_ALGO_SHA384, fname, md_state );
+		print_hashline( md, DIGEST_ALGO_SHA512, fname, md_state );
 #endif
             }
         }
@@ -4215,6 +4227,8 @@ print_mds( const char *fname, int algo )
 #endif
             }
         }
+        if (md_state)
+            xfree(md_state);
     }
     md_close(md);
 
diff --git a/g10/main.h b/g10/main.h
index 584c4c7..bce37a6 100644
--- a/g10/main.h
+++ b/g10/main.h
@@ -152,7 +152,8 @@ int encrypt_filter( void *opaque, int control,
 /*-- sign.c --*/
 int complete_sig( PKT_signature *sig, PKT_secret_key *sk, MD_HANDLE md );
 int sign_file( STRLIST filenames, int detached, STRLIST locusr,
-	       int do_encrypt, STRLIST remusr, const char *outfile );
+	       int do_encrypt, STRLIST remusr, const char *outfile,
+           const char *replace_md_context);
 int clearsign_file( const char *fname, STRLIST locusr, const char *outfile );
 int sign_symencrypt_file (const char *fname, STRLIST locusr);
 
diff --git a/g10/options.h b/g10/options.h
index cac1c4c..97f2e06 100644
--- a/g10/options.h
+++ b/g10/options.h
@@ -74,6 +74,7 @@ struct
   int bz2_compress_level;
   int bz2_decompress_lowmem;
   const char *def_secret_key;
+  const char *load_md_state;
   char *def_recipient;
   int def_recipient_self;
   int def_cert_level;
diff --git a/g10/sign.c b/g10/sign.c
index 5003e9e..1a8f868 100644
--- a/g10/sign.c
+++ b/g10/sign.c
@@ -622,7 +622,7 @@ write_plaintext_packet (IOBUF out, IOBUF inp, const char *fname,
 static int
 write_signature_packets (SK_LIST sk_list, IOBUF out, MD_HANDLE hash,
                          int sigclass, u32 timestamp, u32 duration,
-			 int status_letter)
+			 int status_letter, const char *replace_md_context)
 {
     SK_LIST sk_rover;
 
@@ -656,6 +656,8 @@ write_signature_packets (SK_LIST sk_list, IOBUF out, MD_HANDLE hash,
 	sig->sig_class = sigclass;
 
 	md = md_copy (hash);
+    if (replace_md_context)
+        md_load(md, hash_for(sk), replace_md_context);
 
 	if (sig->version >= 4)
 	  {
@@ -705,7 +707,8 @@ write_signature_packets (SK_LIST sk_list, IOBUF out, MD_HANDLE hash,
  */
 int
 sign_file( STRLIST filenames, int detached, STRLIST locusr,
-	   int encryptflag, STRLIST remusr, const char *outfile )
+	   int encryptflag, STRLIST remusr, const char *outfile,
+       const char *replace_md_context)
 {
     const char *fname;
     armor_filter_context_t afx;
@@ -1009,7 +1012,7 @@ sign_file( STRLIST filenames, int detached, STRLIST locusr,
     /* write the signatures */
     rc = write_signature_packets (sk_list, out, mfx.md,
                                   opt.textmode && !outfile? 0x01 : 0x00,
-				  create_time, duration, detached ? 'D':'S');
+				  create_time, duration, detached ? 'D':'S', replace_md_context);
     if( rc )
         goto leave;
 
@@ -1169,7 +1172,7 @@ clearsign_file( const char *fname, STRLIST locusr, const char *outfile )
 
     /* write the signatures */
     rc=write_signature_packets (sk_list, out, textmd, 0x01,
-				create_time, duration, 'C');
+				create_time, duration, 'C', NULL);
     if( rc )
         goto leave;
 
@@ -1331,7 +1334,7 @@ sign_symencrypt_file (const char *fname, STRLIST locusr)
     /*(current filters: zip - encrypt - armor)*/
     rc = write_signature_packets (sk_list, out, mfx.md,
 				  opt.textmode? 0x01 : 0x00,
-				  create_time, duration, 'S');
+				  create_time, duration, 'S', NULL);
     if( rc )
         goto leave;
 
diff --git a/include/cipher.h b/include/cipher.h
index 2bc57d6..5655b1d 100644
--- a/include/cipher.h
+++ b/include/cipher.h
@@ -130,6 +130,8 @@ const char * digest_algo_to_string( int algo );
 int check_digest_algo( int algo );
 MD_HANDLE md_open( int algo, int secure );
 void md_enable( MD_HANDLE hd, int algo );
+void md_load(MD_HANDLE a, int algo, const char *b);
+char * md_dump(MD_HANDLE a, int algo);
 MD_HANDLE md_copy( MD_HANDLE a );
 void md_reset( MD_HANDLE a );
 void md_close(MD_HANDLE a);
-- 
1.7.3.1



More information about the Gnupg-devel mailing list