Add support for DSA authentication in SSH2, following clever ideas
[u/mdw/putty] / puttygen.c
index b9edf0d..7c3ef0d 100644 (file)
 /* ----------------------------------------------------------------------
  * Progress report code. This is really horrible :-)
  */
-#define PHASE1TOTAL 0x10000
-#define PHASE2TOTAL 0x10000
-#define PHASE3TOTAL 0x04000
-#define PHASE1START 0
-#define PHASE2START (PHASE1TOTAL)
-#define PHASE3START (PHASE1TOTAL + PHASE2TOTAL)
-#define TOTALTOTAL  (PHASE1TOTAL + PHASE2TOTAL + PHASE3TOTAL)
-#define PROGRESSBIGRANGE 65535
-#define DIVISOR     ((TOTALTOTAL + PROGRESSBIGRANGE - 1) / PROGRESSBIGRANGE)
-#define PROGRESSRANGE (TOTALTOTAL / DIVISOR)
+#define PROGRESSRANGE 65535
+#define MAXPHASE 5
 struct progress {
-    unsigned phase1param, phase1current, phase1n;
-    unsigned phase2param, phase2current, phase2n;
-    unsigned phase3mult;
+    int nphases;
+    struct {
+       int exponential;
+       unsigned startpoint, total;
+       unsigned param, current, n;    /* if exponential */
+       unsigned mult;                 /* if linear */
+    } phases[MAXPHASE];
+    unsigned total, divisor, range;
     HWND progbar;
 };
 
-static void progress_update(void *param, int phase, int iprogress)
+static void progress_update(void *param, int action, int phase, int iprogress)
 {
     struct progress *p = (struct progress *) param;
     unsigned progress = iprogress;
     int position;
 
-    switch (phase) {
-      case -1:
-       p->phase1param = 0x10000 + progress;
-       p->phase1current = 0x10000;
-       p->phase1n = 0;
-       return;
-      case -2:
-       p->phase2param = 0x10000 + progress;
-       p->phase2current = 0x10000;
-       p->phase2n = 0;
-       return;
-      case -3:
-       p->phase3mult = PHASE3TOTAL / progress;
-       return;
-      case 1:
-       while (p->phase1n < progress) {
-           p->phase1n++;
-           p->phase1current *= p->phase1param;
-           p->phase1current /= 0x10000;
-       }
-       position = PHASE1START + 0x10000 - p->phase1current;
+    if (action < PROGFN_READY && p->nphases < phase)
+       p->nphases = phase;
+    switch (action) {
+      case PROGFN_LIN_PHASE:
+       p->phases[phase-1].exponential = 0;
+       p->phases[phase-1].mult = p->phases[phase].total / progress;
+       break;
+      case PROGFN_EXP_PHASE:
+       p->phases[phase-1].exponential = 1;
+       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 2:
-       while (p->phase2n < progress) {
-           p->phase2n++;
-           p->phase2current *= p->phase2param;
-           p->phase2current /= 0x10000;
+      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));
        }
-       position = PHASE2START + 0x10000 - p->phase2current;
        break;
-      case 3:
-       position = PHASE3START + progress * p->phase3mult;
+      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;
-      default:
-       /*
-        * Shouldn't happen, but having a default clause placates
-        * gcc -Wall, which would otherwise complain that
-        * `position' might be used uninitialised.
-        */
-       return;
     }
-
-    SendMessage(p->progbar, PBM_SETPOS, position / DIVISOR, 0);
 }
 
 extern char ver[];
@@ -291,7 +291,9 @@ struct rsa_key_thread_params {
     HWND progressbar;                 /* notify this with progress */
     HWND dialog;                      /* notify this on completion */
     int keysize;                      /* bits in key */
+    int is_dsa;
     struct RSAKey *key;
+    struct dss_key *dsskey;
 };
 static DWORD WINAPI generate_rsa_key_thread(void *param)
 {
@@ -300,7 +302,10 @@ static DWORD WINAPI generate_rsa_key_thread(void *param)
     struct progress prog;
     prog.progbar = params->progressbar;
 
-    rsa_generate(params->key, params->keysize, progress_update, &prog);
+    if (params->is_dsa)
+       dsa_generate(params->dsskey, params->keysize, progress_update, &prog);
+    else
+       rsa_generate(params->key, params->keysize, progress_update, &prog);
 
     PostMessage(params->dialog, WM_DONEKEY, 0, 0);
 
@@ -314,11 +319,12 @@ struct MainDlgState {
     int key_exists;
     int entropy_got, entropy_required, entropy_size;
     int keysize;
-    int ssh2;
+    int ssh2, is_dsa;
     char **commentptr;                /* points to key.comment or ssh2key.comment */
     struct ssh2_userkey ssh2key;
     unsigned *entropy;
     struct RSAKey key;
+    struct dss_key dsskey;
 };
 
 static void hidemany(HWND hwnd, const int *ids, int hideit)
@@ -328,7 +334,7 @@ static void hidemany(HWND hwnd, const int *ids, int hideit)
     }
 }
 
