From 79d3c1783b8a9cc7879a2f10b676bfa058731324 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sun, 23 Feb 2020 15:29:40 +0000 Subject: [PATCH] New vtable API for keygen progress reporting. The old API was one of those horrible things I used to do when I was young and foolish, in which you have just one function, and indicate which of lots of things it's doing by passing in flags. It was crying out to be replaced with a vtable. While I'm at it, I've reworked the code on the Windows side that decides what to do with the progress bar, so that it's based on actually justifiable estimates of probability rather than magic integer constants. Since computers are generally faster now than they were at the start of this project, I've also decided there's no longer any point in making the fixed final part of RSA key generation bother to report progress at all. So the progress bars are now only for the variable part, i.e. the actual prime generations. (This is a reapplication of commit a7bdefb39, without the Miller-Rabin refactoring accidentally folded into it. Also this time I've added -lm to the link options, which for some reason _didn't_ cause me a link failure last time round. No idea why not.) --- cmdgen.c | 70 +++++++++---------- configure.ac | 2 + ssh.h | 20 ------ ssh1login-server.c | 6 +- ssh2kex-server.c | 12 ++-- sshdssg.c | 74 ++++++++------------ sshecdsag.c | 7 +- sshkeygen.h | 68 +++++++++++++++++- sshprime.c | 90 ++++++++++++++++++++---- sshrsag.c | 56 +++------------ testcrypt.c | 12 ++-- windows/winpgen.c | 168 ++++++++++++++++++++++++++------------------- 12 files changed, 334 insertions(+), 251 deletions(-) diff --git a/cmdgen.c b/cmdgen.c index ebc55553..6d06c443 100644 --- a/cmdgen.c +++ b/cmdgen.c @@ -13,36 +13,35 @@ #include "putty.h" #include "ssh.h" +#include "sshkeygen.h" #include "mpint.h" -struct progress { - int phase, current; +FILE *progress_fp = NULL; + +static void cmdgen_progress_report_attempt(ProgressReceiver *prog) +{ + if (progress_fp) { + fputc('+', progress_fp); + fflush(progress_fp); + } +} +static void cmdgen_progress_report_phase_complete(ProgressReceiver *prog) +{ + if (progress_fp) { + fputc('\n', progress_fp); + fflush(progress_fp); + } +} + +static const ProgressReceiverVtable cmdgen_progress_vt = { + null_progress_add_probabilistic, + null_progress_ready, + null_progress_start_phase, + cmdgen_progress_report_attempt, + cmdgen_progress_report_phase_complete, }; -static void progress_update(void *param, int action, int phase, int iprogress) -{ - struct progress *p = (struct progress *)param; - if (action != PROGFN_PROGRESS) - return; - if (phase > p->phase) { - if (p->phase >= 0) - fputc('\n', stderr); - p->phase = phase; - if (iprogress >= 0) - p->current = iprogress - 1; - else - p->current = iprogress; - } - while (p->current < iprogress) { - fputc('+', stdout); - p->current++; - } - fflush(stdout); -} - -static void no_progress(void *param, int action, int phase, int iprogress) -{ -} +static ProgressReceiver cmdgen_progress = { .vt = &cmdgen_progress_vt }; /* * Stubs to let everything else link sensibly. @@ -184,10 +183,12 @@ int main(int argc, char **argv) char *ssh2alg = NULL; char *old_passphrase = NULL, *new_passphrase = NULL; bool load_encrypted; - progfn_t progressfn = is_interactive() ? progress_update : no_progress; const char *random_device = NULL; int exit_status = 0; + if (is_interactive()) + progress_fp = stderr; + #define RETURN(status) do { exit_status = (status); goto out; } while (0) /* ------------------------------------------------------------------ @@ -333,7 +334,7 @@ int main(int argc, char **argv) outtype = PUBLIC; break; case 'q': - progressfn = no_progress; + progress_fp = NULL; break; } break; @@ -645,10 +646,6 @@ int main(int argc, char **argv) char *entropy; char default_comment[80]; struct tm tm; - struct progress prog; - - prog.phase = -1; - prog.current = -1; tm = ltime(); if (keytype == DSA) @@ -673,25 +670,25 @@ int main(int argc, char **argv) if (keytype == DSA) { struct dss_key *dsskey = snew(struct dss_key); - dsa_generate(dsskey, bits, progressfn, &prog); + dsa_generate(dsskey, bits, &cmdgen_progress); ssh2key = snew(ssh2_userkey); ssh2key->key = &dsskey->sshk; ssh1key = NULL; } else if (keytype == ECDSA) { struct ecdsa_key *ek = snew(struct ecdsa_key); - ecdsa_generate(ek, bits, progressfn, &prog); + ecdsa_generate(ek, bits); ssh2key = snew(ssh2_userkey); ssh2key->key = &ek->sshk; ssh1key = NULL; } else if (keytype == ED25519) { struct eddsa_key *ek = snew(struct eddsa_key); - eddsa_generate(ek, bits, progressfn, &prog); + eddsa_generate(ek, bits); ssh2key = snew(ssh2_userkey); ssh2key->key = &ek->sshk; ssh1key = NULL; } else { RSAKey *rsakey = snew(RSAKey); - rsa_generate(rsakey, bits, progressfn, &prog); + rsa_generate(rsakey, bits, &cmdgen_progress); rsakey->comment = NULL; if (keytype == RSA1) { ssh1key = rsakey; @@ -700,7 +697,6 @@ int main(int argc, char **argv) ssh2key->key = &rsakey->sshk; } } - progressfn(&prog, PROGFN_PROGRESS, INT_MAX, -1); if (ssh2key) ssh2key->comment = dupstr(default_comment); diff --git a/configure.ac b/configure.ac index 6d94f041..c5bbd454 100644 --- a/configure.ac +++ b/configure.ac @@ -204,6 +204,8 @@ else AC_SUBST(WARNINGOPTS, []) fi +AC_SEARCH_LIBS([pow], [m]) + AC_OUTPUT if test "$gtk_version_desired" = "no"; then cat <hostkey->bytes + 256; s->servkey = snew(RSAKey); - rsa_generate(s->servkey, server_key_bits, no_progress, NULL); + ProgressReceiver null_progress; + null_progress.vt = &null_progress_vt; + rsa_generate(s->servkey, server_key_bits, &null_progress); s->servkey->comment = NULL; s->servkey_generated_here = true; } diff --git a/ssh2kex-server.c b/ssh2kex-server.c index 3a1907d9..d2aef99d 100644 --- a/ssh2kex-server.c +++ b/ssh2kex-server.c @@ -36,10 +36,6 @@ static strbuf *finalise_and_sign_exhash(struct ssh2_transport_state *s) return sb; } -static void no_progress(void *param, int action, int phase, int iprogress) -{ -} - void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) { PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */ @@ -101,7 +97,9 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) * group! It's good enough for testing a client against, * but not for serious use. */ - s->p = primegen(pcs_new(s->pbits), 1, no_progress, NULL); + ProgressReceiver null_progress; + null_progress.vt = &null_progress_vt; + s->p = primegen(pcs_new(s->pbits), &null_progress); s->g = mp_from_integer(2); s->dh_ctx = dh_setup_gex(s->p, s->g); s->kex_init_value = SSH2_MSG_KEX_DH_GEX_INIT; @@ -263,7 +261,9 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) ppl_logevent("Generating a %d-bit RSA key", extra->minklen); s->rsa_kex_key = snew(RSAKey); - rsa_generate(s->rsa_kex_key, extra->minklen, no_progress, NULL); + ProgressReceiver null_progress; + null_progress.vt = &null_progress_vt; + rsa_generate(s->rsa_kex_key, extra->minklen, &null_progress); s->rsa_kex_key->comment = NULL; s->rsa_kex_key_needs_freeing = true; } diff --git a/sshdssg.c b/sshdssg.c index 79e11080..eba3c4e7 100644 --- a/sshdssg.c +++ b/sshdssg.c @@ -7,71 +7,50 @@ #include "sshkeygen.h" #include "mpint.h" -int dsa_generate(struct dss_key *key, int bits, progfn_t pfn, - void *pfnparam) +int dsa_generate(struct dss_key *key, int bits, ProgressReceiver *prog) { /* - * Set up the phase limits for the progress report. We do this - * by passing minus the phase number. + * Progress-reporting setup. * - * For prime generation: our initial filter finds things - * coprime to everything below 2^16. Computing the product of - * (p-1)/p for all prime p below 2^16 gives about 20.33; so - * among B-bit integers, one in every 20.33 will get through - * the initial filter to be a candidate prime. + * DSA generation involves three potentially long jobs: inventing + * the small prime q, the large prime p, and finding an order-q + * element of the multiplicative group of p. * - * Meanwhile, we are searching for primes in the region of 2^B; - * since pi(x) ~ x/log(x), when x is in the region of 2^B, the - * prime density will be d/dx pi(x) ~ 1/log(B), i.e. about - * 1/0.6931B. So the chance of any given candidate being prime - * is 20.33/0.6931B, which is roughly 29.34 divided by B. + * The latter is done by finding an element whose order is + * _divisible_ by q and raising it to the power of (p-1)/q. Every + * element whose order is not divisible by q is a qth power of q + * distinct elements whose order _is_ divisible by q, so the + * probability of not finding a suitable element on the first try + * is in the region of 1/q, i.e. at most 2^-159. * - * So now we have this probability P, we're looking at an - * exponential distribution with parameter P: we will manage in - * one attempt with probability P, in two with probability - * P(1-P), in three with probability P(1-P)^2, etc. The - * probability that we have still not managed to find a prime - * after N attempts is (1-P)^N. - * - * We therefore inform the progress indicator of the number B - * (29.34/B), so that it knows how much to increment by each - * time. We do this in 16-bit fixed point, so 29.34 becomes - * 0x1D.57C4. + * (So the probability of success will end up indistinguishable + * from 1 in IEEE standard floating point! But what can you do.) */ - pfn(pfnparam, PROGFN_PHASE_EXTENT, 1, 0x2800); - pfn(pfnparam, PROGFN_EXP_PHASE, 1, -0x1D57C4 / 160); - pfn(pfnparam, PROGFN_PHASE_EXTENT, 2, 0x40 * bits); - pfn(pfnparam, PROGFN_EXP_PHASE, 2, -0x1D57C4 / bits); - - /* - * In phase three we are finding an order-q element of the - * multiplicative group of p, by finding an element whose order - * is _divisible_ by q and raising it to the power of (p-1)/q. - * _Most_ elements will have order divisible by q, since for a - * start phi(p) of them will be primitive roots. So - * realistically we don't need to set this much below 1 (64K). - * Still, we'll set it to 1/2 (32K) to be on the safe side. - */ - pfn(pfnparam, PROGFN_PHASE_EXTENT, 3, 0x2000); - pfn(pfnparam, PROGFN_EXP_PHASE, 3, -32768); - - pfn(pfnparam, PROGFN_READY, 0, 0); + ProgressPhase phase_q = primegen_add_progress_phase(prog, 160); + ProgressPhase phase_p = primegen_add_progress_phase(prog, bits); + ProgressPhase phase_g = progress_add_probabilistic( + prog, estimate_modexp_cost(bits), 1.0 - 0x1.0p-159); + progress_ready(prog); PrimeCandidateSource *pcs; /* * Generate q: a prime of length 160. */ + progress_start_phase(prog, phase_q); pcs = pcs_new(160); - mp_int *q = primegen(pcs, 1, pfn, pfnparam); + mp_int *q = primegen(pcs, prog); + progress_report_phase_complete(prog); /* * Now generate p: a prime of length `bits', such that p-1 is * divisible by q. */ + progress_start_phase(prog, phase_p); pcs = pcs_new(bits); pcs_require_residue_1(pcs, q); - mp_int *p = primegen(pcs, 2, pfn, pfnparam); + mp_int *p = primegen(pcs, prog); + progress_report_phase_complete(prog); /* * Next we need g. Raise 2 to the power (p-1)/q modulo p, and @@ -79,12 +58,12 @@ int dsa_generate(struct dss_key *key, int bits, progfn_t pfn, * soon as we hit a non-unit (and non-zero!) one, that'll do * for g. */ + progress_start_phase(prog, phase_g); mp_int *power = mp_div(p, q); /* this is floor(p/q) == (p-1)/q */ mp_int *h = mp_from_integer(1); - int progress = 0; mp_int *g; while (1) { - pfn(pfnparam, PROGFN_PROGRESS, 3, ++progress); + progress_report_attempt(prog); g = mp_modpow(h, power, p); if (mp_hs_integer(g, 2)) break; /* got one */ @@ -93,6 +72,7 @@ int dsa_generate(struct dss_key *key, int bits, progfn_t pfn, } mp_free(h); mp_free(power); + progress_report_phase_complete(prog); /* * Now we're nearly done. All we need now is our private key x, diff --git a/sshecdsag.c b/sshecdsag.c index 37048ea6..28a723b2 100644 --- a/sshecdsag.c +++ b/sshecdsag.c @@ -3,10 +3,10 @@ */ #include "ssh.h" +#include "sshkeygen.h" #include "mpint.h" -int ecdsa_generate(struct ecdsa_key *ek, int bits, - progfn_t pfn, void *pfnparam) +int ecdsa_generate(struct ecdsa_key *ek, int bits) { if (!ec_nist_alg_and_curve_by_bits(bits, &ek->curve, &ek->sshk.vt)) return 0; @@ -20,8 +20,7 @@ int ecdsa_generate(struct ecdsa_key *ek, int bits, return 1; } -int eddsa_generate(struct eddsa_key *ek, int bits, - progfn_t pfn, void *pfnparam) +int eddsa_generate(struct eddsa_key *ek, int bits) { if (!ec_ed_alg_and_curve_by_bits(bits, &ek->curve, &ek->sshk.vt)) return 0; diff --git a/sshkeygen.h b/sshkeygen.h index 2e615ec9..44952d6e 100644 --- a/sshkeygen.h +++ b/sshkeygen.h @@ -67,10 +67,74 @@ void pcs_inspect(PrimeCandidateSource *pcs, mp_int **limit_out, /* Query functions for primegen to use */ unsigned pcs_get_bits(PrimeCandidateSource *pcs); +/* ---------------------------------------------------------------------- + * Callback API that allows key generation to report progress to its + * caller. + */ + +typedef struct ProgressReceiverVtable ProgressReceiverVtable; +typedef struct ProgressReceiver ProgressReceiver; +typedef union ProgressPhase ProgressPhase; + +union ProgressPhase { + int n; + void *p; +}; + +struct ProgressReceiver { + const ProgressReceiverVtable *vt; +}; + +struct ProgressReceiverVtable { + ProgressPhase (*add_probabilistic)(ProgressReceiver *prog, + double cost_per_attempt, + double attempt_probability); + void (*ready)(ProgressReceiver *prog); + void (*start_phase)(ProgressReceiver *prog, ProgressPhase phase); + void (*report_attempt)(ProgressReceiver *prog); + void (*report_phase_complete)(ProgressReceiver *prog); +}; + +static inline ProgressPhase progress_add_probabilistic(ProgressReceiver *prog, + double c, double p) +{ return prog->vt->add_probabilistic(prog, c, p); } +static inline void progress_ready(ProgressReceiver *prog) +{ prog->vt->ready(prog); } +static inline void progress_start_phase( + ProgressReceiver *prog, ProgressPhase phase) +{ prog->vt->start_phase(prog, phase); } +static inline void progress_report_attempt(ProgressReceiver *prog) +{ prog->vt->report_attempt(prog); } +static inline void progress_report_phase_complete(ProgressReceiver *prog) +{ prog->vt->report_phase_complete(prog); } + +ProgressPhase null_progress_add_probabilistic( + ProgressReceiver *prog, double c, double p); +void null_progress_ready(ProgressReceiver *prog); +void null_progress_start_phase(ProgressReceiver *prog, ProgressPhase phase); +void null_progress_report_attempt(ProgressReceiver *prog); +void null_progress_report_phase_complete(ProgressReceiver *prog); +extern const ProgressReceiverVtable null_progress_vt; + +/* A helper function for dreaming up progress cost estimates. */ +double estimate_modexp_cost(unsigned bits); + /* ---------------------------------------------------------------------- * The top-level API for generating primes. */ /* This function consumes and frees the PrimeCandidateSource you give it */ -mp_int *primegen(PrimeCandidateSource *pcs, - int phase, progfn_t pfn, void *pfnparam); +mp_int *primegen(PrimeCandidateSource *pcs, ProgressReceiver *prog); + +/* Estimate how long it will take, and add a phase to a ProgressReceiver */ +ProgressPhase primegen_add_progress_phase(ProgressReceiver *prog, + unsigned bits); + +/* ---------------------------------------------------------------------- + * The overall top-level API for generating entire key pairs. + */ + +int rsa_generate(RSAKey *key, int bits, ProgressReceiver *prog); +int dsa_generate(struct dss_key *key, int bits, ProgressReceiver *prog); +int ecdsa_generate(struct ecdsa_key *key, int bits); +int eddsa_generate(struct eddsa_key *key, int bits); diff --git a/sshprime.c b/sshprime.c index 1953f4ef..50325295 100644 --- a/sshprime.c +++ b/sshprime.c @@ -3,6 +3,8 @@ */ #include +#include + #include "ssh.h" #include "mpint.h" #include "mpunsafe.h" @@ -108,16 +110,51 @@ * all, so after we've seen a -1 we can be sure of seeing nothing * but 1s.) */ -mp_int *primegen(PrimeCandidateSource *pcs, - int phase, progfn_t pfn, void *pfnparam) + +static unsigned miller_rabin_checks_needed(unsigned bits) +{ + /* Table 4.4 from Handbook of Applied Cryptography */ + return (bits >= 1300 ? 2 : bits >= 850 ? 3 : bits >= 650 ? 4 : + bits >= 550 ? 5 : bits >= 450 ? 6 : bits >= 400 ? 7 : + bits >= 350 ? 8 : bits >= 300 ? 9 : bits >= 250 ? 12 : + bits >= 200 ? 15 : bits >= 150 ? 18 : 27); +} + +ProgressPhase primegen_add_progress_phase(ProgressReceiver *prog, + unsigned bits) +{ + /* + * The density of primes near x is 1/(log x). When x is about 2^b, + * that's 1/(b log 2). + * + * But we're only doing the expensive part of the process (the M-R + * checks) for a number that passes the initial winnowing test of + * having no factor less than 2^16 (at least, unless the prime is + * so small that PrimeCandidateSource gives up on that winnowing). + * The density of _those_ numbers is about 1/19.76. So the odds of + * hitting a prime per expensive attempt are boosted by a factor + * of 19.76. + */ + const double log_2 = 0.693147180559945309417232121458; + double winnow_factor = (bits < 32 ? 1.0 : 19.76); + double prob = winnow_factor / (bits * log_2); + + /* + * Estimate the cost of prime generation as the cost of the M-R + * modexps. + */ + double cost = (miller_rabin_checks_needed(bits) * + estimate_modexp_cost(bits)); + return progress_add_probabilistic(prog, cost, prob); +} + +mp_int *primegen(PrimeCandidateSource *pcs, ProgressReceiver *prog) { pcs_ready(pcs); - int progress = 0; - STARTOVER: - pfn(pfnparam, PROGFN_PROGRESS, phase, ++progress); + progress_report_attempt(prog); mp_int *p = pcs_generate(pcs); @@ -125,11 +162,7 @@ mp_int *primegen(PrimeCandidateSource *pcs, * Now apply the Miller-Rabin primality test a few times. First * work out how many checks are needed. */ - unsigned checks = - bits >= 1300 ? 2 : bits >= 850 ? 3 : bits >= 650 ? 4 : - bits >= 550 ? 5 : bits >= 450 ? 6 : bits >= 400 ? 7 : - bits >= 350 ? 8 : bits >= 300 ? 9 : bits >= 250 ? 12 : - bits >= 200 ? 15 : bits >= 150 ? 18 : 27; + unsigned checks = miller_rabin_checks_needed(pcs_get_bits(pcs)); /* * Next, write p-1 as q*2^k. @@ -160,8 +193,6 @@ mp_int *primegen(PrimeCandidateSource *pcs, mp_int *w = mp_random_in_range(two, pm1); monty_import_into(mc, w, w); - pfn(pfnparam, PROGFN_PROGRESS, phase, ++progress); - /* * Compute w^q mod p. */ @@ -209,3 +240,38 @@ mp_int *primegen(PrimeCandidateSource *pcs, pcs_free(pcs); return p; } + +/* ---------------------------------------------------------------------- + * Reusable null implementation of the progress-reporting API. + */ + +ProgressPhase null_progress_add_probabilistic( + ProgressReceiver *prog, double c, double p) { + ProgressPhase ph = { .n = 0 }; + return ph; +} +void null_progress_ready(ProgressReceiver *prog) {} +void null_progress_start_phase(ProgressReceiver *prog, ProgressPhase phase) {} +void null_progress_report_attempt(ProgressReceiver *prog) {} +void null_progress_report_phase_complete(ProgressReceiver *prog) {} +const ProgressReceiverVtable null_progress_vt = { + null_progress_add_probabilistic, + null_progress_ready, + null_progress_start_phase, + null_progress_report_attempt, + null_progress_report_phase_complete, +}; + +/* ---------------------------------------------------------------------- + * Helper function for progress estimation. + */ + +double estimate_modexp_cost(unsigned bits) +{ + /* + * A modexp of n bits goes roughly like O(n^2.58), on the grounds + * that our modmul is O(n^1.58) (Karatsuba) and you need O(n) of + * them in a modexp. + */ + return pow(bits, 2.58); +} diff --git a/sshrsag.c b/sshrsag.c index 5dd09a35..91dbcdb1 100644 --- a/sshrsag.c +++ b/sshrsag.c @@ -14,49 +14,12 @@ static void invent_firstbits(unsigned *one, unsigned *two, unsigned min_separation); -int rsa_generate(RSAKey *key, int bits, progfn_t pfn, - void *pfnparam) +int rsa_generate(RSAKey *key, int bits, ProgressReceiver *prog) { unsigned pfirst, qfirst; key->sshk.vt = &ssh_rsa; - /* - * Set up the phase limits for the progress report. We do this - * by passing minus the phase number. - * - * For prime generation: our initial filter finds things - * coprime to everything below 2^16. Computing the product of - * (p-1)/p for all prime p below 2^16 gives about 20.33; so - * among B-bit integers, one in every 20.33 will get through - * the initial filter to be a candidate prime. - * - * Meanwhile, we are searching for primes in the region of 2^B; - * since pi(x) ~ x/log(x), when x is in the region of 2^B, the - * prime density will be d/dx pi(x) ~ 1/log(B), i.e. about - * 1/0.6931B. So the chance of any given candidate being prime - * is 20.33/0.6931B, which is roughly 29.34 divided by B. - * - * So now we have this probability P, we're looking at an - * exponential distribution with parameter P: we will manage in - * one attempt with probability P, in two with probability - * P(1-P), in three with probability P(1-P)^2, etc. The - * probability that we have still not managed to find a prime - * after N attempts is (1-P)^N. - * - * We therefore inform the progress indicator of the number B - * (29.34/B), so that it knows how much to increment by each - * time. We do this in 16-bit fixed point, so 29.34 becomes - * 0x1D.57C4. - */ - pfn(pfnparam, PROGFN_PHASE_EXTENT, 1, 0x10000); - pfn(pfnparam, PROGFN_EXP_PHASE, 1, -0x1D57C4 / (bits / 2)); - pfn(pfnparam, PROGFN_PHASE_EXTENT, 2, 0x10000); - pfn(pfnparam, PROGFN_EXP_PHASE, 2, -0x1D57C4 / (bits - bits / 2)); - pfn(pfnparam, PROGFN_PHASE_EXTENT, 3, 0x4000); - pfn(pfnparam, PROGFN_LIN_PHASE, 3, 5); - pfn(pfnparam, PROGFN_READY, 0, 0); - /* * We don't generate e; we just use a standard one always. */ @@ -80,15 +43,23 @@ int rsa_generate(RSAKey *key, int bits, progfn_t pfn, int pbits = bits - qbits; assert(pbits >= qbits); + ProgressPhase phase_p = primegen_add_progress_phase(prog, pbits); + ProgressPhase phase_q = primegen_add_progress_phase(prog, qbits); + progress_ready(prog); + PrimeCandidateSource *pcs; + progress_start_phase(prog, phase_p); pcs = pcs_new_with_firstbits(pbits, pfirst, NFIRSTBITS); pcs_avoid_residue_small(pcs, RSA_EXPONENT, 1); - mp_int *p = primegen(pcs, 1, pfn, pfnparam); + mp_int *p = primegen(pcs, prog); + progress_report_phase_complete(prog); + progress_start_phase(prog, phase_q); pcs = pcs_new_with_firstbits(qbits, qfirst, NFIRSTBITS); pcs_avoid_residue_small(pcs, RSA_EXPONENT, 1); - mp_int *q = primegen(pcs, 2, pfn, pfnparam); + mp_int *q = primegen(pcs, prog); + progress_report_phase_complete(prog); /* * Ensure p > q, by swapping them if not. @@ -108,22 +79,17 @@ int rsa_generate(RSAKey *key, int bits, progfn_t pfn, * the other helpful quantities: n=pq, d=e^-1 mod (p-1)(q-1), * and (q^-1 mod p). */ - pfn(pfnparam, PROGFN_PROGRESS, 3, 1); mp_int *modulus = mp_mul(p, q); - pfn(pfnparam, PROGFN_PROGRESS, 3, 2); mp_int *pm1 = mp_copy(p); mp_sub_integer_into(pm1, pm1, 1); mp_int *qm1 = mp_copy(q); mp_sub_integer_into(qm1, qm1, 1); mp_int *phi_n = mp_mul(pm1, qm1); - pfn(pfnparam, PROGFN_PROGRESS, 3, 3); mp_free(pm1); mp_free(qm1); mp_int *private_exponent = mp_invert(exponent, phi_n); - pfn(pfnparam, PROGFN_PROGRESS, 3, 4); mp_free(phi_n); mp_int *iqmp = mp_invert(q, p); - pfn(pfnparam, PROGFN_PROGRESS, 3, 5); /* * Populate the returned structure. diff --git a/testcrypt.c b/testcrypt.c index 7d9d3476..61204bd7 100644 --- a/testcrypt.c +++ b/testcrypt.c @@ -1047,18 +1047,18 @@ strbuf *rsa1_save_sb_wrapper(RSAKey *key, const char *comment, #define return_void(out, expression) (expression) -static void no_progress(void *param, int action, int phase, int iprogress) {} +static ProgressReceiver null_progress = { .vt = &null_progress_vt }; mp_int *primegen_wrapper(PrimeCandidateSource *pcs) { - return primegen(pcs, 0, no_progress, NULL); + return primegen(pcs, &null_progress); } #define primegen primegen_wrapper RSAKey *rsa1_generate(int bits) { RSAKey *rsakey = snew(RSAKey); - rsa_generate(rsakey, bits, no_progress, NULL); + rsa_generate(rsakey, bits, &null_progress); rsakey->comment = NULL; return rsakey; } @@ -1072,7 +1072,7 @@ ssh_key *rsa_generate_wrapper(int bits) ssh_key *dsa_generate_wrapper(int bits) { struct dss_key *dsskey = snew(struct dss_key); - dsa_generate(dsskey, bits, no_progress, NULL); + dsa_generate(dsskey, bits, &null_progress); return &dsskey->sshk; } #define dsa_generate dsa_generate_wrapper @@ -1080,7 +1080,7 @@ ssh_key *dsa_generate_wrapper(int bits) ssh_key *ecdsa_generate_wrapper(int bits) { struct ecdsa_key *ek = snew(struct ecdsa_key); - if (!ecdsa_generate(ek, bits, no_progress, NULL)) { + if (!ecdsa_generate(ek, bits)) { sfree(ek); return NULL; } @@ -1091,7 +1091,7 @@ ssh_key *ecdsa_generate_wrapper(int bits) ssh_key *eddsa_generate_wrapper(int bits) { struct eddsa_key *ek = snew(struct eddsa_key); - if (!eddsa_generate(ek, bits, no_progress, NULL)) { + if (!eddsa_generate(ek, bits)) { sfree(ek); return NULL; } diff --git a/windows/winpgen.c b/windows/winpgen.c index ab12b77c..d1ec8d0b 100644 --- a/windows/winpgen.c +++ b/windows/winpgen.c @@ -9,6 +9,7 @@ #include "putty.h" #include "ssh.h" +#include "sshkeygen.h" #include "licence.h" #include "winsecur.h" @@ -59,76 +60,106 @@ void nonfatal(const char *fmt, ...) } /* ---------------------------------------------------------------------- - * Progress report code. This is really horrible :-) + * ProgressReceiver implementation. */ + #define PROGRESSRANGE 65535 #define MAXPHASE 5 -struct progress { - int nphases; - struct { - bool exponential; - unsigned startpoint, total; - unsigned param, current, n; /* if exponential */ - unsigned mult; /* if linear */ - } phases[MAXPHASE]; - unsigned total, divisor, range; - HWND progbar; + +struct progressphase { + double startpoint, total; + double exp_probability, exp_current_value; }; -static void progress_update(void *param, int action, int phase, int iprogress) -{ - struct progress *p = (struct progress *) param; - unsigned progress = iprogress; - int position; +struct progress { + int nphases; + struct progressphase phases[MAXPHASE], *currphase; - if (action < PROGFN_READY && p->nphases < phase) - p->nphases = phase; - switch (action) { - case PROGFN_INITIALISE: - p->nphases = 0; - break; - case PROGFN_LIN_PHASE: - p->phases[phase-1].exponential = false; - p->phases[phase-1].mult = p->phases[phase].total / progress; - break; - case PROGFN_EXP_PHASE: - p->phases[phase-1].exponential = true; - p->phases[phase-1].param = 0x10000 + progress; - p->phases[phase-1].current = p->phases[phase-1].total; - p->phases[phase-1].n = 0; - break; - case PROGFN_PHASE_EXTENT: - p->phases[phase-1].total = progress; - break; - case PROGFN_READY: { - unsigned total = 0; - int i; - for (i = 0; i < p->nphases; i++) { - p->phases[i].startpoint = total; - total += p->phases[i].total; - } - p->total = total; - p->divisor = ((p->total + PROGRESSRANGE - 1) / PROGRESSRANGE); - p->range = p->total / p->divisor; - SendMessage(p->progbar, PBM_SETRANGE, 0, MAKELPARAM(0, p->range)); - break; - } - case PROGFN_PROGRESS: - if (p->phases[phase-1].exponential) { - while (p->phases[phase-1].n < progress) { - p->phases[phase-1].n++; - p->phases[phase-1].current *= p->phases[phase-1].param; - p->phases[phase-1].current /= 0x10000; - } - position = (p->phases[phase-1].startpoint + - p->phases[phase-1].total - p->phases[phase-1].current); - } else { - position = (p->phases[phase-1].startpoint + - progress * p->phases[phase-1].mult); - } - SendMessage(p->progbar, PBM_SETPOS, position / p->divisor, 0); - break; + double scale; + HWND progbar; + + ProgressReceiver rec; +}; + +static ProgressPhase win_progress_add_probabilistic( + ProgressReceiver *prog, double cost_per_attempt, double probability) { + struct progress *p = container_of(prog, struct progress, rec); + + assert(p->nphases < MAXPHASE); + int phase = p->nphases++; + + p->phases[phase].exp_probability = 1.0 - probability; + p->phases[phase].exp_current_value = 1.0; + /* Expected number of attempts = 1 / probability of attempt succeeding */ + p->phases[phase].total = cost_per_attempt / probability; + + ProgressPhase ph = { .n = phase }; + return ph; +} + +static void win_progress_ready(ProgressReceiver *prog) +{ + struct progress *p = container_of(prog, struct progress, rec); + + double total = 0; + for (int i = 0; i < p->nphases; i++) { + p->phases[i].startpoint = total; + total += p->phases[i].total; } + p->scale = PROGRESSRANGE / total; + + SendMessage(p->progbar, PBM_SETRANGE, 0, MAKELPARAM(0, PROGRESSRANGE)); +} + +static void win_progress_start_phase(ProgressReceiver *prog, + ProgressPhase phase) +{ + struct progress *p = container_of(prog, struct progress, rec); + + assert(phase.n < p->nphases); + p->currphase = &p->phases[phase.n]; +} + +static void win_progress_update(struct progress *p, double phasepos) +{ + double position = (p->currphase->startpoint + + p->currphase->total * phasepos); + position *= p->scale; + if (position < 0) + position = 0; + if (position > PROGRESSRANGE) + position = PROGRESSRANGE; + + SendMessage(p->progbar, PBM_SETPOS, (WPARAM)position, 0); +} + +static void win_progress_report_attempt(ProgressReceiver *prog) +{ + struct progress *p = container_of(prog, struct progress, rec); + + p->currphase->exp_current_value *= p->currphase->exp_probability; + win_progress_update(p, 1.0 - p->currphase->exp_current_value); +} + +static void win_progress_report_phase_complete(ProgressReceiver *prog) +{ + struct progress *p = container_of(prog, struct progress, rec); + + win_progress_update(p, 1.0); +} + +static const ProgressReceiverVtable win_progress_vt = { + win_progress_add_probabilistic, + win_progress_ready, + win_progress_start_phase, + win_progress_report_attempt, + win_progress_report_phase_complete, +}; + +static void win_progress_initialise(struct progress *p) +{ + p->nphases = 0; + p->rec.vt = &win_progress_vt; } struct PassphraseProcStruct { @@ -353,17 +384,16 @@ static DWORD WINAPI generate_key_thread(void *param) struct progress prog; prog.progbar = params->progressbar; - progress_update(&prog, PROGFN_INITIALISE, 0, 0); + win_progress_initialise(&prog); if (params->keytype == DSA) - dsa_generate(params->dsskey, params->key_bits, progress_update, &prog); + dsa_generate(params->dsskey, params->key_bits, &prog.rec); else if (params->keytype == ECDSA) - ecdsa_generate(params->eckey, params->curve_bits, - progress_update, &prog); + ecdsa_generate(params->eckey, params->curve_bits); else if (params->keytype == ED25519) - eddsa_generate(params->edkey, 255, progress_update, &prog); + eddsa_generate(params->edkey, 255); else - rsa_generate(params->key, params->key_bits, progress_update, &prog); + rsa_generate(params->key, params->key_bits, &prog.rec); PostMessage(params->dialog, WM_DONEKEY, 0, 0);