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);