-static void setupbigedit1(HWND hwnd, int id, struct RSAKey *key)
+static void setupbigedit1(HWND hwnd, int id, int idstatic, struct RSAKey *key)
 {
     char *buffer;
     char *dec1, *dec2;
@@ -340,12 +346,15 @@ static void setupbigedit1(HWND hwnd, int id, struct RSAKey *key)
     sprintf(buffer, "%d %s %s %s",
            bignum_bitcount(key->modulus), dec1, dec2, key->comment);
     SetDlgItemText(hwnd, id, buffer);
+    SetDlgItemText(hwnd, idstatic,
+                  "&Public key for pasting into authorized_keys file:");
     sfree(dec1);
     sfree(dec2);
     sfree(buffer);
 }
 
-static void setupbigedit2(HWND hwnd, int id, struct ssh2_userkey *key)
+static void setupbigedit2(HWND hwnd, int id, int idstatic,
+                         struct ssh2_userkey *key)
 {
     unsigned char *pub_blob;
     char *buffer, *p;
@@ -368,10 +377,77 @@ static void setupbigedit2(HWND hwnd, int id, struct ssh2_userkey *key)
     *p++ = ' ';
     strcpy(p, key->comment);
     SetDlgItemText(hwnd, id, buffer);
+    SetDlgItemText(hwnd, idstatic, "&Public key for pasting into "
+                  "OpenSSH authorized_keys2 file:");
     sfree(pub_blob);
     sfree(buffer);
 }
 
+static int save_ssh1_pubkey(char *filename, struct RSAKey *key)
+{
+    char *dec1, *dec2;
+    FILE *fp;
+
+    dec1 = bignum_decimal(key->exponent);
+    dec2 = bignum_decimal(key->modulus);
+    fp = fopen(filename, "wb");
+    if (!fp)
+       return 0;
+    fprintf(fp, "%d %s %s %s\n",
+           bignum_bitcount(key->modulus), dec1, dec2, key->comment);
+    fclose(fp);
+    sfree(dec1);
+    sfree(dec2);
+    return 1;
+}
+
+static int save_ssh2_pubkey(char *filename, struct ssh2_userkey *key)
+{
+    unsigned char *pub_blob;
+    char *p;
+    int pub_len;
+    int i, column;
+    FILE *fp;
+
+    pub_blob = key->alg->public_blob(key->data, &pub_len);
+
+    fp = fopen(filename, "wb");
+    if (!fp)
+       return 0;
+
+    fprintf(fp, "---- BEGIN SSH2 PUBLIC KEY ----\n");
+
+    fprintf(fp, "Comment: \"");
+    for (p = key->comment; *p; p++) {
+       if (*p == '\\' || *p == '\"')
+           fputc('\\', fp);
+       fputc(*p, fp);
+    }
+    fprintf(fp, "\"\n");
+
+    i = 0;
+    column = 0;
+    while (i < pub_len) {
+       char buf[5];
+       int n = (pub_len - i < 3 ? pub_len - i : 3);
+       base64_encode_atom(pub_blob + i, n, buf);
+       i += n;
+       buf[4] = '\0';
+       fputs(buf, fp);
+       if (++column >= 16) {
+           fputc('\n', fp);
+           column = 0;
+       }
+    }
+    if (column > 0)
+       fputc('\n', fp);
+    
+    fprintf(fp, "---- END SSH2 PUBLIC KEY ----\n");
+    fclose(fp);
+    sfree(pub_blob);
+    return 1;
+}
+
 /*
  * Dialog-box function for the main PuTTYgen dialog box.
  */
