Upgrade libgit2
[TortoiseGit.git] / src / TortoisePlink / SSHPUBK.C
blob14bf4470d8f5f5253686f1b7cb7c62b92bef57cd
1 /*\r
2  * Generic SSH public-key handling operations. In particular,\r
3  * reading of SSH public-key files, and also the generic `sign'\r
4  * operation for SSH-2 (which checks the type of the key and\r
5  * dispatches to the appropriate key-type specific function).\r
6  */\r
7 \r
8 #include <stdio.h>\r
9 #include <string.h>\r
10 #include <errno.h>\r
11 #include <stdlib.h>\r
12 #include <assert.h>\r
13 #include <ctype.h>\r
15 #include "putty.h"\r
16 #include "mpint.h"\r
17 #include "ssh.h"\r
18 #include "misc.h"\r
20 /*\r
21  * Fairly arbitrary size limit on any public or private key blob.\r
22  * Chosen to match AGENT_MAX_MSGLEN, on the basis that any key too\r
23  * large to transfer over the ssh-agent protocol is probably too large\r
24  * to be useful in general.\r
25  *\r
26  * MAX_KEY_BLOB_LINES is the corresponding limit on the Public-Lines\r
27  * or Private-Lines header field in a key file.\r
28  */\r
29 #define MAX_KEY_BLOB_SIZE 262144\r
30 #define MAX_KEY_BLOB_LINES (MAX_KEY_BLOB_SIZE / 48)\r
32 /*\r
33  * Corresponding limit on the size of a key _file_ itself, based on\r
34  * base64-encoding the key blob and then adding a few Kb for\r
35  * surrounding metadata.\r
36  */\r
37 #define MAX_KEY_FILE_SIZE (MAX_KEY_BLOB_SIZE * 4 / 3 + 4096)\r
39 static const ptrlen rsa1_signature =\r
40     PTRLEN_DECL_LITERAL("SSH PRIVATE KEY FILE FORMAT 1.1\n\0");\r
42 #define BASE64_TOINT(x) ( (x)-'A'<26 ? (x)-'A'+0 :\\r
43                           (x)-'a'<26 ? (x)-'a'+26 :\\r
44                           (x)-'0'<10 ? (x)-'0'+52 :\\r
45                           (x)=='+' ? 62 : \\r
46                           (x)=='/' ? 63 : 0 )\r
48 LoadedFile *lf_new(size_t max_size)\r
49 {\r
50     LoadedFile *lf = snew_plus(LoadedFile, max_size);\r
51     lf->data = snew_plus_get_aux(lf);\r
52     lf->len = 0;\r
53     lf->max_size = max_size;\r
54     return lf;\r
55 }\r
57 void lf_free(LoadedFile *lf)\r
58 {\r
59     smemclr(lf->data, lf->max_size);\r
60     smemclr(lf, sizeof(LoadedFile));\r
61     sfree(lf);\r
62 }\r
64 LoadFileStatus lf_load_fp(LoadedFile *lf, FILE *fp)\r
65 {\r
66     lf->len = 0;\r
67     while (lf->len < lf->max_size) {\r
68         size_t retd = fread(lf->data + lf->len, 1, lf->max_size - lf->len, fp);\r
69         if (ferror(fp))\r
70             return LF_ERROR;\r
72         if (retd == 0)\r
73             break;\r
75         lf->len += retd;\r
76     }\r
78     LoadFileStatus status = LF_OK;\r
80     if (lf->len == lf->max_size) {\r
81         /* The file might be too long to fit in our fixed-size\r
82          * structure. Try reading one more byte, to check. */\r
83         if (fgetc(fp) != EOF)\r
84             status = LF_TOO_BIG;\r
85     }\r
87     BinarySource_INIT(lf, lf->data, lf->len);\r
89     return status;\r
90 }\r
92 LoadFileStatus lf_load(LoadedFile *lf, const Filename *filename)\r
93 {\r
94     FILE *fp = f_open(filename, "rb", false);\r
95     if (!fp)\r
96         return LF_ERROR;\r
98     LoadFileStatus status = lf_load_fp(lf, fp);\r
99     fclose(fp);\r
100     return status;\r
103 static inline bool lf_load_keyfile_helper(LoadFileStatus status,\r
104                                           const char **errptr)\r
106     const char *error;\r
107     switch (status) {\r
108       case LF_OK:\r
109         return true;\r
110       case LF_TOO_BIG:\r
111         error = "file is too large to be a key file";\r
112         break;\r
113       case LF_ERROR:\r
114         error = strerror(errno);\r
115         break;\r
116       default:\r
117         unreachable("bad status value in lf_load_keyfile_helper");\r
118     }\r
119     if (errptr)\r
120         *errptr = error;\r
121     return false;\r
124 LoadedFile *lf_load_keyfile(const Filename *filename, const char **errptr)\r
126     LoadedFile *lf = lf_new(MAX_KEY_FILE_SIZE);\r
127     if (!lf_load_keyfile_helper(lf_load(lf, filename), errptr)) {\r
128         lf_free(lf);\r
129         return NULL;\r
130     }\r
131     return lf;\r
134 LoadedFile *lf_load_keyfile_fp(FILE *fp, const char **errptr)\r
136     LoadedFile *lf = lf_new(MAX_KEY_FILE_SIZE);\r
137     if (!lf_load_keyfile_helper(lf_load_fp(lf, fp), errptr)) {\r
138         lf_free(lf);\r
139         return NULL;\r
140     }\r
141     return lf;\r
144 static bool expect_signature(BinarySource *src, ptrlen realsig)\r
146     ptrlen thissig = get_data(src, realsig.len);\r
147     return !get_err(src) && ptrlen_eq_ptrlen(realsig, thissig);\r
150 static int rsa1_load_s_internal(BinarySource *src, RSAKey *key, bool pub_only,\r
151                                 char **commentptr, const char *passphrase,\r
152                                 const char **error)\r
154     strbuf *buf = NULL;\r
155     int ciphertype;\r
156     int ret = 0;\r
157     ptrlen comment;\r
159     *error = "not an SSH-1 RSA file";\r
161     if (!expect_signature(src, rsa1_signature))\r
162         goto end;\r
164     *error = "file format error";\r
166     /* One byte giving encryption type, and one reserved uint32. */\r
167     ciphertype = get_byte(src);\r
168     if (ciphertype != 0 && ciphertype != SSH1_CIPHER_3DES)\r
169         goto end;\r
170     if (get_uint32(src) != 0)\r
171         goto end;                 /* reserved field nonzero, panic! */\r
173     /* Now the serious stuff. An ordinary SSH-1 public key. */\r
174     get_rsa_ssh1_pub(src, key, RSA_SSH1_MODULUS_FIRST);\r
176     /* Next, the comment field. */\r
177     comment = get_string(src);\r
178     if (commentptr)\r
179         *commentptr = mkstr(comment);\r
180     if (key)\r
181         key->comment = mkstr(comment);\r
183     if (pub_only) {\r
184         ret = 1;\r
185         goto end;\r
186     }\r
188     if (!key) {\r
189         ret = ciphertype != 0;\r
190         *error = NULL;\r
191         goto end;\r
192     }\r
194     /*\r
195      * Decrypt remainder of buffer.\r
196      */\r
197     if (ciphertype) {\r
198         size_t enclen = get_avail(src);\r
199         if (enclen & 7)\r
200             goto end;\r
202         buf = strbuf_dup_nm(get_data(src, enclen));\r
204         unsigned char keybuf[16];\r
205         hash_simple(&ssh_md5, ptrlen_from_asciz(passphrase), keybuf);\r
206         des3_decrypt_pubkey(keybuf, buf->u, enclen);\r
207         smemclr(keybuf, sizeof(keybuf));        /* burn the evidence */\r
209         BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf(buf));\r
210     }\r
212     /*\r
213      * We are now in the secret part of the key. The first four\r
214      * bytes should be of the form a, b, a, b.\r
215      */\r
216     {\r
217         int b0a = get_byte(src);\r
218         int b1a = get_byte(src);\r
219         int b0b = get_byte(src);\r
220         int b1b = get_byte(src);\r
221         if (b0a != b0b || b1a != b1b) {\r
222             *error = "wrong passphrase";\r
223             ret = -1;\r
224             goto end;\r
225         }\r
226     }\r
228     /*\r
229      * After that, we have one further bignum which is our\r
230      * decryption exponent, and then the three auxiliary values\r
231      * (iqmp, q, p).\r
232      */\r
233     get_rsa_ssh1_priv(src, key);\r
234     key->iqmp = get_mp_ssh1(src);\r
235     key->q = get_mp_ssh1(src);\r
236     key->p = get_mp_ssh1(src);\r
238     if (!rsa_verify(key)) {\r
239         *error = "rsa_verify failed";\r
240         freersakey(key);\r
241         ret = 0;\r
242     } else {\r
243         *error = NULL;\r
244         ret = 1;\r
245     }\r
247   end:\r
248     if (buf)\r
249         strbuf_free(buf);\r
250     return ret;\r
253 int rsa1_load_s(BinarySource *src, RSAKey *key,\r
254                 const char *passphrase, const char **errstr)\r
256     return rsa1_load_s_internal(src, key, false, NULL, passphrase, errstr);\r
259 int rsa1_load_f(const Filename *filename, RSAKey *key,\r
260                 const char *passphrase, const char **errstr)\r
262     LoadedFile *lf = lf_load_keyfile(filename, errstr);\r
263     if (!lf)\r
264         return false;\r
266     int toret = rsa1_load_s(BinarySource_UPCAST(lf), key, passphrase, errstr);\r
267     lf_free(lf);\r
268     return toret;\r
271 /*\r
272  * See whether an RSA key is encrypted. Return its comment field as\r
273  * well.\r
274  */\r
275 bool rsa1_encrypted_s(BinarySource *src, char **comment)\r
277     const char *dummy;\r
278     return rsa1_load_s_internal(src, NULL, false, comment, NULL, &dummy) == 1;\r
281 bool rsa1_encrypted_f(const Filename *filename, char **comment)\r
283     LoadedFile *lf = lf_load_keyfile(filename, NULL);\r
284     if (!lf)\r
285         return false; /* couldn't even open the file */\r
287     bool toret = rsa1_encrypted_s(BinarySource_UPCAST(lf), comment);\r
288     lf_free(lf);\r
289     return toret;\r
292 /*\r
293  * Read the public part of an SSH-1 RSA key from a file (public or\r
294  * private), and generate its public blob in exponent-first order.\r
295  */\r
296 int rsa1_loadpub_s(BinarySource *src, BinarySink *bs,\r
297                    char **commentptr, const char **errorstr)\r
299     RSAKey key;\r
300     int ret;\r
301     const char *error = NULL;\r
303     /* Default return if we fail. */\r
304     ret = 0;\r
306     bool is_privkey_file = expect_signature(src, rsa1_signature);\r
307     BinarySource_REWIND(src);\r
309     if (is_privkey_file) {\r
310         /*\r
311          * Load just the public half from an SSH-1 private key file.\r
312          */\r
313         memset(&key, 0, sizeof(key));\r
314         if (rsa1_load_s_internal(src, &key, true, commentptr, NULL, &error)) {\r
315             rsa_ssh1_public_blob(bs, &key, RSA_SSH1_EXPONENT_FIRST);\r
316             freersakey(&key);\r
317             ret = 1;\r
318         }\r
319     } else {\r
320         /*\r
321          * Try interpreting the file as an SSH-1 public key.\r
322          */\r
323         char *line, *p, *bitsp, *expp, *modp, *commentp;\r
325         line = mkstr(get_chomped_line(src));\r
326         p = line;\r
328         bitsp = p;\r
329         p += strspn(p, "0123456789");\r
330         if (*p != ' ')\r
331             goto not_public_either;\r
332         *p++ = '\0';\r
334         expp = p;\r
335         p += strspn(p, "0123456789");\r
336         if (*p != ' ')\r
337             goto not_public_either;\r
338         *p++ = '\0';\r
340         modp = p;\r
341         p += strspn(p, "0123456789");\r
342         if (*p) {\r
343             if (*p != ' ')\r
344                 goto not_public_either;\r
345             *p++ = '\0';\r
346             commentp = p;\r
347         } else {\r
348             commentp = NULL;\r
349         }\r
351         memset(&key, 0, sizeof(key));\r
352         key.exponent = mp_from_decimal(expp);\r
353         key.modulus = mp_from_decimal(modp);\r
354         if (atoi(bitsp) != mp_get_nbits(key.modulus)) {\r
355             mp_free(key.exponent);\r
356             mp_free(key.modulus);\r
357             sfree(line);\r
358             error = "key bit count does not match in SSH-1 public key file";\r
359             goto end;\r
360         }\r
361         if (commentptr)\r
362             *commentptr = commentp ? dupstr(commentp) : NULL;\r
363         rsa_ssh1_public_blob(bs, &key, RSA_SSH1_EXPONENT_FIRST);\r
364         freersakey(&key);\r
365         sfree(line);\r
366         return 1;\r
368       not_public_either:\r
369         sfree(line);\r
370         error = "not an SSH-1 RSA file";\r
371     }\r
373   end:\r
374     if ((ret != 1) && errorstr)\r
375         *errorstr = error;\r
376     return ret;\r
379 int rsa1_loadpub_f(const Filename *filename, BinarySink *bs,\r
380                    char **commentptr, const char **errorstr)\r
382     LoadedFile *lf = lf_load_keyfile(filename, errorstr);\r
383     if (!lf)\r
384         return 0;\r
386     int toret = rsa1_loadpub_s(BinarySource_UPCAST(lf), bs,\r
387                                commentptr, errorstr);\r
388     lf_free(lf);\r
389     return toret;\r
392 strbuf *rsa1_save_sb(RSAKey *key, const char *passphrase)\r
394     strbuf *buf = strbuf_new_nm();\r
395     int estart;\r
397     /*\r
398      * The public part of the key.\r
399      */\r
400     put_datapl(buf, rsa1_signature);\r
401     put_byte(buf, passphrase ? SSH1_CIPHER_3DES : 0); /* encryption type */\r
402     put_uint32(buf, 0);                              /* reserved */\r
403     rsa_ssh1_public_blob(BinarySink_UPCAST(buf), key,\r
404                          RSA_SSH1_MODULUS_FIRST);\r
405     put_stringz(buf, NULLTOEMPTY(key->comment));\r
407     /*\r
408      * The encrypted portion starts here.\r
409      */\r
410     estart = buf->len;\r
412     /*\r
413      * Two bytes, then the same two bytes repeated.\r
414      */\r
415     {\r
416         uint8_t bytes[2];\r
417         random_read(bytes, 2);\r
418         put_data(buf, bytes, 2);\r
419         put_data(buf, bytes, 2);\r
420     }\r
422     /*\r
423      * Four more bignums: the decryption exponent, then iqmp, then\r
424      * q, then p.\r
425      */\r
426     put_mp_ssh1(buf, key->private_exponent);\r
427     put_mp_ssh1(buf, key->iqmp);\r
428     put_mp_ssh1(buf, key->q);\r
429     put_mp_ssh1(buf, key->p);\r
431     /*\r
432      * Now write zeros until the encrypted portion is a multiple of\r
433      * 8 bytes.\r
434      */\r
435     put_padding(buf, (estart - buf->len) & 7, 0);\r
437     /*\r
438      * Now encrypt the encrypted portion.\r
439      */\r
440     if (passphrase) {\r
441         unsigned char keybuf[16];\r
443         hash_simple(&ssh_md5, ptrlen_from_asciz(passphrase), keybuf);\r
444         des3_encrypt_pubkey(keybuf, buf->u + estart, buf->len - estart);\r
445         smemclr(keybuf, sizeof(keybuf));        /* burn the evidence */\r
446     }\r
448     return buf;\r
451 /*\r
452  * Save an RSA key file. Return true on success.\r
453  */\r
454 bool rsa1_save_f(const Filename *filename, RSAKey *key, const char *passphrase)\r
456     FILE *fp = f_open(filename, "wb", true);\r
457     if (!fp)\r
458         return false;\r
460     strbuf *buf = rsa1_save_sb(key, passphrase);\r
461     bool toret = fwrite(buf->s, 1, buf->len, fp) == buf->len;\r
462     if (fclose(fp))\r
463         toret = false;\r
464     strbuf_free(buf);\r
465     return toret;\r
468 /* ----------------------------------------------------------------------\r
469  * SSH-2 private key load/store functions.\r
470  *\r
471  * PuTTY's own file format for SSH-2 keys is given in doc/ppk.but, aka\r
472  * the "PPK file format" appendix in the PuTTY manual.\r
473  */\r
475 static bool read_header(BinarySource *src, char *header)\r
477     int len = 39;\r
478     int c;\r
480     while (1) {\r
481         c = get_byte(src);\r
482         if (c == '\n' || c == '\r' || get_err(src))\r
483             return false;              /* failure */\r
484         if (c == ':') {\r
485             c = get_byte(src);\r
486             if (c != ' ')\r
487                 return false;\r
488             *header = '\0';\r
489             return true;               /* success! */\r
490         }\r
491         if (len == 0)\r
492             return false;              /* failure */\r
493         *header++ = c;\r
494         len--;\r
495     }\r
496     return false;                      /* failure */\r
499 static char *read_body(BinarySource *src)\r
501     strbuf *buf = strbuf_new_nm();\r
503     while (1) {\r
504         int c = get_byte(src);\r
505         if (c == '\r' || c == '\n' || get_err(src)) {\r
506             if (!get_err(src)) {\r
507                 c = get_byte(src);\r
508                 if (c != '\r' && c != '\n' && !get_err(src))\r
509                     src->pos--;\r
510             }\r
511             return strbuf_to_str(buf);\r
512         }\r
513         put_byte(buf, c);\r
514     }\r
517 static bool read_blob(BinarySource *src, int nlines, BinarySink *bs)\r
519     char *line;\r
520     int linelen;\r
521     int i, j, k;\r
523     /* We expect at most 64 base64 characters, ie 48 real bytes, per line. */\r
525     for (i = 0; i < nlines; i++) {\r
526         line = read_body(src);\r
527         if (!line)\r
528             return false;\r
529         linelen = strlen(line);\r
530         if (linelen % 4 != 0 || linelen > 64) {\r
531             sfree(line);\r
532             return false;\r
533         }\r
534         for (j = 0; j < linelen; j += 4) {\r
535             unsigned char decoded[3];\r
536             k = base64_decode_atom(line + j, decoded);\r
537             if (!k) {\r
538                 sfree(line);\r
539                 return false;\r
540             }\r
541             put_data(bs, decoded, k);\r
542         }\r
543         sfree(line);\r
544     }\r
545     return true;\r
548 /*\r
549  * Magic error return value for when the passphrase is wrong.\r
550  */\r
551 ssh2_userkey ssh2_wrong_passphrase = { NULL, NULL };\r
553 const ssh_keyalg *const all_keyalgs[] = {\r
554     &ssh_rsa,\r
555     &ssh_rsa_sha256,\r
556     &ssh_rsa_sha512,\r
557     &ssh_dsa,\r
558     &ssh_ecdsa_nistp256,\r
559     &ssh_ecdsa_nistp384,\r
560     &ssh_ecdsa_nistp521,\r
561     &ssh_ecdsa_ed25519,\r
562     &ssh_ecdsa_ed448,\r
563     &opensshcert_ssh_dsa,\r
564     &opensshcert_ssh_rsa,\r
565     &opensshcert_ssh_rsa_sha256,\r
566     &opensshcert_ssh_rsa_sha512,\r
567     &opensshcert_ssh_ecdsa_ed25519,\r
568     &opensshcert_ssh_ecdsa_nistp256,\r
569     &opensshcert_ssh_ecdsa_nistp384,\r
570     &opensshcert_ssh_ecdsa_nistp521,\r
571 };\r
572 const size_t n_keyalgs = lenof(all_keyalgs);\r
574 const ssh_keyalg *find_pubkey_alg_len(ptrlen name)\r
576     for (size_t i = 0; i < n_keyalgs; i++)\r
577         if (ptrlen_eq_string(name, all_keyalgs[i]->ssh_id))\r
578             return all_keyalgs[i];\r
580     return NULL;\r
583 const ssh_keyalg *find_pubkey_alg(const char *name)\r
585     return find_pubkey_alg_len(ptrlen_from_asciz(name));\r
588 ptrlen pubkey_blob_to_alg_name(ptrlen blob)\r
590     BinarySource src[1];\r
591     BinarySource_BARE_INIT_PL(src, blob);\r
592     return get_string(src);\r
595 const ssh_keyalg *pubkey_blob_to_alg(ptrlen blob)\r
597     return find_pubkey_alg_len(pubkey_blob_to_alg_name(blob));\r
600 struct ppk_cipher {\r
601     const char *name;\r
602     size_t blocklen, keylen, ivlen;\r
603 };\r
604 static const struct ppk_cipher ppk_cipher_none = { "none", 1, 0, 0 };\r
605 static const struct ppk_cipher ppk_cipher_aes256_cbc = { "aes256-cbc", 16, 32, 16 };\r
607 static void ssh2_ppk_derive_keys(\r
608     unsigned fmt_version, const struct ppk_cipher *ciphertype,\r
609     ptrlen passphrase, strbuf *storage, ptrlen *cipherkey, ptrlen *cipheriv,\r
610     ptrlen *mackey, ptrlen passphrase_salt, ppk_save_parameters *params)\r
612     size_t mac_keylen;\r
614     switch (fmt_version) {\r
615       case 3: {\r
616         if (ciphertype->keylen == 0) {\r
617             mac_keylen = 0;\r
618             break;\r
619         }\r
620         ptrlen empty = PTRLEN_LITERAL("");\r
622         mac_keylen = 32;\r
624         uint32_t taglen = ciphertype->keylen + ciphertype->ivlen + mac_keylen;\r
626         if (params->argon2_passes_auto) {\r
627             uint32_t passes;\r
629             argon2_choose_passes(\r
630                 params->argon2_flavour, params->argon2_mem,\r
631                 params->argon2_milliseconds, &passes,\r
632                 params->argon2_parallelism, taglen,\r
633                 passphrase, passphrase_salt, empty, empty, storage);\r
635             params->argon2_passes_auto = false;\r
636             params->argon2_passes = passes;\r
637         } else {\r
638             argon2(params->argon2_flavour, params->argon2_mem,\r
639                    params->argon2_passes, params->argon2_parallelism, taglen,\r
640                    passphrase, passphrase_salt, empty, empty, storage);\r
641         }\r
643         break;\r
644       }\r
646       case 2:\r
647       case 1: {\r
648         /* Counter-mode iteration to generate cipher key data. */\r
649         for (unsigned ctr = 0; ctr * 20 < ciphertype->keylen; ctr++) {\r
650             ssh_hash *h = ssh_hash_new(&ssh_sha1);\r
651             put_uint32(h, ctr);\r
652             put_datapl(h, passphrase);\r
653             ssh_hash_final(h, strbuf_append(storage, 20));\r
654         }\r
655         strbuf_shrink_to(storage, ciphertype->keylen);\r
657         /* In this version of the format, the CBC IV was always all 0. */\r
658         put_padding(storage, ciphertype->ivlen, 0);\r
660         /* Completely separate hash for the MAC key. */\r
661         ssh_hash *h = ssh_hash_new(&ssh_sha1);\r
662         mac_keylen = ssh_hash_alg(h)->hlen;\r
663         put_datapl(h, PTRLEN_LITERAL("putty-private-key-file-mac-key"));\r
664         put_datapl(h, passphrase);\r
665         ssh_hash_final(h, strbuf_append(storage, mac_keylen));\r
667         break;\r
668       }\r
670       default:\r
671         unreachable("bad format version in ssh2_ppk_derive_keys");\r
672     }\r
674     BinarySource src[1];\r
675     BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf(storage));\r
676     *cipherkey = get_data(src, ciphertype->keylen);\r
677     *cipheriv = get_data(src, ciphertype->ivlen);\r
678     *mackey = get_data(src, mac_keylen);\r
681 static int userkey_parse_line_counter(const char *text)\r
683     char *endptr;\r
684     unsigned long ul = strtoul(text, &endptr, 10);\r
685     if (*text && !*endptr && ul < MAX_KEY_BLOB_LINES)\r
686         return ul;\r
687     else\r
688         return -1;\r
691 static bool str_to_uint32_t(const char *s, uint32_t *out)\r
693     char *endptr;\r
694     unsigned long converted = strtoul(s, &endptr, 10);\r
695     if (*s && !*endptr && converted <= ~(uint32_t)0) {\r
696         *out = converted;\r
697         return true;\r
698     } else {\r
699         return false;\r
700     }\r
703 ssh2_userkey *ppk_load_s(BinarySource *src, const char *passphrase,\r
704                          const char **errorstr)\r
706     char header[40], *b, *encryption, *comment, *mac;\r
707     const ssh_keyalg *alg;\r
708     ssh2_userkey *ret;\r
709     strbuf *public_blob, *private_blob, *cipher_mac_keys_blob;\r
710     strbuf *passphrase_salt = strbuf_new();\r
711     ptrlen cipherkey, cipheriv, mackey;\r
712     const struct ppk_cipher *ciphertype;\r
713     int i;\r
714     bool is_mac;\r
715     unsigned fmt_version;\r
716     const char *error = NULL;\r
717     ppk_save_parameters params;\r
719     ret = NULL;                        /* return NULL for most errors */\r
720     encryption = comment = mac = NULL;\r
721     public_blob = private_blob = cipher_mac_keys_blob = NULL;\r
723     /* Read the first header line which contains the key type. */\r
724     if (!read_header(src, header)) {\r
725         error = "no header line found in key file";\r
726         goto error;\r
727     }\r
728     if (0 == strcmp(header, "PuTTY-User-Key-File-3")) {\r
729         fmt_version = 3;\r
730     } else if (0 == strcmp(header, "PuTTY-User-Key-File-2")) {\r
731         fmt_version = 2;\r
732     } else if (0 == strcmp(header, "PuTTY-User-Key-File-1")) {\r
733         /* this is an old key file; warn and then continue */\r
734         old_keyfile_warning();\r
735         fmt_version = 1;\r
736     } else if (0 == strncmp(header, "PuTTY-User-Key-File-", 20)) {\r
737         /* this is a key file FROM THE FUTURE; refuse it, but with a\r
738          * more specific error message than the generic one below */\r
739         error = "PuTTY key format too new";\r
740         goto error;\r
741     } else {\r
742         error = "not a PuTTY SSH-2 private key";\r
743         goto error;\r
744     }\r
745     error = "file format error";\r
746     if ((b = read_body(src)) == NULL)\r
747         goto error;\r
748     /* Select key algorithm structure. */\r
749     alg = find_pubkey_alg(b);\r
750     if (!alg) {\r
751         sfree(b);\r
752         goto error;\r
753     }\r
754     sfree(b);\r
756     /* Read the Encryption header line. */\r
757     if (!read_header(src, header) || 0 != strcmp(header, "Encryption"))\r
758         goto error;\r
759     if ((encryption = read_body(src)) == NULL)\r
760         goto error;\r
761     if (!strcmp(encryption, "aes256-cbc")) {\r
762         ciphertype = &ppk_cipher_aes256_cbc;\r
763     } else if (!strcmp(encryption, "none")) {\r
764         ciphertype = &ppk_cipher_none;\r
765     } else {\r
766         goto error;\r
767     }\r
769     /* Read the Comment header line. */\r
770     if (!read_header(src, header) || 0 != strcmp(header, "Comment"))\r
771         goto error;\r
772     if ((comment = read_body(src)) == NULL)\r
773         goto error;\r
775     memset(&params, 0, sizeof(params)); /* in particular, sets\r
776                                          * passes_auto=false */\r
778     /* Read the Public-Lines header line and the public blob. */\r
779     if (!read_header(src, header) || 0 != strcmp(header, "Public-Lines"))\r
780         goto error;\r
781     if ((b = read_body(src)) == NULL)\r
782         goto error;\r
783     i = userkey_parse_line_counter(b);\r
784     sfree(b);\r
785     if (i < 0)\r
786         goto error;\r
787     public_blob = strbuf_new();\r
788     if (!read_blob(src, i, BinarySink_UPCAST(public_blob)))\r
789         goto error;\r
791     if (fmt_version >= 3 && ciphertype->keylen != 0) {\r
792         /* Read Argon2 key derivation parameters. */\r
793         if (!read_header(src, header) || 0 != strcmp(header, "Key-Derivation"))\r
794             goto error;\r
795         if ((b = read_body(src)) == NULL)\r
796             goto error;\r
797         if (!strcmp(b, "Argon2d")) {\r
798             params.argon2_flavour = Argon2d;\r
799         } else if (!strcmp(b, "Argon2i")) {\r
800             params.argon2_flavour = Argon2i;\r
801         } else if (!strcmp(b, "Argon2id")) {\r
802             params.argon2_flavour = Argon2id;\r
803         } else {\r
804             sfree(b);\r
805             goto error;\r
806         }\r
807         sfree(b);\r
809         if (!read_header(src, header) || 0 != strcmp(header, "Argon2-Memory"))\r
810             goto error;\r
811         if ((b = read_body(src)) == NULL)\r
812             goto error;\r
813         if (!str_to_uint32_t(b, &params.argon2_mem)) {\r
814             sfree(b);\r
815             goto error;\r
816         }\r
817         sfree(b);\r
819         if (!read_header(src, header) || 0 != strcmp(header, "Argon2-Passes"))\r
820             goto error;\r
821         if ((b = read_body(src)) == NULL)\r
822             goto error;\r
823         if (!str_to_uint32_t(b, &params.argon2_passes)) {\r
824             sfree(b);\r
825             goto error;\r
826         }\r
827         sfree(b);\r
829         if (!read_header(src, header) ||\r
830             0 != strcmp(header, "Argon2-Parallelism"))\r
831             goto error;\r
832         if ((b = read_body(src)) == NULL)\r
833             goto error;\r
834         if (!str_to_uint32_t(b, &params.argon2_parallelism)) {\r
835             sfree(b);\r
836             goto error;\r
837         }\r
838         sfree(b);\r
840         if (!read_header(src, header) || 0 != strcmp(header, "Argon2-Salt"))\r
841             goto error;\r
842         if ((b = read_body(src)) == NULL)\r
843             goto error;\r
844         for (size_t i = 0; b[i]; i += 2) {\r
845             if (isxdigit((unsigned char)b[i]) && b[i+1] &&\r
846                 isxdigit((unsigned char)b[i+1])) {\r
847                 char s[3];\r
848                 s[0] = b[i];\r
849                 s[1] = b[i+1];\r
850                 s[2] = '\0';\r
851                 put_byte(passphrase_salt, strtoul(s, NULL, 16));\r
852             } else {\r
853                 sfree(b);\r
854                 goto error;\r
855             }\r
856         }\r
857         sfree(b);\r
858     }\r
860     /* Read the Private-Lines header line and the Private blob. */\r
861     if (!read_header(src, header) || 0 != strcmp(header, "Private-Lines"))\r
862         goto error;\r
863     if ((b = read_body(src)) == NULL)\r
864         goto error;\r
865     i = userkey_parse_line_counter(b);\r
866     sfree(b);\r
867     if (i < 0)\r
868         goto error;\r
869     private_blob = strbuf_new_nm();\r
870     if (!read_blob(src, i, BinarySink_UPCAST(private_blob)))\r
871         goto error;\r
873     /* Read the Private-MAC or Private-Hash header line. */\r
874     if (!read_header(src, header))\r
875         goto error;\r
876     if (0 == strcmp(header, "Private-MAC")) {\r
877         if ((mac = read_body(src)) == NULL)\r
878             goto error;\r
879         is_mac = true;\r
880     } else if (0 == strcmp(header, "Private-Hash") && fmt_version == 1) {\r
881         if ((mac = read_body(src)) == NULL)\r
882             goto error;\r
883         is_mac = false;\r
884     } else\r
885         goto error;\r
887     cipher_mac_keys_blob = strbuf_new();\r
888     ssh2_ppk_derive_keys(fmt_version, ciphertype,\r
889                          ptrlen_from_asciz(passphrase ? passphrase : ""),\r
890                          cipher_mac_keys_blob, &cipherkey, &cipheriv, &mackey,\r
891                          ptrlen_from_strbuf(passphrase_salt), &params);\r
893     /*\r
894      * Decrypt the private blob.\r
895      */\r
896     if (private_blob->len % ciphertype->blocklen)\r
897         goto error;\r
898     if (ciphertype == &ppk_cipher_aes256_cbc) {\r
899         aes256_decrypt_pubkey(cipherkey.ptr, cipheriv.ptr,\r
900                               private_blob->u, private_blob->len);\r
901     }\r
903     /*\r
904      * Verify the MAC.\r
905      */\r
906     {\r
907         unsigned char binary[32];\r
908         char realmac[sizeof(binary) * 2 + 1];\r
909         strbuf *macdata;\r
910         bool free_macdata;\r
912         const ssh2_macalg *mac_alg =\r
913             fmt_version <= 2 ? &ssh_hmac_sha1 : &ssh_hmac_sha256;\r
915         if (fmt_version == 1) {\r
916             /* MAC (or hash) only covers the private blob. */\r
917             macdata = private_blob;\r
918             free_macdata = false;\r
919         } else {\r
920             macdata = strbuf_new_nm();\r
921             put_stringz(macdata, alg->ssh_id);\r
922             put_stringz(macdata, encryption);\r
923             put_stringz(macdata, comment);\r
924             put_string(macdata, public_blob->s,\r
925                        public_blob->len);\r
926             put_string(macdata, private_blob->s,\r
927                        private_blob->len);\r
928             free_macdata = true;\r
929         }\r
931         if (is_mac) {\r
932             ssh2_mac *mac;\r
934             mac = ssh2_mac_new(mac_alg, NULL);\r
935             ssh2_mac_setkey(mac, mackey);\r
936             ssh2_mac_start(mac);\r
937             put_data(mac, macdata->s, macdata->len);\r
938             ssh2_mac_genresult(mac, binary);\r
939             ssh2_mac_free(mac);\r
940         } else {\r
941             hash_simple(&ssh_sha1, ptrlen_from_strbuf(macdata), binary);\r
942         }\r
944         if (free_macdata)\r
945             strbuf_free(macdata);\r
947         for (i = 0; i < mac_alg->len; i++)\r
948             sprintf(realmac + 2 * i, "%02x", binary[i]);\r
950         if (strcmp(mac, realmac)) {\r
951             /* An incorrect MAC is an unconditional Error if the key is\r
952              * unencrypted. Otherwise, it means Wrong Passphrase. */\r
953             if (ciphertype->keylen != 0) {\r
954                 error = "wrong passphrase";\r
955                 ret = SSH2_WRONG_PASSPHRASE;\r
956             } else {\r
957                 error = "MAC failed";\r
958                 ret = NULL;\r
959             }\r
960             goto error;\r
961         }\r
962     }\r
964     /*\r
965      * Create and return the key.\r
966      */\r
967     ret = snew(ssh2_userkey);\r
968     ret->comment = comment;\r
969     comment = NULL;\r
970     ret->key = ssh_key_new_priv(\r
971         alg, ptrlen_from_strbuf(public_blob),\r
972         ptrlen_from_strbuf(private_blob));\r
973     if (!ret->key) {\r
974         sfree(ret);\r
975         ret = NULL;\r
976         error = "createkey failed";\r
977         goto error;\r
978     }\r
979     error = NULL;\r
981     /*\r
982      * Error processing.\r
983      */\r
984   error:\r
985     if (comment)\r
986         sfree(comment);\r
987     if (encryption)\r
988         sfree(encryption);\r
989     if (mac)\r
990         sfree(mac);\r
991     if (public_blob)\r
992         strbuf_free(public_blob);\r
993     if (private_blob)\r
994         strbuf_free(private_blob);\r
995     if (cipher_mac_keys_blob)\r
996         strbuf_free(cipher_mac_keys_blob);\r
997     strbuf_free(passphrase_salt);\r
998     if (errorstr)\r
999         *errorstr = error;\r
1000     return ret;\r
1003 ssh2_userkey *ppk_load_f(const Filename *filename, const char *passphrase,\r
1004                          const char **errorstr)\r
1006     LoadedFile *lf = lf_load_keyfile(filename, errorstr);\r
1007     ssh2_userkey *toret;\r
1008     if (lf) {\r
1009         toret = ppk_load_s(BinarySource_UPCAST(lf), passphrase, errorstr);\r
1010         lf_free(lf);\r
1011     } else {\r
1012         toret = NULL;\r
1013         *errorstr = "can't open file";\r
1014     }\r
1015     return toret;\r
1018 static bool rfc4716_loadpub(BinarySource *src, char **algorithm,\r
1019                             BinarySink *bs,\r
1020                             char **commentptr, const char **errorstr)\r
1022     const char *error;\r
1023     char *line, *colon, *value;\r
1024     char *comment = NULL;\r
1025     strbuf *pubblob = NULL;\r
1026     char base64in[4];\r
1027     unsigned char base64out[3];\r
1028     int base64bytes;\r
1029     int alglen;\r
1031     line = mkstr(get_chomped_line(src));\r
1032     if (!line || 0 != strcmp(line, "---- BEGIN SSH2 PUBLIC KEY ----")) {\r
1033         error = "invalid begin line in SSH-2 public key file";\r
1034         goto error;\r
1035     }\r
1036     sfree(line); line = NULL;\r
1038     while (1) {\r
1039         line = mkstr(get_chomped_line(src));\r
1040         if (!line) {\r
1041             error = "truncated SSH-2 public key file";\r
1042             goto error;\r
1043         }\r
1044         colon = strstr(line, ": ");\r
1045         if (!colon)\r
1046             break;\r
1047         *colon = '\0';\r
1048         value = colon + 2;\r
1050         if (!strcmp(line, "Comment")) {\r
1051             char *p, *q;\r
1053             /* Remove containing double quotes, if present */\r
1054             p = value;\r
1055             if (*p == '"' && p[strlen(p)-1] == '"') {\r
1056                 p[strlen(p)-1] = '\0';\r
1057                 p++;\r
1058             }\r
1060             /* Remove \-escaping, not in RFC4716 but seen in the wild\r
1061              * in practice. */\r
1062             for (q = line; *p; p++) {\r
1063                 if (*p == '\\' && p[1])\r
1064                     p++;\r
1065                 *q++ = *p;\r
1066             }\r
1068             *q = '\0';\r
1069             sfree(comment);   /* *just* in case of multiple Comment headers */\r
1070             comment = dupstr(line);\r
1071         } else if (!strcmp(line, "Subject") ||\r
1072                    !strncmp(line, "x-", 2)) {\r
1073             /* Headers we recognise and ignore. Do nothing. */\r
1074         } else {\r
1075             error = "unrecognised header in SSH-2 public key file";\r
1076             goto error;\r
1077         }\r
1079         sfree(line); line = NULL;\r
1080     }\r
1082     /*\r
1083      * Now line contains the initial line of base64 data. Loop round\r
1084      * while it still does contain base64.\r
1085      */\r
1086     pubblob = strbuf_new();\r
1087     base64bytes = 0;\r
1088     while (line && line[0] != '-') {\r
1089         char *p;\r
1090         for (p = line; *p; p++) {\r
1091             base64in[base64bytes++] = *p;\r
1092             if (base64bytes == 4) {\r
1093                 int n = base64_decode_atom(base64in, base64out);\r
1094                 put_data(pubblob, base64out, n);\r
1095                 base64bytes = 0;\r
1096             }\r
1097         }\r
1098         sfree(line); line = NULL;\r
1099         line = mkstr(get_chomped_line(src));\r
1100     }\r
1102     /*\r
1103      * Finally, check the END line makes sense.\r
1104      */\r
1105     if (!line || 0 != strcmp(line, "---- END SSH2 PUBLIC KEY ----")) {\r
1106         error = "invalid end line in SSH-2 public key file";\r
1107         goto error;\r
1108     }\r
1109     sfree(line); line = NULL;\r
1111     /*\r
1112      * OK, we now have a public blob and optionally a comment. We must\r
1113      * return the key algorithm string too, so look for that at the\r
1114      * start of the public blob.\r
1115      */\r
1116     if (pubblob->len < 4) {\r
1117         error = "not enough data in SSH-2 public key file";\r
1118         goto error;\r
1119     }\r
1120     alglen = toint(GET_32BIT_MSB_FIRST(pubblob->u));\r
1121     if (alglen < 0 || alglen > pubblob->len-4) {\r
1122         error = "invalid algorithm prefix in SSH-2 public key file";\r
1123         goto error;\r
1124     }\r
1125     if (algorithm)\r
1126         *algorithm = dupprintf("%.*s", alglen, pubblob->s+4);\r
1127     if (commentptr)\r
1128         *commentptr = comment;\r
1129     else\r
1130         sfree(comment);\r
1131     put_datapl(bs, ptrlen_from_strbuf(pubblob));\r
1132     strbuf_free(pubblob);\r
1133     return true;\r
1135   error:\r
1136     sfree(line);\r
1137     sfree(comment);\r
1138     if (pubblob)\r
1139         strbuf_free(pubblob);\r
1140     if (errorstr)\r
1141         *errorstr = error;\r
1142     return false;\r
1145 static bool openssh_loadpub(BinarySource *src, char **algorithm,\r
1146                             BinarySink *bs,\r
1147                             char **commentptr, const char **errorstr)\r
1149     const char *error;\r
1150     char *line, *base64;\r
1151     char *comment = NULL;\r
1152     unsigned char *pubblob = NULL;\r
1153     int pubbloblen, pubblobsize;\r
1154     int alglen;\r
1156     line = mkstr(get_chomped_line(src));\r
1158     base64 = strchr(line, ' ');\r
1159     if (!base64) {\r
1160         error = "no key blob in OpenSSH public key file";\r
1161         goto error;\r
1162     }\r
1163     *base64++ = '\0';\r
1165     comment = strchr(base64, ' ');\r
1166     if (comment) {\r
1167         *comment++ = '\0';\r
1168         comment = dupstr(comment);\r
1169     }\r
1171     pubblobsize = strlen(base64) / 4 * 3;\r
1172     pubblob = snewn(pubblobsize, unsigned char);\r
1173     pubbloblen = 0;\r
1175     while (!memchr(base64, '\0', 4)) {\r
1176         assert(pubbloblen + 3 <= pubblobsize);\r
1177         pubbloblen += base64_decode_atom(base64, pubblob + pubbloblen);\r
1178         base64 += 4;\r
1179     }\r
1180     if (*base64) {\r
1181         error = "invalid length for base64 data in OpenSSH public key file";\r
1182         goto error;\r
1183     }\r
1185     /*\r
1186      * Sanity check: the first word on the line should be the key\r
1187      * algorithm, and should match the encoded string at the start of\r
1188      * the public blob.\r
1189      */\r
1190     alglen = strlen(line);\r
1191     if (pubbloblen < alglen + 4 ||\r
1192         GET_32BIT_MSB_FIRST(pubblob) != alglen ||\r
1193         0 != memcmp(pubblob + 4, line, alglen)) {\r
1194         error = "key algorithms do not match in OpenSSH public key file";\r
1195         goto error;\r
1196     }\r
1198     /*\r
1199      * Done.\r
1200      */\r
1201     if (algorithm)\r
1202         *algorithm = dupstr(line);\r
1203     if (commentptr)\r
1204         *commentptr = comment;\r
1205     else\r
1206         sfree(comment);\r
1207     sfree(line);\r
1208     put_data(bs, pubblob, pubbloblen);\r
1209     sfree(pubblob);\r
1210     return true;\r
1212   error:\r
1213     sfree(line);\r
1214     sfree(comment);\r
1215     sfree(pubblob);\r
1216     if (errorstr)\r
1217         *errorstr = error;\r
1218     return false;\r
1221 bool ppk_loadpub_s(BinarySource *src, char **algorithm, BinarySink *bs,\r
1222                    char **commentptr, const char **errorstr)\r
1224     char header[40], *b;\r
1225     const ssh_keyalg *alg;\r
1226     int type, i;\r
1227     const char *error = NULL;\r
1228     char *comment = NULL;\r
1230     /* Initially, check if this is a public-only key file. Sometimes\r
1231      * we'll be asked to read a public blob from one of those. */\r
1232     type = key_type_s(src);\r
1233     if (type == SSH_KEYTYPE_SSH2_PUBLIC_RFC4716) {\r
1234         bool ret = rfc4716_loadpub(src, algorithm, bs, commentptr, errorstr);\r
1235         return ret;\r
1236     } else if (type == SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH) {\r
1237         bool ret = openssh_loadpub(src, algorithm, bs, commentptr, errorstr);\r
1238         return ret;\r
1239     } else if (type != SSH_KEYTYPE_SSH2) {\r
1240         error = "not a public key or a PuTTY SSH-2 private key";\r
1241         goto error;\r
1242     }\r
1244     /* Read the first header line which contains the key type. */\r
1245     if (!read_header(src, header)\r
1246         || (0 != strcmp(header, "PuTTY-User-Key-File-3") &&\r
1247             0 != strcmp(header, "PuTTY-User-Key-File-2") &&\r
1248             0 != strcmp(header, "PuTTY-User-Key-File-1"))) {\r
1249         if (0 == strncmp(header, "PuTTY-User-Key-File-", 20))\r
1250             error = "PuTTY key format too new";\r
1251         else\r
1252             error = "not a public key or a PuTTY SSH-2 private key";\r
1253         goto error;\r
1254     }\r
1255     error = "file format error";\r
1256     if ((b = read_body(src)) == NULL)\r
1257         goto error;\r
1258     /* Select key algorithm structure. */\r
1259     alg = find_pubkey_alg(b);\r
1260     sfree(b);\r
1261     if (!alg) {\r
1262         goto error;\r
1263     }\r
1265     /* Read the Encryption header line. */\r
1266     if (!read_header(src, header) || 0 != strcmp(header, "Encryption"))\r
1267         goto error;\r
1268     if ((b = read_body(src)) == NULL)\r
1269         goto error;\r
1270     sfree(b);                          /* we don't care */\r
1272     /* Read the Comment header line. */\r
1273     if (!read_header(src, header) || 0 != strcmp(header, "Comment"))\r
1274         goto error;\r
1275     if ((comment = read_body(src)) == NULL)\r
1276         goto error;\r
1278     if (commentptr)\r
1279         *commentptr = comment;\r
1280     else\r
1281         sfree(comment);\r
1283     /* Read the Public-Lines header line and the public blob. */\r
1284     if (!read_header(src, header) || 0 != strcmp(header, "Public-Lines"))\r
1285         goto error;\r
1286     if ((b = read_body(src)) == NULL)\r
1287         goto error;\r
1288     i = userkey_parse_line_counter(b);\r
1289     sfree(b);\r
1290     if (i < 0)\r
1291         goto error;\r
1292     if (!read_blob(src, i, bs))\r
1293         goto error;\r
1295     if (algorithm)\r
1296         *algorithm = dupstr(alg->ssh_id);\r
1297     return true;\r
1299     /*\r
1300      * Error processing.\r
1301      */\r
1302   error:\r
1303     if (errorstr)\r
1304         *errorstr = error;\r
1305     if (comment && commentptr) {\r
1306         sfree(comment);\r
1307         *commentptr = NULL;\r
1308     }\r
1309     return false;\r
1312 bool ppk_loadpub_f(const Filename *filename, char **algorithm, BinarySink *bs,\r
1313                    char **commentptr, const char **errorstr)\r
1315     LoadedFile *lf = lf_load_keyfile(filename, errorstr);\r
1316     if (!lf)\r
1317         return false;\r
1319     bool toret = ppk_loadpub_s(BinarySource_UPCAST(lf), algorithm, bs,\r
1320                                commentptr, errorstr);\r
1321     lf_free(lf);\r
1322     return toret;\r
1325 bool ppk_encrypted_s(BinarySource *src, char **commentptr)\r
1327     char header[40], *b, *comment;\r
1328     bool ret;\r
1330     if (commentptr)\r
1331         *commentptr = NULL;\r
1333     if (!read_header(src, header)\r
1334         || (0 != strcmp(header, "PuTTY-User-Key-File-3") &&\r
1335             0 != strcmp(header, "PuTTY-User-Key-File-2") &&\r
1336             0 != strcmp(header, "PuTTY-User-Key-File-1"))) {\r
1337         return false;\r
1338     }\r
1339     if ((b = read_body(src)) == NULL) {\r
1340         return false;\r
1341     }\r
1342     sfree(b);                          /* we don't care about key type here */\r
1343     /* Read the Encryption header line. */\r
1344     if (!read_header(src, header) || 0 != strcmp(header, "Encryption")) {\r
1345         return false;\r
1346     }\r
1347     if ((b = read_body(src)) == NULL) {\r
1348         return false;\r
1349     }\r
1351     /* Read the Comment header line. */\r
1352     if (!read_header(src, header) || 0 != strcmp(header, "Comment")) {\r
1353         sfree(b);\r
1354         return true;\r
1355     }\r
1356     if ((comment = read_body(src)) == NULL) {\r
1357         sfree(b);\r
1358         return true;\r
1359     }\r
1361     if (commentptr)\r
1362         *commentptr = comment;\r
1363     else\r
1364         sfree(comment);\r
1366     if (!strcmp(b, "aes256-cbc"))\r
1367         ret = true;\r
1368     else\r
1369         ret = false;\r
1370     sfree(b);\r
1371     return ret;\r
1374 bool ppk_encrypted_f(const Filename *filename, char **commentptr)\r
1376     LoadedFile *lf = lf_load_keyfile(filename, NULL);\r
1377     if (!lf) {\r
1378         if (commentptr)\r
1379             *commentptr = NULL;\r
1380         return false;\r
1381     }\r
1383     bool toret = ppk_encrypted_s(BinarySource_UPCAST(lf), commentptr);\r
1384     lf_free(lf);\r
1385     return toret;\r
1388 int base64_lines(int datalen)\r
1390     /* When encoding, we use 64 chars/line, which equals 48 real chars. */\r
1391     return (datalen + 47) / 48;\r
1394 const ppk_save_parameters ppk_save_default_parameters = {\r
1395     .fmt_version = 3,\r
1397     /*\r
1398      * The Argon2 spec recommends the hybrid variant Argon2id, where\r
1399      * you don't have a good reason to go with the pure Argon2d or\r
1400      * Argon2i.\r
1401      */\r
1402     .argon2_flavour = Argon2id,\r
1404     /*\r
1405      * Memory requirement for hashing a password: I don't want to set\r
1406      * this to some truly huge thing like a gigabyte, because for all\r
1407      * I know people might perfectly reasonably be running PuTTY on\r
1408      * machines that don't _have_ a gigabyte spare to hash a private\r
1409      * key passphrase in the legitimate use cases.\r
1410      *\r
1411      * I've picked 8 MB as an amount of memory that isn't unreasonable\r
1412      * to expect a desktop client machine to have, but is also large\r
1413      * compared to the memory requirements of the PPK v2 password hash\r
1414      * (which was plain SHA-1), so it still imposes a limit on\r
1415      * parallel attacks on someone's key file.\r
1416      */\r
1417     .argon2_mem = 8192,                /* require 8 Mb memory */\r
1419     /*\r
1420      * Automatically scale the number of Argon2 passes so that the\r
1421      * overall time taken is about 1/10 second. (Again, I could crank\r
1422      * this up to a larger time and _most_ people might be OK with it,\r
1423      * but for the moment, I'm trying to err on the side of not\r
1424      * stopping anyone from using the tools at all.)\r
1425      */\r
1426     .argon2_passes_auto = true,\r
1427     .argon2_milliseconds = 100,\r
1429     /*\r
1430      * PuTTY's own Argon2 implementation is single-threaded. So we\r
1431      * might as well set parallelism to 1, which requires that\r
1432      * attackers' implementations must also be effectively\r
1433      * single-threaded, and they don't get any benefit from using\r
1434      * multiple cores on the same hash attempt. (Of course they can\r
1435      * still use multiple cores for _separate_ hash attempts, but at\r
1436      * least they don't get a speed advantage over us in computing\r
1437      * even one hash.)\r
1438      */\r
1439     .argon2_parallelism = 1,\r
1440 };\r
1442 strbuf *ppk_save_sb(ssh2_userkey *key, const char *passphrase,\r
1443                     const ppk_save_parameters *params_orig)\r
1445     strbuf *pub_blob, *priv_blob, *cipher_mac_keys_blob;\r
1446     unsigned char *priv_blob_encrypted;\r
1447     int priv_encrypted_len;\r
1448     int cipherblk;\r
1449     int i;\r
1450     const char *cipherstr;\r
1451     ptrlen cipherkey, cipheriv, mackey;\r
1452     const struct ppk_cipher *ciphertype;\r
1453     unsigned char priv_mac[32];\r
1455     /*\r
1456      * Fetch the key component blobs.\r
1457      */\r
1458     pub_blob = strbuf_new();\r
1459     ssh_key_public_blob(key->key, BinarySink_UPCAST(pub_blob));\r
1460     priv_blob = strbuf_new_nm();\r
1461     ssh_key_private_blob(key->key, BinarySink_UPCAST(priv_blob));\r
1463     /*\r
1464      * Determine encryption details, and encrypt the private blob.\r
1465      */\r
1466     if (passphrase) {\r
1467         cipherstr = "aes256-cbc";\r
1468         cipherblk = 16;\r
1469         ciphertype = &ppk_cipher_aes256_cbc;\r
1470     } else {\r
1471         cipherstr = "none";\r
1472         cipherblk = 1;\r
1473         ciphertype = &ppk_cipher_none;\r
1474     }\r
1475     priv_encrypted_len = priv_blob->len + cipherblk - 1;\r
1476     priv_encrypted_len -= priv_encrypted_len % cipherblk;\r
1477     priv_blob_encrypted = snewn(priv_encrypted_len, unsigned char);\r
1478     memset(priv_blob_encrypted, 0, priv_encrypted_len);\r
1479     memcpy(priv_blob_encrypted, priv_blob->u, priv_blob->len);\r
1480     /* Create padding based on the SHA hash of the unpadded blob. This prevents\r
1481      * too easy a known-plaintext attack on the last block. */\r
1482     hash_simple(&ssh_sha1, ptrlen_from_strbuf(priv_blob), priv_mac);\r
1483     assert(priv_encrypted_len - priv_blob->len < 20);\r
1484     memcpy(priv_blob_encrypted + priv_blob->len, priv_mac,\r
1485            priv_encrypted_len - priv_blob->len);\r
1487     /* Copy the save parameters, so that when derive_keys chooses the\r
1488      * number of Argon2 passes, it can write the result back to our\r
1489      * copy for us to retrieve. */\r
1490     ppk_save_parameters params = *params_orig;\r
1492     strbuf *passphrase_salt = strbuf_new();\r
1494     if (params.fmt_version == 3) {\r
1495         /* Invent a salt for the password hash. */\r
1496         if (params.salt)\r
1497             put_data(passphrase_salt, params.salt, params.saltlen);\r
1498         else\r
1499             random_read(strbuf_append(passphrase_salt, 16), 16);\r
1500     }\r
1502     cipher_mac_keys_blob = strbuf_new();\r
1503     ssh2_ppk_derive_keys(params.fmt_version, ciphertype,\r
1504                          ptrlen_from_asciz(passphrase ? passphrase : ""),\r
1505                          cipher_mac_keys_blob, &cipherkey, &cipheriv, &mackey,\r
1506                          ptrlen_from_strbuf(passphrase_salt), &params);\r
1508     const ssh2_macalg *macalg = (params.fmt_version == 2 ?\r
1509                                  &ssh_hmac_sha1 : &ssh_hmac_sha256);\r
1511     /* Now create the MAC. */\r
1512     {\r
1513         strbuf *macdata;\r
1515         macdata = strbuf_new_nm();\r
1516         put_stringz(macdata, ssh_key_ssh_id(key->key));\r
1517         put_stringz(macdata, cipherstr);\r
1518         put_stringz(macdata, key->comment);\r
1519         put_string(macdata, pub_blob->s, pub_blob->len);\r
1520         put_string(macdata, priv_blob_encrypted, priv_encrypted_len);\r
1522         mac_simple(macalg, mackey, ptrlen_from_strbuf(macdata), priv_mac);\r
1523         strbuf_free(macdata);\r
1524     }\r
1526     if (passphrase) {\r
1527         assert(cipherkey.len == 32);\r
1528         aes256_encrypt_pubkey(cipherkey.ptr, cipheriv.ptr,\r
1529                               priv_blob_encrypted, priv_encrypted_len);\r
1530     }\r
1532     strbuf *out = strbuf_new_nm();\r
1533     put_fmt(out, "PuTTY-User-Key-File-%u: %s\n",\r
1534             params.fmt_version, ssh_key_ssh_id(key->key));\r
1535     put_fmt(out, "Encryption: %s\n", cipherstr);\r
1536     put_fmt(out, "Comment: %s\n", key->comment);\r
1537     put_fmt(out, "Public-Lines: %d\n", base64_lines(pub_blob->len));\r
1538     base64_encode_bs(BinarySink_UPCAST(out), ptrlen_from_strbuf(pub_blob), 64);\r
1539     if (params.fmt_version == 3 && ciphertype->keylen != 0) {\r
1540         put_fmt(out, "Key-Derivation: %s\n",\r
1541                 params.argon2_flavour == Argon2d ? "Argon2d" :\r
1542                 params.argon2_flavour == Argon2i ? "Argon2i" : "Argon2id");\r
1543         put_fmt(out, "Argon2-Memory: %"PRIu32"\n", params.argon2_mem);\r
1544         assert(!params.argon2_passes_auto);\r
1545         put_fmt(out, "Argon2-Passes: %"PRIu32"\n", params.argon2_passes);\r
1546         put_fmt(out, "Argon2-Parallelism: %"PRIu32"\n",\r
1547                 params.argon2_parallelism);\r
1548         put_fmt(out, "Argon2-Salt: ");\r
1549         for (size_t i = 0; i < passphrase_salt->len; i++)\r
1550             put_fmt(out, "%02x", passphrase_salt->u[i]);\r
1551         put_fmt(out, "\n");\r
1552     }\r
1553     put_fmt(out, "Private-Lines: %d\n", base64_lines(priv_encrypted_len));\r
1554     base64_encode_bs(BinarySink_UPCAST(out),\r
1555                      make_ptrlen(priv_blob_encrypted, priv_encrypted_len), 64);\r
1556     put_fmt(out, "Private-MAC: ");\r
1557     for (i = 0; i < macalg->len; i++)\r
1558         put_fmt(out, "%02x", priv_mac[i]);\r
1559     put_fmt(out, "\n");\r
1561     strbuf_free(cipher_mac_keys_blob);\r
1562     strbuf_free(passphrase_salt);\r
1563     strbuf_free(pub_blob);\r
1564     strbuf_free(priv_blob);\r
1565     smemclr(priv_blob_encrypted, priv_encrypted_len);\r
1566     sfree(priv_blob_encrypted);\r
1567     return out;\r
1570 bool ppk_save_f(const Filename *filename, ssh2_userkey *key,\r
1571                 const char *passphrase, const ppk_save_parameters *params)\r
1573     FILE *fp = f_open(filename, "wb", true);\r
1574     if (!fp)\r
1575         return false;\r
1577     strbuf *buf = ppk_save_sb(key, passphrase, params);\r
1578     bool toret = fwrite(buf->s, 1, buf->len, fp) == buf->len;\r
1579     if (fclose(fp))\r
1580         toret = false;\r
1581     strbuf_free(buf);\r
1582     return toret;\r
1585 /* ----------------------------------------------------------------------\r
1586  * Output public keys.\r
1587  */\r
1588 char *ssh1_pubkey_str(RSAKey *key)\r
1590     char *buffer;\r
1591     char *dec1, *dec2;\r
1593     dec1 = mp_get_decimal(key->exponent);\r
1594     dec2 = mp_get_decimal(key->modulus);\r
1595     buffer = dupprintf("%"SIZEu" %s %s%s%s", mp_get_nbits(key->modulus),\r
1596                        dec1, dec2, key->comment ? " " : "",\r
1597                        key->comment ? key->comment : "");\r
1598     sfree(dec1);\r
1599     sfree(dec2);\r
1600     return buffer;\r
1603 void ssh1_write_pubkey(FILE *fp, RSAKey *key)\r
1605     char *buffer = ssh1_pubkey_str(key);\r
1606     fprintf(fp, "%s\n", buffer);\r
1607     sfree(buffer);\r
1610 static char *ssh2_pubkey_openssh_str_internal(const char *comment,\r
1611                                               const void *v_pub_blob,\r
1612                                               int pub_len)\r
1614     const unsigned char *ssh2blob = (const unsigned char *)v_pub_blob;\r
1615     ptrlen alg;\r
1616     char *buffer, *p;\r
1617     int i;\r
1619     {\r
1620         BinarySource src[1];\r
1621         BinarySource_BARE_INIT(src, ssh2blob, pub_len);\r
1622         alg = get_string(src);\r
1623         if (get_err(src)) {\r
1624             const char *replacement_str = "INVALID-ALGORITHM";\r
1625             alg.ptr = replacement_str;\r
1626             alg.len = strlen(replacement_str);\r
1627         }\r
1628     }\r
1630     buffer = snewn(alg.len +\r
1631                    4 * ((pub_len+2) / 3) +\r
1632                    (comment ? strlen(comment) : 0) + 3, char);\r
1633     p = buffer + sprintf(buffer, "%.*s ", PTRLEN_PRINTF(alg));\r
1634     i = 0;\r
1635     while (i < pub_len) {\r
1636         int n = (pub_len - i < 3 ? pub_len - i : 3);\r
1637         base64_encode_atom(ssh2blob + i, n, p);\r
1638         i += n;\r
1639         p += 4;\r
1640     }\r
1641     if (comment) {\r
1642         *p++ = ' ';\r
1643         strcpy(p, comment);\r
1644     } else\r
1645         *p++ = '\0';\r
1647     return buffer;\r
1650 char *ssh2_pubkey_openssh_str(ssh2_userkey *key)\r
1652     strbuf *blob;\r
1653     char *ret;\r
1655     blob = strbuf_new();\r
1656     ssh_key_public_blob(key->key, BinarySink_UPCAST(blob));\r
1657     ret = ssh2_pubkey_openssh_str_internal(\r
1658         key->comment, blob->s, blob->len);\r
1659     strbuf_free(blob);\r
1661     return ret;\r
1664 void ssh2_write_pubkey(FILE *fp, const char *comment,\r
1665                        const void *v_pub_blob, int pub_len,\r
1666                        int keytype)\r
1668     unsigned char *pub_blob = (unsigned char *)v_pub_blob;\r
1670     if (keytype == SSH_KEYTYPE_SSH2_PUBLIC_RFC4716) {\r
1671         const char *p;\r
1672         int i, column;\r
1674         fprintf(fp, "---- BEGIN SSH2 PUBLIC KEY ----\n");\r
1676         if (comment) {\r
1677             fprintf(fp, "Comment: \"");\r
1678             for (p = comment; *p; p++) {\r
1679                 if (*p == '\\' || *p == '\"')\r
1680                     fputc('\\', fp);\r
1681                 fputc(*p, fp);\r
1682             }\r
1683             fprintf(fp, "\"\n");\r
1684         }\r
1686         i = 0;\r
1687         column = 0;\r
1688         while (i < pub_len) {\r
1689             char buf[5];\r
1690             int n = (pub_len - i < 3 ? pub_len - i : 3);\r
1691             base64_encode_atom(pub_blob + i, n, buf);\r
1692             i += n;\r
1693             buf[4] = '\0';\r
1694             fputs(buf, fp);\r
1695             if (++column >= 16) {\r
1696                 fputc('\n', fp);\r
1697                 column = 0;\r
1698             }\r
1699         }\r
1700         if (column > 0)\r
1701             fputc('\n', fp);\r
1703         fprintf(fp, "---- END SSH2 PUBLIC KEY ----\n");\r
1704     } else if (keytype == SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH) {\r
1705         char *buffer = ssh2_pubkey_openssh_str_internal(comment,\r
1706                                                         v_pub_blob, pub_len);\r
1707         fprintf(fp, "%s\n", buffer);\r
1708         sfree(buffer);\r
1709     } else {\r
1710         unreachable("Bad key type in ssh2_write_pubkey");\r
1711     }\r
1714 /* ----------------------------------------------------------------------\r
1715  * Utility functions to compute SSH-2 fingerprints in a uniform way.\r
1716  */\r
1717 static void ssh2_fingerprint_blob_md5(ptrlen blob, strbuf *sb)\r
1719     unsigned char digest[16];\r
1721     hash_simple(&ssh_md5, blob, digest);\r
1722     for (unsigned i = 0; i < 16; i++)\r
1723         put_fmt(sb, "%02x%s", digest[i], i==15 ? "" : ":");\r
1726 static void ssh2_fingerprint_blob_sha256(ptrlen blob, strbuf *sb)\r
1728     unsigned char digest[32];\r
1729     hash_simple(&ssh_sha256, blob, digest);\r
1731     put_datapl(sb, PTRLEN_LITERAL("SHA256:"));\r
1733     for (unsigned i = 0; i < 32; i += 3) {\r
1734         char buf[5];\r
1735         unsigned len = 32-i;\r
1736         if (len > 3)\r
1737             len = 3;\r
1738         base64_encode_atom(digest + i, len, buf);\r
1739         put_data(sb, buf, 4);\r
1740     }\r
1741     strbuf_chomp(sb, '=');\r
1744 char *ssh2_fingerprint_blob(ptrlen blob, FingerprintType fptype)\r
1746     strbuf *sb = strbuf_new();\r
1747     strbuf *tmp = NULL;\r
1749     /*\r
1750      * Identify the key algorithm, if possible.\r
1751      *\r
1752      * If we can't do that, then we have a seriously confused key\r
1753      * blob, in which case we return only the hash.\r
1754      */\r
1755     BinarySource src[1];\r
1756     BinarySource_BARE_INIT_PL(src, blob);\r
1757     ptrlen algname = get_string(src);\r
1758     if (!get_err(src)) {\r
1759         const ssh_keyalg *alg = find_pubkey_alg_len(algname);\r
1760         if (alg) {\r
1761             int bits = ssh_key_public_bits(alg, blob);\r
1762             put_fmt(sb, "%.*s %d ", PTRLEN_PRINTF(algname), bits);\r
1764             if (!ssh_fptype_is_cert(fptype) && alg->is_certificate) {\r
1765                 ssh_key *key = ssh_key_new_pub(alg, blob);\r
1766                 if (key) {\r
1767                     tmp = strbuf_new();\r
1768                     ssh_key_public_blob(ssh_key_base_key(key),\r
1769                                         BinarySink_UPCAST(tmp));\r
1770                     blob = ptrlen_from_strbuf(tmp);\r
1771                     ssh_key_free(key);\r
1772                 }\r
1773             }\r
1774         } else {\r
1775             put_fmt(sb, "%.*s ", PTRLEN_PRINTF(algname));\r
1776         }\r
1777     }\r
1779     switch (ssh_fptype_from_cert(fptype)) {\r
1780       case SSH_FPTYPE_MD5:\r
1781         ssh2_fingerprint_blob_md5(blob, sb);\r
1782         break;\r
1783       case SSH_FPTYPE_SHA256:\r
1784         ssh2_fingerprint_blob_sha256(blob, sb);\r
1785         break;\r
1786       default:\r
1787         unreachable("ssh_fptype_from_cert ruled out the other values");\r
1788     }\r
1790     if (tmp)\r
1791         strbuf_free(tmp);\r
1793     return strbuf_to_str(sb);\r
1796 char *ssh2_double_fingerprint_blob(ptrlen blob, FingerprintType fptype)\r
1798     if (ssh_fptype_is_cert(fptype))\r
1799         fptype = ssh_fptype_from_cert(fptype);\r
1801     char *fp = ssh2_fingerprint_blob(blob, fptype);\r
1802     char *p = strrchr(fp, ' ');\r
1803     char *hash = p ? p + 1 : fp;\r
1805     char *fpc = ssh2_fingerprint_blob(blob, ssh_fptype_to_cert(fptype));\r
1806     char *pc = strrchr(fpc, ' ');\r
1807     char *hashc = pc ? pc + 1 : fpc;\r
1809     if (strcmp(hash, hashc)) {\r
1810         char *tmp = dupprintf("%s (with certificate: %s)", fp, hashc);\r
1811         sfree(fp);\r
1812         fp = tmp;\r
1813     }\r
1815     sfree(fpc);\r
1816     return fp;\r
1819 char **ssh2_all_fingerprints_for_blob(ptrlen blob)\r
1821     char **fps = snewn(SSH_N_FPTYPES, char *);\r
1822     for (unsigned i = 0; i < SSH_N_FPTYPES; i++)\r
1823         fps[i] = ssh2_fingerprint_blob(blob, i);\r
1824     return fps;\r
1827 char *ssh2_fingerprint(ssh_key *data, FingerprintType fptype)\r
1829     strbuf *blob = strbuf_new();\r
1830     ssh_key_public_blob(data, BinarySink_UPCAST(blob));\r
1831     char *ret = ssh2_fingerprint_blob(ptrlen_from_strbuf(blob), fptype);\r
1832     strbuf_free(blob);\r
1833     return ret;\r
1836 char *ssh2_double_fingerprint(ssh_key *data, FingerprintType fptype)\r
1838     strbuf *blob = strbuf_new();\r
1839     ssh_key_public_blob(data, BinarySink_UPCAST(blob));\r
1840     char *ret = ssh2_double_fingerprint_blob(ptrlen_from_strbuf(blob), fptype);\r
1841     strbuf_free(blob);\r
1842     return ret;\r
1845 char **ssh2_all_fingerprints(ssh_key *data)\r
1847     strbuf *blob = strbuf_new();\r
1848     ssh_key_public_blob(data, BinarySink_UPCAST(blob));\r
1849     char **ret = ssh2_all_fingerprints_for_blob(ptrlen_from_strbuf(blob));\r
1850     strbuf_free(blob);\r
1851     return ret;\r
1854 void ssh2_free_all_fingerprints(char **fps)\r
1856     for (unsigned i = 0; i < SSH_N_FPTYPES; i++)\r
1857         sfree(fps[i]);\r
1858     sfree(fps);\r
1861 /* ----------------------------------------------------------------------\r
1862  * Determine the type of a private key file.\r
1863  */\r
1864 static int key_type_s_internal(BinarySource *src)\r
1866     static const ptrlen public_std_sig =\r
1867         PTRLEN_DECL_LITERAL("---- BEGIN SSH2 PUBLIC KEY");\r
1868     static const ptrlen putty2_sig =\r
1869         PTRLEN_DECL_LITERAL("PuTTY-User-Key-File-");\r
1870     static const ptrlen sshcom_sig =\r
1871         PTRLEN_DECL_LITERAL("---- BEGIN SSH2 ENCRYPTED PRIVAT");\r
1872     static const ptrlen openssh_new_sig =\r
1873         PTRLEN_DECL_LITERAL("-----BEGIN OPENSSH PRIVATE KEY");\r
1874     static const ptrlen openssh_sig =\r
1875         PTRLEN_DECL_LITERAL("-----BEGIN ");\r
1877     if (BinarySource_REWIND(src), expect_signature(src, rsa1_signature))\r
1878         return SSH_KEYTYPE_SSH1;\r
1879     if (BinarySource_REWIND(src), expect_signature(src, public_std_sig))\r
1880         return SSH_KEYTYPE_SSH2_PUBLIC_RFC4716;\r
1881     if (BinarySource_REWIND(src), expect_signature(src, putty2_sig))\r
1882         return SSH_KEYTYPE_SSH2;\r
1883     if (BinarySource_REWIND(src), expect_signature(src, openssh_new_sig))\r
1884         return SSH_KEYTYPE_OPENSSH_NEW;\r
1885     if (BinarySource_REWIND(src), expect_signature(src, openssh_sig))\r
1886         return SSH_KEYTYPE_OPENSSH_PEM;\r
1887     if (BinarySource_REWIND(src), expect_signature(src, sshcom_sig))\r
1888         return SSH_KEYTYPE_SSHCOM;\r
1890     BinarySource_REWIND(src);\r
1891     if (get_chars(src, "0123456789").len > 0 && get_chars(src, " ").len == 1 &&\r
1892         get_chars(src, "0123456789").len > 0 && get_chars(src, " ").len == 1 &&\r
1893         get_chars(src, "0123456789").len > 0 &&\r
1894         get_nonchars(src, " \n").len == 0)\r
1895         return SSH_KEYTYPE_SSH1_PUBLIC;\r
1897     BinarySource_REWIND(src);\r
1898     if (find_pubkey_alg_len(get_nonchars(src, " \n")) > 0 &&\r
1899         get_chars(src, " ").len == 1 &&\r
1900         get_chars(src, "0123456789ABCDEFGHIJKLMNOPQRSTUV"\r
1901                   "WXYZabcdefghijklmnopqrstuvwxyz+/=").len > 0 &&\r
1902         get_nonchars(src, " \n").len == 0)\r
1903         return SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH;\r
1905     return SSH_KEYTYPE_UNKNOWN;        /* unrecognised or EOF */\r
1908 int key_type_s(BinarySource *src)\r
1910     int toret = key_type_s_internal(src);\r
1911     BinarySource_REWIND(src);\r
1912     return toret;\r
1915 int key_type(const Filename *filename)\r
1917     LoadedFile *lf = lf_new(1024);\r
1918     if (lf_load(lf, filename) == LF_ERROR) {\r
1919         lf_free(lf);\r
1920         return SSH_KEYTYPE_UNOPENABLE;\r
1921     }\r
1923     int toret = key_type_s(BinarySource_UPCAST(lf));\r
1924     lf_free(lf);\r
1925     return toret;\r
1928 /*\r
1929  * Convert the type word to a string, for `wrong type' error\r
1930  * messages.\r
1931  */\r
1932 const char *key_type_to_str(int type)\r
1934     switch (type) {\r
1935       case SSH_KEYTYPE_UNOPENABLE:\r
1936         return "unable to open file";\r
1937       case SSH_KEYTYPE_UNKNOWN:\r
1938         return "not a recognised key file format";\r
1939       case SSH_KEYTYPE_SSH1_PUBLIC:\r
1940         return "SSH-1 public key";\r
1941       case SSH_KEYTYPE_SSH2_PUBLIC_RFC4716:\r
1942         return "SSH-2 public key (RFC 4716 format)";\r
1943       case SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH:\r
1944         return "SSH-2 public key (OpenSSH format)";\r
1945       case SSH_KEYTYPE_SSH1:\r
1946         return "SSH-1 private key";\r
1947       case SSH_KEYTYPE_SSH2:\r
1948         return "PuTTY SSH-2 private key";\r
1949       case SSH_KEYTYPE_OPENSSH_PEM:\r
1950         return "OpenSSH SSH-2 private key (old PEM format)";\r
1951       case SSH_KEYTYPE_OPENSSH_NEW:\r
1952         return "OpenSSH SSH-2 private key (new format)";\r
1953       case SSH_KEYTYPE_SSHCOM:\r
1954         return "ssh.com SSH-2 private key";\r
1956         /*\r
1957          * This function is called with a key type derived from\r
1958          * looking at an actual key file, so the output-only type\r
1959          * OPENSSH_AUTO should never get here, and is much an INTERNAL\r
1960          * ERROR as a code we don't even understand.\r
1961          */\r
1962       case SSH_KEYTYPE_OPENSSH_AUTO:\r
1963         unreachable("OPENSSH_AUTO should never reach key_type_to_str");\r
1964       default:\r
1965         unreachable("bad key type in key_type_to_str");\r
1966     }\r