- dtucker@cvs.openbsd.org 2011/10/16 11:02:46
     [moduli.c ssh-keygen.1 ssh-keygen.c]
     Add optional checkpoints for moduli screening.  feedback & ok deraadt
diff --git a/ChangeLog b/ChangeLog
index 1d00c68..9f33f53 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -3,6 +3,9 @@
    - djm@cvs.openbsd.org 2011/10/04 14:17:32
      [sftp-glob.c]
      silence error spam for "ls */foo" in directory with files; bz#1683
+   - dtucker@cvs.openbsd.org 2011/10/16 11:02:46
+     [moduli.c ssh-keygen.1 ssh-keygen.c]
+     Add optional checkpoints for moduli screening.  feedback & ok deraadt
 
 20111001
  - (dtucker) [openbsd-compat/mktemp.c] Fix compiler warning.  ok djm
diff --git a/moduli.c b/moduli.c
index 2964a8b..f734d1c 100644
--- a/moduli.c
+++ b/moduli.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: moduli.c,v 1.22 2010/11/10 01:33:07 djm Exp $ */
+/* $OpenBSD: moduli.c,v 1.23 2011/10/16 11:02:46 dtucker Exp $ */
 /*
  * Copyright 1994 Phil Karn <karn@qualcomm.com>
  * Copyright 1996-1998, 2003 William Allen Simpson <wsimpson@greendragon.com>
@@ -49,6 +49,7 @@
 #include <string.h>
 #include <stdarg.h>
 #include <time.h>
+#include <unistd.h>
 
 #include "xmalloc.h"
 #include "dh.h"
@@ -137,7 +138,7 @@
 static BIGNUM *largebase;
 
 int gen_candidates(FILE *, u_int32_t, u_int32_t, BIGNUM *);
-int prime_test(FILE *, FILE *, u_int32_t, u_int32_t);
+int prime_test(FILE *, FILE *, u_int32_t, u_int32_t, char *);
 
 /*
  * print moduli out in consistent form,
@@ -438,6 +439,52 @@
 	return (ret);
 }
 
+static void
+write_checkpoint(char *cpfile, u_int32_t lineno)
+{
+	FILE *fp;
+	char tmpfile[MAXPATHLEN];
+	int r;
+
+	r = snprintf(tmpfile, sizeof(tmpfile), "%s.XXXXXXXXXX", cpfile);
+	if (r == -1 || r >= MAXPATHLEN) {
+		logit("write_checkpoint: temp pathname too long");
+		return;
+	}
+	if ((r = mkstemp(tmpfile)) == -1) {
+		logit("mkstemp(%s): %s", tmpfile, strerror(errno));
+		return;
+	}
+	if ((fp = fdopen(r, "w")) == NULL) {
+		logit("write_checkpoint: fdopen: %s", strerror(errno));
+		close(r);
+		return;
+	}
+	if (fprintf(fp, "%lu\n", (unsigned long)lineno) > 0 && fclose(fp) == 0
+	    && rename(tmpfile, cpfile) == 0)
+		debug3("wrote checkpoint line %lu to '%s'",
+		    (unsigned long)lineno, cpfile);
+	else
+		logit("failed to write to checkpoint file '%s': %s", cpfile,
+		    strerror(errno));
+}
+
+static unsigned long
+read_checkpoint(char *cpfile)
+{
+	FILE *fp;
+	unsigned long lineno = 0;
+
+	if ((fp = fopen(cpfile, "r")) == NULL)
+		return 0;
+	if (fscanf(fp, "%lu\n", &lineno) < 1)
+		logit("Failed to load checkpoint from '%s'", cpfile);
+	else
+		logit("Loaded checkpoint from '%s' line %lu", cpfile, lineno);
+	fclose(fp);
+	return lineno;
+}
+
 /*
  * perform a Miller-Rabin primality test
  * on the list of candidates
@@ -445,13 +492,15 @@
  * The result is a list of so-call "safe" primes
  */
 int