@@ -393,9 +469,9 @@ static int CALLBACK MainDlgProc(HWND hwnd, UINT msg,
        IDC_BOX_ACTIONS,
        IDC_GENSTATIC, IDC_GENERATE,
        IDC_LOADSTATIC, IDC_LOAD,
-       IDC_SAVESTATIC, IDC_SAVE,
+       IDC_SAVESTATIC, IDC_SAVE, IDC_SAVEPUB,
        IDC_BOX_PARAMS,
-       IDC_TYPESTATIC, IDC_KEYSSH1, IDC_KEYSSH2RSA,
+       IDC_TYPESTATIC, IDC_KEYSSH1, IDC_KEYSSH2RSA, IDC_KEYSSH2DSA,
        IDC_BITSSTATIC, IDC_BITS,
        IDC_ABOUT,
     };
@@ -441,16 +517,16 @@ static int CALLBACK MainDlgProc(HWND hwnd, UINT msg,
        {
            struct ctlpos cp, cp2;
 
-           /* Accelerators used: acglops */
+           /* Accelerators used: acglops1rbd */
 
            ctlposinit(&cp, hwnd, 10, 10, 10);
            bartitle(&cp, "Public and private key generation for PuTTY",
                     IDC_TITLE);
            beginbox(&cp, "Key", IDC_BOX_KEY);
            cp2 = cp;
-           statictext(&cp2, "No key.", IDC_NOKEY);
+           statictext(&cp2, "No key.", 1, IDC_NOKEY);
            cp2 = cp;
-           statictext(&cp2, "", IDC_GENERATING);
+           statictext(&cp2, "", 1, IDC_GENERATING);
            progressbar(&cp2, IDC_PROGRESS);
            bigeditctrl(&cp,
                        "&Public key for pasting into authorized_keys file:",
@@ -472,13 +548,15 @@ static int CALLBACK MainDlgProc(HWND hwnd, UINT msg,
                      IDC_GENSTATIC, "&Generate", IDC_GENERATE);
            staticbtn(&cp, "Load an existing private key file",
                      IDC_LOADSTATIC, "&Load", IDC_LOAD);
-           staticbtn(&cp, "Save the generated key to a new file",
-                     IDC_SAVESTATIC, "&Save", IDC_SAVE);
+           static2btn(&cp, "Save the generated key", IDC_SAVESTATIC,
+                      "Save p&ublic key", IDC_SAVEPUB,
+                      "&Save private key", IDC_SAVE);
            endbox(&cp);
            beginbox(&cp, "Parameters", IDC_BOX_PARAMS);
-           radioline(&cp, "Type of key to generate:", IDC_TYPESTATIC, 2,
+           radioline(&cp, "Type of key to generate:", IDC_TYPESTATIC, 3,
                      "SSH&1 (RSA)", IDC_KEYSSH1,
-                     "SSH2 &RSA", IDC_KEYSSH2RSA, NULL);
+                     "SSH2 &RSA", IDC_KEYSSH2RSA,
+                     "SSH2 &DSA", IDC_KEYSSH2DSA, NULL);
            staticedit(&cp, "Number of &bits in a generated key:",
                       IDC_BITSSTATIC, IDC_BITS, 20);
            endbox(&cp);
@@ -489,13 +567,14 @@ static int CALLBACK MainDlgProc(HWND hwnd, UINT msg,
        /*
         * Initially, hide the progress bar and the key display,
         * and show the no-key display. Also disable the Save
-        * button, because with no key we obviously can't save
+        * buttons, because with no key we obviously can't save
         * anything.
         */
        hidemany(hwnd, nokey_ids, FALSE);
        hidemany(hwnd, generating_ids, TRUE);
        hidemany(hwnd, gotkey_ids, TRUE);
        EnableWindow(GetDlgItem(hwnd, IDC_SAVE), 0);
+       EnableWindow(GetDlgItem(hwnd, IDC_SAVEPUB), 0);
 
        return 1;
       case WM_MOUSEMOVE:
@@ -527,7 +606,9 @@ static int CALLBACK MainDlgProc(HWND hwnd, UINT msg,
                params->progressbar = GetDlgItem(hwnd, IDC_PROGRESS);
                params->dialog = hwnd;
                params->keysize = state->keysize;
+               params->is_dsa = state->is_dsa;
                params->key = &state->key;
+               params->dsskey = &state->dsskey;
 
                if (!CreateThread(NULL, 0, generate_rsa_key_thread,
                                  params, 0, &threadid)) {
@@ -555,10 +636,11 @@ static int CALLBACK MainDlgProc(HWND hwnd, UINT msg,
                    *state->commentptr = smalloc(len + 1);
                    GetWindowText(editctl, *state->commentptr, len + 1);
                    if (state->ssh2) {
-                       setupbigedit2(hwnd, IDC_KEYDISPLAY,
+                       setupbigedit2(hwnd, IDC_KEYDISPLAY, IDC_PKSTATIC,
                                      &state->ssh2key);
                    } else {
-                       setupbigedit1(hwnd, IDC_KEYDISPLAY, &state->key);
+                       setupbigedit1(hwnd, IDC_KEYDISPLAY, IDC_PKSTATIC,
+                                     &state->key);
                    }
                }
            }
@@ -579,6 +661,7 @@ static int CALLBACK MainDlgProc(HWND hwnd, UINT msg,
                    state->keysize = DEFAULT_KEYSIZE;
                /* If we ever introduce a new key type, check it here! */
                state->ssh2 = !IsDlgButtonChecked(hwnd, IDC_KEYSSH1);
+               state->is_dsa = IsDlgButtonChecked(hwnd, IDC_KEYSSH2DSA);
                if (state->keysize < 256) {
                    int ret = MessageBox(hwnd,
                                         "PuTTYgen will not generate a key"
@@ -597,6 +680,10 @@ static int CALLBACK MainDlgProc(HWND hwnd, UINT msg,
                EnableWindow(GetDlgItem(hwnd, IDC_GENERATE), 0);
                EnableWindow(GetDlgItem(hwnd, IDC_LOAD), 0);
                EnableWindow(GetDlgItem(hwnd, IDC_SAVE), 0);
+               EnableWindow(GetDlgItem(hwnd, IDC_SAVEPUB), 0);
+               EnableWindow(GetDlgItem(hwnd, IDC_KEYSSH1), 0);
+               EnableWindow(GetDlgItem(hwnd, IDC_KEYSSH2RSA), 0);
+               EnableWindow(GetDlgItem(hwnd, IDC_BITS), 0);
                state->key_exists = FALSE;
                SetDlgItemText(hwnd, IDC_GENERATING, entropy_msg);
                state->collecting_entropy = TRUE;
@@ -681,6 +768,37 @@ static int CALLBACK MainDlgProc(HWND hwnd, UINT msg,
                }
            }
            break;
+         case IDC_SAVEPUB:
+           state =
+               (struct MainDlgState *) GetWindowLong(hwnd, GWL_USERDATA);
+           if (state->key_exists) {
+               char filename[FILENAME_MAX];
+               if (prompt_keyfile(hwnd, "Save public key as:",
+                                  filename, 1)) {
+                   int ret;
+                   FILE *fp = fopen(filename, "r");
+                   if (fp) {
+                       char buffer[FILENAME_MAX + 80];
+                       fclose(fp);
+                       sprintf(buffer, "Overwrite existing file\n%.*s?",
+                               FILENAME_MAX, filename);
+                       ret = MessageBox(hwnd, buffer, "PuTTYgen Warning",
+                                        MB_YESNO | MB_ICONWARNING);
+                       if (ret != IDYES)
+                           break;
+                   }
+                   if (state->ssh2) {
+                       ret = save_ssh2_pubkey(filename, &state->ssh2key);
+                   } else {
+                       ret = save_ssh1_pubkey(filename, &state->key);
+                   }
+                   if (ret <= 0) {
+                       MessageBox(hwnd, "Unable to save key file",
+                                  "PuTTYgen Error", MB_OK | MB_ICONERROR);
+                   }
+               }
+           }
+           break;
          case IDC_LOAD:
            state =
                (struct MainDlgState *) GetWindowLong(hwnd, GWL_USERDATA);
@@ -747,6 +865,10 @@ static int CALLBACK MainDlgProc(HWND hwnd, UINT msg,
                        EnableWindow(GetDlgItem(hwnd, IDC_GENERATE), 1);
                        EnableWindow(GetDlgItem(hwnd, IDC_LOAD), 1);
                        EnableWindow(GetDlgItem(hwnd, IDC_SAVE), 1);
+                       EnableWindow(GetDlgItem(hwnd, IDC_SAVEPUB), 1);
+                       EnableWindow(GetDlgItem(hwnd, IDC_KEYSSH1), 1);
+                       EnableWindow(GetDlgItem(hwnd, IDC_KEYSSH2RSA), 1);
+                       EnableWindow(GetDlgItem(hwnd, IDC_BITS), 1);
                        /*
                         * Now update the key controls with all the
                         * key data.
@@ -780,7 +902,7 @@ static int CALLBACK MainDlgProc(HWND hwnd, UINT msg,
                                 * .ssh/authorized_keys on a Unix box.
                                 */
                                setupbigedit1(hwnd, IDC_KEYDISPLAY,
-                                             &state->key);
+                                             IDC_PKSTATIC, &state->key);
                            } else {
                                char *fp;
                                char *savecomment;
@@ -802,7 +924,7 @@ static int CALLBACK MainDlgProc(HWND hwnd, UINT msg,
                                sfree(fp);
 
                                setupbigedit2(hwnd, IDC_KEYDISPLAY,
-                                             &state->ssh2key);
+                                             IDC_PKSTATIC, &state->ssh2key);
                            }
                            SetDlgItemText(hwnd, IDC_COMMENTEDIT,
                                           *state->commentptr);
@@ -825,14 +947,24 @@ static int CALLBACK MainDlgProc(HWND hwnd, UINT msg,
        state = (struct MainDlgState *) GetWindowLong(hwnd, GWL_USERDATA);
        state->generation_thread_exists = FALSE;
        state->key_exists = TRUE;
-       SendDlgItemMessage(hwnd, IDC_PROGRESS, PBM_SETPOS, PROGRESSRANGE,
-                          0);
+       SendDlgItemMessage(hwnd, IDC_PROGRESS, PBM_SETRANGE, 0,
+                          MAKELPARAM(0, PROGRESSRANGE));
+       SendDlgItemMessage(hwnd, IDC_PROGRESS, PBM_SETPOS, PROGRESSRANGE, 0);
        EnableWindow(GetDlgItem(hwnd, IDC_GENERATE), 1);
        EnableWindow(GetDlgItem(hwnd, IDC_LOAD), 1);
        EnableWindow(GetDlgItem(hwnd, IDC_SAVE), 1);
+       EnableWindow(GetDlgItem(hwnd, IDC_SAVEPUB), 1);
+       EnableWindow(GetDlgItem(hwnd, IDC_KEYSSH1), 1);
+       EnableWindow(GetDlgItem(hwnd, IDC_KEYSSH2RSA), 1);
+       EnableWindow(GetDlgItem(hwnd, IDC_BITS), 1);
        if (state->ssh2) {
-           state->ssh2key.data = &state->key;
-           state->ssh2key.alg = &ssh_rsa;
+           if (state->is_dsa) {
+               state->ssh2key.data = &state->dsskey;
+               state->ssh2key.alg = &ssh_dss;
+           } else {
+               state->ssh2key.data = &state->key;
+               state->ssh2key.alg = &ssh_rsa;
+           }
            state->commentptr = &state->ssh2key.comment;
        } else {
            state->commentptr = &state->key.comment;
@@ -849,7 +981,10 @@ static int CALLBACK MainDlgProc(HWND hwnd, UINT msg,
            struct tm *tm;
            time(&t);
            tm = localtime(&t);
-           strftime(*state->commentptr, 30, "rsa-key-%Y%m%d", tm);
+           if (state->is_dsa)
+               strftime(*state->commentptr, 30, "dsa-key-%Y%m%d", tm);
+           else
+               strftime(*state->commentptr, 30, "rsa-key-%Y%m%d", tm);
        }
 
        /*
@@ -886,12 +1021,15 @@ static int CALLBACK MainDlgProc(HWND hwnd, UINT msg,
            *state->commentptr = savecomment;
            /*
             * Construct a decimal representation of the key, for
-            * pasting into .ssh/authorized_keys on a Unix box.
+            * pasting into .ssh/authorized_keys or
+            * .ssh/authorized_keys2 on a Unix box.
             */
            if (state->ssh2) {
-               setupbigedit2(hwnd, IDC_KEYDISPLAY, &state->ssh2key);
+               setupbigedit2(hwnd, IDC_KEYDISPLAY,
+                             IDC_PKSTATIC, &state->ssh2key);
            } else {
-               setupbigedit1(hwnd, IDC_KEYDISPLAY, &state->key);
+               setupbigedit1(hwnd, IDC_KEYDISPLAY,
+                             IDC_PKSTATIC, &state->key);
            }
        }
        /*