-
Notifications
You must be signed in to change notification settings - Fork 2.5k
PuTTY .ppk v3 support #6003
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
elipsion
wants to merge
3
commits into
openwall:bleeding-jumbo
Choose a base branch
from
elipsion:bleeding-jumbo
base: bleeding-jumbo
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+545
−179
Open
PuTTY .ppk v3 support #6003
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -16,8 +16,8 @@ | |
| #endif | ||
|
|
||
| #include <stdint.h> | ||
| #include <stddef.h> /* for size_t */ | ||
| #include <string.h> /* for memcpy() */ | ||
| #include <stddef.h> /* for size_t */ | ||
| #include <string.h> /* for memcpy() */ | ||
| #include <stdio.h> | ||
| #include <string.h> | ||
| #include <stdlib.h> | ||
|
|
@@ -31,6 +31,7 @@ | |
|
|
||
| #include "memory.h" | ||
| #include "jumbo.h" | ||
| #include "common.h" | ||
| #if _MSC_VER | ||
| #include <io.h> | ||
| #endif | ||
|
|
@@ -44,8 +45,8 @@ | |
|
|
||
| struct ssh2_userkey { | ||
| const struct ssh_signkey *alg; /* the key algorithm */ | ||
| void *data; /* the key data */ | ||
| char *comment; /* the key comment */ | ||
| void *data; /* the key data */ | ||
| char *comment; /* the key comment */ | ||
| }; | ||
|
|
||
| enum { | ||
|
|
@@ -63,18 +64,23 @@ static const char *putty_error = NULL; | |
| static int i, is_mac, old_fmt; | ||
| static char alg[32]; | ||
| static int cipher, cipherblk; | ||
| static int ppk_version; | ||
| static char *key_derivation; | ||
| static int argon2_memory, argon2_passes, argon2_parallelism; | ||
| static unsigned char argon2_salt[64]; | ||
| static int argon2_salt_len; | ||
| static unsigned char *public_blob, *private_blob; | ||
| static int public_blob_len, private_blob_len; | ||
|
|
||
| static char *read_body(FILE * fp) | ||
| static char *read_body(FILE *fp) | ||
| { | ||
| char *text; | ||
| int len; | ||
| int size; | ||
| int c; | ||
|
|
||
| size = 128 * 1024; | ||
| text = (char*)malloc(size); | ||
| text = (char *)malloc(size); | ||
| if (!text) { | ||
| fprintf(stderr, "malloc failed in read_body, exiting!\n"); | ||
| exit(-1); | ||
|
|
@@ -101,7 +107,7 @@ static char *read_body(FILE * fp) | |
| } | ||
| } | ||
|
|
||
| static unsigned char *read_blob(FILE * fp, int nlines, int *bloblen) | ||
| static unsigned char *read_blob(FILE *fp, int nlines, int *bloblen) | ||
| { | ||
| unsigned char *blob; | ||
| char *line; | ||
|
|
@@ -113,7 +119,7 @@ static unsigned char *read_blob(FILE * fp, int nlines, int *bloblen) | |
| return NULL; | ||
|
|
||
| /* We expect at most 64 base64 characters, ie 48 real bytes, per line. */ | ||
| blob = (unsigned char*)malloc(48 * nlines); | ||
| blob = (unsigned char *)malloc(48 * nlines); | ||
| if (!blob) { | ||
| fprintf(stderr, "malloc failed in read_blob, exiting!\n"); | ||
| exit(-1); | ||
|
|
@@ -148,36 +154,43 @@ static unsigned char *read_blob(FILE * fp, int nlines, int *bloblen) | |
| return blob; | ||
| } | ||
|
|
||
| static int read_header(FILE * fp, char *header) | ||
| static int read_header(FILE *fp, char *header) | ||
| { | ||
| int len = 39; | ||
| int c; | ||
|
|
||
| while (len > 0) { | ||
| c = fgetc(fp); | ||
| if (c == '\n' || c == '\r' || c == EOF) | ||
| return 0; /* failure */ | ||
| return 0; /* failure */ | ||
| if (c == ':') { | ||
| c = fgetc(fp); | ||
| if (c != ' ') | ||
| return 0; | ||
| *header = '\0'; | ||
| return 1; /* success! */ | ||
| return 1; /* success! */ | ||
| } | ||
| if (len == 0) | ||
| return 0; /* failure */ | ||
| return 0; /* failure */ | ||
| *header++ = c; | ||
| len--; | ||
| } | ||
| return 0; /* failure */ | ||
| return 0; /* failure */ | ||
| } | ||
|
|
||
|
|
||
| static int init_LAME(const char *filename) { | ||
| static int init_LAME(const char *filename) | ||
| { | ||
| FILE *fp; | ||
|
|
||
| common_init(); | ||
|
|
||
| argon2_memory = argon2_passes = argon2_parallelism = 0; | ||
| argon2_salt_len = 0; | ||
| encryption = comment = mac = NULL; | ||
| key_derivation = NULL; | ||
| public_blob = private_blob = NULL; | ||
| ppk_version = 0; | ||
|
|
||
| fp = fopen(filename, "rb"); | ||
| if (!fp) { | ||
|
|
@@ -188,9 +201,14 @@ static int init_LAME(const char *filename) { | |
| /* Read the first header line which contains the key type. */ | ||
| if (!read_header(fp, header)) | ||
| goto error; | ||
| if (0 == strcmp(header, "PuTTY-User-Key-File-2")) { | ||
| if (0 == strcmp(header, "PuTTY-User-Key-File-3")) { | ||
| ppk_version = 3; | ||
| old_fmt = 0; | ||
| } else if (0 == strcmp(header, "PuTTY-User-Key-File-2")) { | ||
| ppk_version = 2; | ||
| old_fmt = 0; | ||
| } else if (0 == strcmp(header, "PuTTY-User-Key-File-1")) { | ||
| ppk_version = 1; | ||
| /* this is an old key file; warn and then continue */ | ||
| // old_keyfile_warning(); | ||
| old_fmt = 1; | ||
|
|
@@ -244,6 +262,57 @@ static int init_LAME(const char *filename) { | |
| if ((public_blob = read_blob(fp, i, &public_blob_len)) == NULL) | ||
| goto error; | ||
|
|
||
| if (ppk_version >= 3 && cipher) { | ||
| /* Parse Argon2 KDF section used in PPK v3 encrypted keys. */ | ||
| if (!read_header(fp, header) || 0 != strcmp(header, "Key-Derivation")) | ||
| goto error; | ||
| if ((key_derivation = read_body(fp)) == NULL) | ||
| goto error; | ||
| if (strcmp(key_derivation, "Argon2d") && strcmp(key_derivation, "Argon2i") && strcmp(key_derivation, "Argon2id")) | ||
| goto error; | ||
|
|
||
| if (!read_header(fp, header) || 0 != strcmp(header, "Argon2-Memory")) | ||
| goto error; | ||
| if ((b = read_body(fp)) == NULL) | ||
| goto error; | ||
| argon2_memory = atoi(b); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To avoid potential UB in this and other |
||
| MEM_FREE(b); | ||
| if (argon2_memory <= 0) | ||
| goto error; | ||
|
|
||
| if (!read_header(fp, header) || 0 != strcmp(header, "Argon2-Passes")) | ||
| goto error; | ||
| if ((b = read_body(fp)) == NULL) | ||
| goto error; | ||
| argon2_passes = atoi(b); | ||
| MEM_FREE(b); | ||
| if (argon2_passes <= 0) | ||
| goto error; | ||
|
|
||
| if (!read_header(fp, header) || 0 != strcmp(header, "Argon2-Parallelism")) | ||
| goto error; | ||
| if ((b = read_body(fp)) == NULL) | ||
| goto error; | ||
| argon2_parallelism = atoi(b); | ||
| MEM_FREE(b); | ||
| if (argon2_parallelism <= 0) | ||
| goto error; | ||
|
|
||
| if (!read_header(fp, header) || 0 != strcmp(header, "Argon2-Salt")) | ||
| goto error; | ||
| if ((b = read_body(fp)) == NULL) | ||
| goto error; | ||
| int extra; | ||
| argon2_salt_len = hexlenl(b, &extra); | ||
| if (extra || argon2_salt_len > (int)sizeof(argon2_salt) * 2) | ||
| goto error; | ||
| for (i = 0; i < argon2_salt_len; i++) | ||
| argon2_salt[i] = atoi16[ARCH_INDEX(b[i * 2])] * 16 + atoi16[ARCH_INDEX(b[i * 2 + 1])]; | ||
| MEM_FREE(b); | ||
| if (argon2_salt_len <= 0) | ||
| goto error; | ||
| } | ||
|
|
||
| /* Read the Private-Lines header line and the Private blob. */ | ||
| if (!read_header(fp, header) || 0 != strcmp(header, "Private-Lines")) | ||
| goto error; | ||
|
|
@@ -277,6 +346,7 @@ static int init_LAME(const char *filename) { | |
| fclose(fp); | ||
| MEM_FREE(comment); | ||
| MEM_FREE(encryption); | ||
| MEM_FREE(key_derivation); | ||
| MEM_FREE(mac); | ||
| MEM_FREE(public_blob); | ||
| MEM_FREE(private_blob); | ||
|
|
@@ -286,41 +356,56 @@ static int init_LAME(const char *filename) { | |
| static void print_hex(unsigned char *str, int len) | ||
| { | ||
| int i; | ||
|
|
||
| for (i = 0; i < len; ++i) | ||
| printf("%02x", str[i]); | ||
| } | ||
|
|
||
| static void LAME_ssh2_load_userkey(const char *path, const char **errorstr) | ||
| { | ||
| const char *ext[] = {".ppk"}; | ||
| const char *ext[] = { ".ppk" }; | ||
| char *fname; | ||
|
|
||
| /* | ||
| * Decrypt the private blob. | ||
| */ | ||
| * Decrypt the private blob. | ||
| */ | ||
| if (cipher) { | ||
| if (private_blob_len % cipherblk) | ||
| goto error; | ||
| } | ||
|
|
||
| { | ||
| fname = strip_suffixes(basename(path), ext, 1); | ||
| printf("%s:$putty$%d*%d*%d*%d*%s*%d*", fname, cipher, cipherblk, is_mac, old_fmt, mac, public_blob_len); | ||
| print_hex(public_blob, public_blob_len); | ||
| printf("*%d*", private_blob_len); | ||
| print_hex(private_blob, private_blob_len); | ||
| if (!old_fmt) { | ||
| if (ppk_version >= 3) { | ||
| printf("%s:$putty$3*%d*%d*%d*%d*%s*%d*%d*%d*", fname, | ||
| cipher, cipherblk, is_mac, old_fmt, | ||
| key_derivation ? key_derivation : "none", argon2_memory, argon2_passes, argon2_parallelism); | ||
| print_hex(argon2_salt, argon2_salt_len); | ||
| printf("*%s*%d*", mac, public_blob_len); | ||
| print_hex(public_blob, public_blob_len); | ||
| printf("*%d*", private_blob_len); | ||
| print_hex(private_blob, private_blob_len); | ||
| printf("*%s*%s*%s\n", alg, encryption, comment); | ||
| } | ||
| else { | ||
| printf("\n"); | ||
| } else { | ||
| printf("%s:$putty$%d*%d*%d*%d*%s*%d*", fname, cipher, cipherblk, is_mac, old_fmt, mac, public_blob_len); | ||
| print_hex(public_blob, public_blob_len); | ||
| printf("*%d*", private_blob_len); | ||
| print_hex(private_blob, private_blob_len); | ||
| if (!old_fmt) { | ||
| printf("*%s*%s*%s\n", alg, encryption, comment); | ||
| } else { | ||
| printf("\n"); | ||
| } | ||
| } | ||
| MEM_FREE(comment); | ||
| MEM_FREE(key_derivation); | ||
| return; | ||
| } | ||
| error: | ||
| fprintf(stderr, "Something failed!\n"); | ||
| MEM_FREE(comment); | ||
| MEM_FREE(encryption); | ||
| MEM_FREE(key_derivation); | ||
| MEM_FREE(mac); | ||
| MEM_FREE(public_blob); | ||
| MEM_FREE(private_blob); | ||
|
|
@@ -350,15 +435,15 @@ static int key_type(const char *filename) | |
| return SSH_KEYTYPE_UNOPENABLE; | ||
| if (i < 32) | ||
| return SSH_KEYTYPE_UNKNOWN; | ||
| if (!memcmp(buf, rsa_signature, sizeof(rsa_signature)-1)) | ||
| if (!memcmp(buf, rsa_signature, sizeof(rsa_signature) - 1)) | ||
| return SSH_KEYTYPE_SSH1; | ||
| if (!memcmp(buf, putty2_sig, sizeof(putty2_sig)-1)) | ||
| if (!memcmp(buf, putty2_sig, sizeof(putty2_sig) - 1)) | ||
| return SSH_KEYTYPE_SSH2; | ||
| if (!memcmp(buf, openssh_sig, sizeof(openssh_sig)-1)) | ||
| if (!memcmp(buf, openssh_sig, sizeof(openssh_sig) - 1)) | ||
| return SSH_KEYTYPE_OPENSSH; | ||
| if (!memcmp(buf, sshcom_sig, sizeof(sshcom_sig)-1)) | ||
| if (!memcmp(buf, sshcom_sig, sizeof(sshcom_sig) - 1)) | ||
| return SSH_KEYTYPE_SSHCOM; | ||
| return SSH_KEYTYPE_UNKNOWN; /* unrecognised or EOF */ | ||
| return SSH_KEYTYPE_UNKNOWN; /* unrecognised or EOF */ | ||
| } | ||
|
|
||
| static int ssh2_userkey_encrypted(const char *filename, char **commentptr) | ||
|
|
@@ -374,16 +459,16 @@ static int ssh2_userkey_encrypted(const char *filename, char **commentptr) | |
| if (!fp) | ||
| return 0; | ||
| if (!read_header(fp, header) | ||
| || (0 != strcmp(header, "PuTTY-User-Key-File-2") && | ||
| 0 != strcmp(header, "PuTTY-User-Key-File-1"))) { | ||
| || (0 != strcmp(header, "PuTTY-User-Key-File-3") && | ||
| 0 != strcmp(header, "PuTTY-User-Key-File-2") && 0 != strcmp(header, "PuTTY-User-Key-File-1"))) { | ||
| fclose(fp); | ||
| return 0; | ||
| } | ||
| if ((b = read_body(fp)) == NULL) { | ||
| fclose(fp); | ||
| return 0; | ||
| } | ||
| MEM_FREE(b); /* we don't care about key type here */ | ||
| MEM_FREE(b); /* we don't care about key type here */ | ||
| /* Read the Encryption header line. */ | ||
| if (!read_header(fp, header) || 0 != strcmp(header, "Encryption")) { | ||
| fclose(fp); | ||
|
|
@@ -440,7 +525,7 @@ static int base64_decode_atom(char *atom, unsigned char *out) | |
| else if (c == '=') | ||
| v = -1; | ||
| else | ||
| return 0; /* invalid atom */ | ||
| return 0; /* invalid atom */ | ||
| vals[i] = v; | ||
| } | ||
|
|
||
|
|
@@ -456,8 +541,7 @@ static int base64_decode_atom(char *atom, unsigned char *out) | |
| else | ||
| len = 1; | ||
|
|
||
| word = ((vals[0] << 18) | | ||
| (vals[1] << 12) | ((vals[2] & 0x3F) << 6) | (vals[3] & 0x3F)); | ||
| word = ((vals[0] << 18) | (vals[1] << 12) | ((vals[2] & 0x3F) << 6) | (vals[3] & 0x3F)); | ||
| out[0] = (word >> 16) & 0xFF; | ||
| if (len > 1) | ||
| out[1] = (word >> 8) & 0xFF; | ||
|
|
@@ -512,7 +596,7 @@ static void process_file(const char *filename) | |
| if (type == SSH_KEYTYPE_SSH1) { | ||
| fprintf(stderr, "SSH1 key type not supported!\n"); | ||
| goto out; | ||
| } else { // SSH_KEYTYPE_SSH2 | ||
| } else { // SSH_KEYTYPE_SSH2 | ||
| if (realtype == type) { | ||
| LAME_ssh2_load_userkey(filename, &errmsg); | ||
| } | ||
|
|
@@ -522,6 +606,7 @@ static void process_file(const char *filename) | |
|
|
||
| MEM_FREE(comment); | ||
| MEM_FREE(encryption); | ||
| MEM_FREE(key_derivation); | ||
| MEM_FREE(mac); | ||
| MEM_FREE(public_blob); | ||
| MEM_FREE(private_blob); | ||
|
|
@@ -551,8 +636,8 @@ int main(int argc, char **argv) | |
| int i; | ||
|
|
||
| if (argc < 2) { | ||
| printf( "Usage: putty2john [.ppk PuTTY-Private-Key-File(s)]\n"); | ||
| printf( "\nKey types supported: RSA, DSA, ECDSA, ED25519\n"); | ||
| printf("Usage: putty2john [.ppk PuTTY-Private-Key-File(s)]\n"); | ||
| printf("\nKey types supported: RSA, DSA, ECDSA, ED25519\n"); | ||
| exit(1); | ||
| } | ||
|
|
||
|
|
||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please preserve (re-add) the blank line after this (should be two blank lines separating this from older release news), and I suggest dropping the mention of tunable costs (too minor, as I recall we don't include that in news for other formats).