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
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
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
29 #define MAX_KEY_BLOB_SIZE 262144
\r
30 #define MAX_KEY_BLOB_LINES (MAX_KEY_BLOB_SIZE / 48)
\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
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
48 LoadedFile *lf_new(size_t max_size)
\r
50 LoadedFile *lf = snew_plus(LoadedFile, max_size);
\r
51 lf->data = snew_plus_get_aux(lf);
\r
53 lf->max_size = max_size;
\r
57 void lf_free(LoadedFile *lf)
\r
59 smemclr(lf->data, lf->max_size);
\r
60 smemclr(lf, sizeof(LoadedFile));
\r
64 LoadFileStatus lf_load_fp(LoadedFile *lf, FILE *fp)
\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
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
87 BinarySource_INIT(lf, lf->data, lf->len);
\r
92 LoadFileStatus lf_load(LoadedFile *lf, const Filename *filename)
\r
94 FILE *fp = f_open(filename, "rb", false);
\r
98 LoadFileStatus status = lf_load_fp(lf, fp);
\r
103 static inline bool lf_load_keyfile_helper(LoadFileStatus status,
\r
104 const char **errptr)
\r
111 error = "file is too large to be a key file";
\r
114 error = strerror(errno);
\r
117 unreachable("bad status value in lf_load_keyfile_helper");
\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
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
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
159 *error = "not an SSH-1 RSA file";
\r
161 if (!expect_signature(src, rsa1_signature))
\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
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
179 *commentptr = mkstr(comment);
\r
181 key->comment = mkstr(comment);
\r
189 ret = ciphertype != 0;
\r
195 * Decrypt remainder of buffer.
\r
198 size_t enclen = get_avail(src);
\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
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
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
229 * After that, we have one further bignum which is our
\r
230 * decryption exponent, and then the three auxiliary values
\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
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
266 int toret = rsa1_load_s(BinarySource_UPCAST(lf), key, passphrase, errstr);
\r
272 * See whether an RSA key is encrypted. Return its comment field as
\r
275 bool rsa1_encrypted_s(BinarySource *src, char **comment)
\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
285 return false; /* couldn't even open the file */
\r
287 bool toret = rsa1_encrypted_s(BinarySource_UPCAST(lf), comment);
\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
296 int rsa1_loadpub_s(BinarySource *src, BinarySink *bs,
\r
297 char **commentptr, const char **errorstr)
\r
301 const char *error = NULL;
\r
303 /* Default return if we fail. */
\r
306 bool is_privkey_file = expect_signature(src, rsa1_signature);
\r
307 BinarySource_REWIND(src);
\r
309 if (is_privkey_file) {
\r
311 * Load just the public half from an SSH-1 private key file.
\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
321 * Try interpreting the file as an SSH-1 public key.
\r
323 char *line, *p, *bitsp, *expp, *modp, *commentp;
\r
325 line = mkstr(get_chomped_line(src));
\r
329 p += strspn(p, "0123456789");
\r
331 goto not_public_either;
\r
335 p += strspn(p, "0123456789");
\r
337 goto not_public_either;
\r
341 p += strspn(p, "0123456789");
\r
344 goto not_public_either;
\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
358 error = "key bit count does not match in SSH-1 public key file";
\r
362 *commentptr = commentp ? dupstr(commentp) : NULL;
\r
363 rsa_ssh1_public_blob(bs, &key, RSA_SSH1_EXPONENT_FIRST);
\r
370 error = "not an SSH-1 RSA file";
\r
374 if ((ret != 1) && errorstr)
\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
386 int toret = rsa1_loadpub_s(BinarySource_UPCAST(lf), bs,
\r
387 commentptr, errorstr);
\r
392 strbuf *rsa1_save_sb(RSAKey *key, const char *passphrase)
\r
394 strbuf *buf = strbuf_new_nm();
\r
398 * The public part of the key.
\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
408 * The encrypted portion starts here.
\r
413 * Two bytes, then the same two bytes repeated.
\r
417 random_read(bytes, 2);
\r
418 put_data(buf, bytes, 2);
\r
419 put_data(buf, bytes, 2);
\r
423 * Four more bignums: the decryption exponent, then iqmp, then
\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
432 * Now write zeros until the encrypted portion is a multiple of
\r
435 put_padding(buf, (estart - buf->len) & 7, 0);
\r
438 * Now encrypt the encrypted portion.
\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
452 * Save an RSA key file. Return true on success.
\r
454 bool rsa1_save_f(const Filename *filename, RSAKey *key, const char *passphrase)
\r
456 FILE *fp = f_open(filename, "wb", true);
\r
460 strbuf *buf = rsa1_save_sb(key, passphrase);
\r
461 bool toret = fwrite(buf->s, 1, buf->len, fp) == buf->len;
\r
468 /* ----------------------------------------------------------------------
\r
469 * SSH-2 private key load/store functions.
\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
475 static bool read_header(BinarySource *src, char *header)
\r
482 if (c == '\n' || c == '\r' || get_err(src))
\r
483 return false; /* failure */
\r
489 return true; /* success! */
\r
492 return false; /* failure */
\r
496 return false; /* failure */
\r
499 static char *read_body(BinarySource *src)
\r
501 strbuf *buf = strbuf_new_nm();
\r
504 int c = get_byte(src);
\r
505 if (c == '\r' || c == '\n' || get_err(src)) {
\r
506 if (!get_err(src)) {
\r
508 if (c != '\r' && c != '\n' && !get_err(src))
\r
511 return strbuf_to_str(buf);
\r
517 static bool read_blob(BinarySource *src, int nlines, BinarySink *bs)
\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
529 linelen = strlen(line);
\r
530 if (linelen % 4 != 0 || linelen > 64) {
\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
541 put_data(bs, decoded, k);
\r
549 * Magic error return value for when the passphrase is wrong.
\r
551 ssh2_userkey ssh2_wrong_passphrase = { NULL, NULL };
\r
553 const ssh_keyalg *const all_keyalgs[] = {
\r
558 &ssh_ecdsa_nistp256,
\r
559 &ssh_ecdsa_nistp384,
\r
560 &ssh_ecdsa_nistp521,
\r
561 &ssh_ecdsa_ed25519,
\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
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
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
602 size_t blocklen, keylen, ivlen;
\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
614 switch (fmt_version) {
\r
616 if (ciphertype->keylen == 0) {
\r
620 ptrlen empty = PTRLEN_LITERAL("");
\r
624 uint32_t taglen = ciphertype->keylen + ciphertype->ivlen + mac_keylen;
\r
626 if (params->argon2_passes_auto) {
\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
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
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
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
671 unreachable("bad format version in ssh2_ppk_derive_keys");
\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
684 unsigned long ul = strtoul(text, &endptr, 10);
\r
685 if (*text && !*endptr && ul < MAX_KEY_BLOB_LINES)
\r
691 static bool str_to_uint32_t(const char *s, uint32_t *out)
\r
694 unsigned long converted = strtoul(s, &endptr, 10);
\r
695 if (*s && !*endptr && converted <= ~(uint32_t)0) {
\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
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
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
728 if (0 == strcmp(header, "PuTTY-User-Key-File-3")) {
\r
730 } else if (0 == strcmp(header, "PuTTY-User-Key-File-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
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
742 error = "not a PuTTY SSH-2 private key";
\r
745 error = "file format error";
\r
746 if ((b = read_body(src)) == NULL)
\r
748 /* Select key algorithm structure. */
\r
749 alg = find_pubkey_alg(b);
\r
756 /* Read the Encryption header line. */
\r
757 if (!read_header(src, header) || 0 != strcmp(header, "Encryption"))
\r
759 if ((encryption = read_body(src)) == NULL)
\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
769 /* Read the Comment header line. */
\r
770 if (!read_header(src, header) || 0 != strcmp(header, "Comment"))
\r
772 if ((comment = read_body(src)) == NULL)
\r
775 memset(¶ms, 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
781 if ((b = read_body(src)) == NULL)
\r
783 i = userkey_parse_line_counter(b);
\r
787 public_blob = strbuf_new();
\r
788 if (!read_blob(src, i, BinarySink_UPCAST(public_blob)))
\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
795 if ((b = read_body(src)) == NULL)
\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
809 if (!read_header(src, header) || 0 != strcmp(header, "Argon2-Memory"))
\r
811 if ((b = read_body(src)) == NULL)
\r
813 if (!str_to_uint32_t(b, ¶ms.argon2_mem)) {
\r
819 if (!read_header(src, header) || 0 != strcmp(header, "Argon2-Passes"))
\r
821 if ((b = read_body(src)) == NULL)
\r
823 if (!str_to_uint32_t(b, ¶ms.argon2_passes)) {
\r
829 if (!read_header(src, header) ||
\r
830 0 != strcmp(header, "Argon2-Parallelism"))
\r
832 if ((b = read_body(src)) == NULL)
\r
834 if (!str_to_uint32_t(b, ¶ms.argon2_parallelism)) {
\r
840 if (!read_header(src, header) || 0 != strcmp(header, "Argon2-Salt"))
\r
842 if ((b = read_body(src)) == NULL)
\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
851 put_byte(passphrase_salt, strtoul(s, NULL, 16));
\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
863 if ((b = read_body(src)) == NULL)
\r
865 i = userkey_parse_line_counter(b);
\r
869 private_blob = strbuf_new_nm();
\r
870 if (!read_blob(src, i, BinarySink_UPCAST(private_blob)))
\r
873 /* Read the Private-MAC or Private-Hash header line. */
\r
874 if (!read_header(src, header))
\r
876 if (0 == strcmp(header, "Private-MAC")) {
\r
877 if ((mac = read_body(src)) == NULL)
\r
880 } else if (0 == strcmp(header, "Private-Hash") && fmt_version == 1) {
\r
881 if ((mac = read_body(src)) == NULL)
\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), ¶ms);
\r
894 * Decrypt the private blob.
\r
896 if (private_blob->len % ciphertype->blocklen)
\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
907 unsigned char binary[32];
\r
908 char realmac[sizeof(binary) * 2 + 1];
\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
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
926 put_string(macdata, private_blob->s,
\r
927 private_blob->len);
\r
928 free_macdata = true;
\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
941 hash_simple(&ssh_sha1, ptrlen_from_strbuf(macdata), binary);
\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
957 error = "MAC failed";
\r
965 * Create and return the key.
\r
967 ret = snew(ssh2_userkey);
\r
968 ret->comment = comment;
\r
970 ret->key = ssh_key_new_priv(
\r
971 alg, ptrlen_from_strbuf(public_blob),
\r
972 ptrlen_from_strbuf(private_blob));
\r
976 error = "createkey failed";
\r
982 * Error processing.
\r
992 strbuf_free(public_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
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
1009 toret = ppk_load_s(BinarySource_UPCAST(lf), passphrase, errorstr);
\r
1013 *errorstr = "can't open file";
\r
1018 static bool rfc4716_loadpub(BinarySource *src, char **algorithm,
\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
1027 unsigned char base64out[3];
\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
1036 sfree(line); line = NULL;
\r
1039 line = mkstr(get_chomped_line(src));
\r
1041 error = "truncated SSH-2 public key file";
\r
1044 colon = strstr(line, ": ");
\r
1048 value = colon + 2;
\r
1050 if (!strcmp(line, "Comment")) {
\r
1053 /* Remove containing double quotes, if present */
\r
1055 if (*p == '"' && p[strlen(p)-1] == '"') {
\r
1056 p[strlen(p)-1] = '\0';
\r
1060 /* Remove \-escaping, not in RFC4716 but seen in the wild
\r
1062 for (q = line; *p; p++) {
\r
1063 if (*p == '\\' && p[1])
\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
1075 error = "unrecognised header in SSH-2 public key file";
\r
1079 sfree(line); line = NULL;
\r
1083 * Now line contains the initial line of base64 data. Loop round
\r
1084 * while it still does contain base64.
\r
1086 pubblob = strbuf_new();
\r
1088 while (line && line[0] != '-') {
\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
1098 sfree(line); line = NULL;
\r
1099 line = mkstr(get_chomped_line(src));
\r
1103 * Finally, check the END line makes sense.
\r
1105 if (!line || 0 != strcmp(line, "---- END SSH2 PUBLIC KEY ----")) {
\r
1106 error = "invalid end line in SSH-2 public key file";
\r
1109 sfree(line); line = NULL;
\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
1116 if (pubblob->len < 4) {
\r
1117 error = "not enough data in SSH-2 public key file";
\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
1126 *algorithm = dupprintf("%.*s", alglen, pubblob->s+4);
\r
1128 *commentptr = comment;
\r
1131 put_datapl(bs, ptrlen_from_strbuf(pubblob));
\r
1132 strbuf_free(pubblob);
\r
1139 strbuf_free(pubblob);
\r
1141 *errorstr = error;
\r
1145 static bool openssh_loadpub(BinarySource *src, char **algorithm,
\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
1156 line = mkstr(get_chomped_line(src));
\r
1158 base64 = strchr(line, ' ');
\r
1160 error = "no key blob in OpenSSH public key file";
\r
1165 comment = strchr(base64, ' ');
\r
1167 *comment++ = '\0';
\r
1168 comment = dupstr(comment);
\r
1171 pubblobsize = strlen(base64) / 4 * 3;
\r
1172 pubblob = snewn(pubblobsize, unsigned char);
\r
1175 while (!memchr(base64, '\0', 4)) {
\r
1176 assert(pubbloblen + 3 <= pubblobsize);
\r
1177 pubbloblen += base64_decode_atom(base64, pubblob + pubbloblen);
\r
1181 error = "invalid length for base64 data in OpenSSH public key file";
\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
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
1202 *algorithm = dupstr(line);
\r
1204 *commentptr = comment;
\r
1208 put_data(bs, pubblob, pubbloblen);
\r
1217 *errorstr = error;
\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
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
1236 } else if (type == SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH) {
\r
1237 bool ret = openssh_loadpub(src, algorithm, bs, commentptr, errorstr);
\r
1239 } else if (type != SSH_KEYTYPE_SSH2) {
\r
1240 error = "not a public key or a PuTTY SSH-2 private key";
\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
1252 error = "not a public key or a PuTTY SSH-2 private key";
\r
1255 error = "file format error";
\r
1256 if ((b = read_body(src)) == NULL)
\r
1258 /* Select key algorithm structure. */
\r
1259 alg = find_pubkey_alg(b);
\r
1265 /* Read the Encryption header line. */
\r
1266 if (!read_header(src, header) || 0 != strcmp(header, "Encryption"))
\r
1268 if ((b = read_body(src)) == NULL)
\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
1275 if ((comment = read_body(src)) == NULL)
\r
1279 *commentptr = 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
1286 if ((b = read_body(src)) == NULL)
\r
1288 i = userkey_parse_line_counter(b);
\r
1292 if (!read_blob(src, i, bs))
\r
1296 *algorithm = dupstr(alg->ssh_id);
\r
1300 * Error processing.
\r
1304 *errorstr = error;
\r
1305 if (comment && commentptr) {
\r
1307 *commentptr = NULL;
\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
1319 bool toret = ppk_loadpub_s(BinarySource_UPCAST(lf), algorithm, bs,
\r
1320 commentptr, errorstr);
\r
1325 bool ppk_encrypted_s(BinarySource *src, char **commentptr)
\r
1327 char header[40], *b, *comment;
\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
1339 if ((b = read_body(src)) == NULL) {
\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
1347 if ((b = read_body(src)) == NULL) {
\r
1351 /* Read the Comment header line. */
\r
1352 if (!read_header(src, header) || 0 != strcmp(header, "Comment")) {
\r
1356 if ((comment = read_body(src)) == NULL) {
\r
1362 *commentptr = comment;
\r
1366 if (!strcmp(b, "aes256-cbc"))
\r
1374 bool ppk_encrypted_f(const Filename *filename, char **commentptr)
\r
1376 LoadedFile *lf = lf_load_keyfile(filename, NULL);
\r
1379 *commentptr = NULL;
\r
1383 bool toret = ppk_encrypted_s(BinarySource_UPCAST(lf), commentptr);
\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
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
1402 .argon2_flavour = Argon2id,
\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
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
1417 .argon2_mem = 8192, /* require 8 Mb memory */
\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
1426 .argon2_passes_auto = true,
\r
1427 .argon2_milliseconds = 100,
\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
1439 .argon2_parallelism = 1,
\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
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
1456 * Fetch the key component blobs.
\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
1464 * Determine encryption details, and encrypt the private blob.
\r
1467 cipherstr = "aes256-cbc";
\r
1469 ciphertype = &ppk_cipher_aes256_cbc;
\r
1471 cipherstr = "none";
\r
1473 ciphertype = &ppk_cipher_none;
\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
1497 put_data(passphrase_salt, params.salt, params.saltlen);
\r
1499 random_read(strbuf_append(passphrase_salt, 16), 16);
\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), ¶ms);
\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
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
1527 assert(cipherkey.len == 32);
\r
1528 aes256_encrypt_pubkey(cipherkey.ptr, cipheriv.ptr,
\r
1529 priv_blob_encrypted, priv_encrypted_len);
\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
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
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
1577 strbuf *buf = ppk_save_sb(key, passphrase, params);
\r
1578 bool toret = fwrite(buf->s, 1, buf->len, fp) == buf->len;
\r
1585 /* ----------------------------------------------------------------------
\r
1586 * Output public keys.
\r
1588 char *ssh1_pubkey_str(RSAKey *key)
\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
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
1610 static char *ssh2_pubkey_openssh_str_internal(const char *comment,
\r
1611 const void *v_pub_blob,
\r
1614 const unsigned char *ssh2blob = (const unsigned char *)v_pub_blob;
\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
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
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
1643 strcpy(p, comment);
\r
1650 char *ssh2_pubkey_openssh_str(ssh2_userkey *key)
\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
1664 void ssh2_write_pubkey(FILE *fp, const char *comment,
\r
1665 const void *v_pub_blob, int pub_len,
\r
1668 unsigned char *pub_blob = (unsigned char *)v_pub_blob;
\r
1670 if (keytype == SSH_KEYTYPE_SSH2_PUBLIC_RFC4716) {
\r
1674 fprintf(fp, "---- BEGIN SSH2 PUBLIC KEY ----\n");
\r
1677 fprintf(fp, "Comment: \"");
\r
1678 for (p = comment; *p; p++) {
\r
1679 if (*p == '\\' || *p == '\"')
\r
1683 fprintf(fp, "\"\n");
\r
1688 while (i < pub_len) {
\r
1690 int n = (pub_len - i < 3 ? pub_len - i : 3);
\r
1691 base64_encode_atom(pub_blob + i, n, buf);
\r
1695 if (++column >= 16) {
\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
1710 unreachable("Bad key type in ssh2_write_pubkey");
\r
1714 /* ----------------------------------------------------------------------
\r
1715 * Utility functions to compute SSH-2 fingerprints in a uniform way.
\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
1735 unsigned len = 32-i;
\r
1738 base64_encode_atom(digest + i, len, buf);
\r
1739 put_data(sb, buf, 4);
\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
1750 * Identify the key algorithm, if possible.
\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
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
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
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
1775 put_fmt(sb, "%.*s ", PTRLEN_PRINTF(algname));
\r
1779 switch (ssh_fptype_from_cert(fptype)) {
\r
1780 case SSH_FPTYPE_MD5:
\r
1781 ssh2_fingerprint_blob_md5(blob, sb);
\r
1783 case SSH_FPTYPE_SHA256:
\r
1784 ssh2_fingerprint_blob_sha256(blob, sb);
\r
1787 unreachable("ssh_fptype_from_cert ruled out the other values");
\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
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
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
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
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
1854 void ssh2_free_all_fingerprints(char **fps)
\r
1856 for (unsigned i = 0; i < SSH_N_FPTYPES; i++)
\r
1861 /* ----------------------------------------------------------------------
\r
1862 * Determine the type of a private key file.
\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
1915 int key_type(const Filename *filename)
\r
1917 LoadedFile *lf = lf_new(1024);
\r
1918 if (lf_load(lf, filename) == LF_ERROR) {
\r
1920 return SSH_KEYTYPE_UNOPENABLE;
\r
1923 int toret = key_type_s(BinarySource_UPCAST(lf));
\r
1929 * Convert the type word to a string, for `wrong type' error
\r
1932 const char *key_type_to_str(int 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
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
1962 case SSH_KEYTYPE_OPENSSH_AUTO:
\r
1963 unreachable("OPENSSH_AUTO should never reach key_type_to_str");
\r
1965 unreachable("bad key type in key_type_to_str");
\r