-prime_test(FILE *in, FILE *out, u_int32_t trials, u_int32_t generator_wanted)
+prime_test(FILE *in, FILE *out, u_int32_t trials, u_int32_t generator_wanted,
+    char *checkpoint_file)
 {
 	BIGNUM *q, *p, *a;
 	BN_CTX *ctx;
 	char *cp, *lp;
 	u_int32_t count_in = 0, count_out = 0, count_possible = 0;
 	u_int32_t generator_known, in_tests, in_tries, in_type, in_size;
+	unsigned long last_processed = 0;
 	time_t time_start, time_stop;
 	int res;
 
@@ -472,10 +521,21 @@
 	debug2("%.24s Final %u Miller-Rabin trials (%x generator)",
 	    ctime(&time_start), trials, generator_wanted);
 
+	if (checkpoint_file != NULL)
+		last_processed = read_checkpoint(checkpoint_file);
+
 	res = 0;
 	lp = xmalloc(QLINESIZE + 1);
 	while (fgets(lp, QLINESIZE + 1, in) != NULL) {
 		count_in++;
+		if (checkpoint_file != NULL) {
+			if (count_in <= last_processed) {
+				debug3("skipping line %u, before checkpoint",
+				    count_in);
+				continue;
+			}
+			write_checkpoint(checkpoint_file, count_in);
+		}
 		if (strlen(lp) < 14 || *lp == '!' || *lp == '#') {
 			debug2("%10u: comment or short line", count_in);
 			continue;
@@ -644,6 +704,9 @@
 	BN_free(q);
 	BN_CTX_free(ctx);
 
+	if (checkpoint_file != NULL)
+		unlink(checkpoint_file);
+
 	logit("%.24s Found %u safe primes of %u candidates in %ld seconds",
 	    ctime(&time_stop), count_out, count_possible,
 	    (long) (time_stop - time_start));
diff --git a/ssh-keygen.1 b/ssh-keygen.1
index 528fcd3..41da207 100644
--- a/ssh-keygen.1
+++ b/ssh-keygen.1
@@ -1,4 +1,4 @@
-.\"	$OpenBSD: ssh-keygen.1,v 1.107 2011/09/07 02:18:31 deraadt Exp $
+.\"	$OpenBSD: ssh-keygen.1,v 1.108 2011/10/16 11:02:46 dtucker Exp $
 .\"
 .\" Author: Tatu Ylonen <ylo@cs.hut.fi>
 .\" Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -35,7 +35,7 @@
 .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 .\"
-.Dd $Mdocdate: September 7 2011 $
+.Dd $Mdocdate: October 16 2011 $
 .Dt SSH-KEYGEN 1
 .Os
 .Sh NAME
@@ -104,6 +104,7 @@
 .Fl f Ar input_file
 .Op Fl v
 .Op Fl a Ar num_trials
+.Op Fl K Ar checkpt
 .Op Fl W Ar generator
 .Nm ssh-keygen
 .Fl s Ar ca_key
@@ -296,6 +297,14 @@
 .Fl m
 option and print an OpenSSH compatible private
 (or public) key to stdout.
+.It Fl K Ar checkpt
+Write the last line processed to the file
+.Ar checkpt
+while performing DH candidate screening using the
+.Fl T
+option.
+This will be used to skip lines in the input file that have already been
+processed if the job is restarted.
 This option allows importing keys from other software, including several
 commercial SSH implementations.
 The default import format is
diff --git a/ssh-keygen.c b/ssh-keygen.c
index 4b6218b..bd15ccc 100644
--- a/ssh-keygen.c
+++ b/ssh-keygen.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: ssh-keygen.c,v 1.210 2011/04/18 00:46:05 djm Exp $ */
+/* $OpenBSD: ssh-keygen.c,v 1.211 2011/10/16 11:02:46 dtucker Exp $ */
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
  * Copyright (c) 1994 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -154,7 +154,7 @@
 
 /* moduli.c */
 int gen_candidates(FILE *, u_int32_t, u_int32_t, BIGNUM *);
-int prime_test(FILE *, FILE *, u_int32_t, u_int32_t);
+int prime_test(FILE *, FILE *, u_int32_t, u_int32_t, char *);
 
 static void
 type_bits_valid(int type, u_int32_t *bitsp)
@@ -1881,6 +1881,7 @@
 	fprintf(stderr, "  -G file     Generate candidates for DH-GEX moduli.\n");
 	fprintf(stderr, "  -g          Use generic DNS resource record format.\n");
 	fprintf(stderr, "  -H          Hash names in known_hosts file.\n");
+	fprintf(stderr, "  -K checkpt  Write checkpoints to this file.\n");
 	fprintf(stderr, "  -h          Generate host certificate instead of a user certificate.\n");
 	fprintf(stderr, "  -I key_id   Key identifier to include in certificate.\n");
 	fprintf(stderr, "  -i          Import foreign format to OpenSSH key file.\n");
@@ -1916,6 +1917,7 @@
 main(int argc, char **argv)
 {
 	char dotsshdir[MAXPATHLEN], comment[1024], *passphrase1, *passphrase2;
+	char *checkpoint = NULL;
 	char out_file[MAXPATHLEN], *rr_hostname = NULL;
 	Key *private, *public;
 	struct passwd *pw;
@@ -1952,7 +1954,7 @@
 		exit(1);
 	}
 
-	while ((opt = getopt(argc, argv, "AegiqpclBHLhvxXyF:b:f:t:D:I:P:m:N:n:"
+	while ((opt = getopt(argc, argv, "AegiqpclBHLhvxXyF:b:f:t:D:I:K:P:m:N:n:"
 	    "O:C:r:g:R:T:G:M:S:s:a:V:W:z:")) != -1) {
 		switch (opt) {
 		case 'A':
@@ -2103,6 +2105,11 @@
 			    sizeof(out_file))
 				fatal("Output filename too long");
 			break;
+		case 'K':
+			if (strlen(optarg) >= MAXPATHLEN)
+				fatal("Checkpoint filename too long");
+			checkpoint = xstrdup(optarg);
+			break;
 		case 'S':
 			/* XXX - also compare length against bits */
 			if (BN_hex2bn(&start, optarg) == 0)
@@ -2225,7 +2232,8 @@
 			fatal("Couldn't open moduli file \"%s\": %s",
 			    out_file, strerror(errno));
 		}
-		if (prime_test(in, out, trials, generator_wanted) != 0)
+		if (prime_test(in, out, trials, generator_wanted, checkpoint)
+		    != 0)
 			fatal("modulus screening failed");
 		return (0);
 	}