add OpenSSH Private Key decryption demo

Signed-off-by: Steffen Jaeckel <s@jaeckel.eu>
This commit is contained in:
Steffen Jaeckel 2019-09-26 16:23:18 +02:00
parent f27a312a6c
commit bee3ad2a2a
3 changed files with 279 additions and 4 deletions

2
.gitignore vendored
View file

@ -34,6 +34,8 @@ multi
multi.exe
openssl-enc
openssl-enc.exe
openssh-privkey
openssh-privkey.exe
sizes
sizes.exe
small

272
demos/openssh-privkey.c Normal file
View file

@ -0,0 +1,272 @@
/* LibTomCrypt, modular cryptographic library -- Tom St Denis
*
* LibTomCrypt is a library that provides various cryptographic
* algorithms in a highly modular and flexible manner.
*
* The library is free for all purposes without any express
* guarantee it works.
*/
/**
@file openssh-privkey.c
OpenSSH Private Key decryption demo, Steffen Jaeckel
*/
#include <bsd/string.h>
#include <tomcrypt.h>
#include <stdarg.h>
static int verbose = 1;
static void print_hex(const char* what, const void* v, const unsigned long l)
{
const unsigned char* p = v;
unsigned long x, y = 0, z;
if (!verbose) return;
fprintf(stderr, "%s contents: \n", what);
for (x = 0; x < l; ) {
fprintf(stderr, "%02X ", p[x]);
if (!(++x % 16) || x == l) {
if((x % 16) != 0) {
z = 16 - (x % 16);
if(z >= 8)
fprintf(stderr, " ");
for (; z != 0; --z) {
fprintf(stderr, " ");
}
}
fprintf(stderr, " | ");
for(; y < x; y++) {
if((y % 8) == 0)
fprintf(stderr, " ");
if(isgraph(p[y]))
fprintf(stderr, "%c", p[y]);
else
fprintf(stderr, ".");
}
fprintf(stderr, "\n");
}
else if((x % 8) == 0) {
fprintf(stderr, " ");
}
}
}
static void print_err(const char *fmt, ...)
{
va_list args;
if (!verbose) return;
va_start(args, fmt);
vfprintf(stderr, fmt, args);
}
static void die_(int err, int line)
{
print_err("%3d: LTC sez %s\n", line, error_to_string(err));
exit(EXIT_FAILURE);
}
#define die(i) do { die_(i, __LINE__); } while(0)
#define DIE(s) do { print_err("%3d: " s "\n", __LINE__); exit(EXIT_FAILURE); } while(0)
enum blockcipher_mode {
none, cbc, ctr, stream, gcm
};
struct ssh_blockcipher {
const char *name;
const char *cipher;
int len;
enum blockcipher_mode mode;
};
/* Table as of
* https://www.iana.org/assignments/ssh-parameters/ssh-parameters.xhtml#ssh-parameters-17
*/
const struct ssh_blockcipher ssh_ciphers[] =
{
{ "aes256-cbc", "aes", 256 / 8, cbc },
{ 0 },
};
struct ssh_decode_ctx {
unsigned char *orig;
size_t len, rd;
void *cur;
};
ulong32 read_length_and_advance(int is_length, struct ssh_decode_ctx *ctx) {
ulong32 r;
ctx->cur = &ctx->orig[ctx->rd];
LOAD32H(r, ctx->cur);
print_err("%u\n", r);
if (is_length && ctx->rd + r > ctx->len) DIE("too long");
ctx->rd += 4;
ctx->cur = &ctx->orig[ctx->rd];
if (is_length) print_hex("next", ctx->cur, r);
return r;
}
/* The basic format of the key is described here:
* https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.key
*/
int main(int argc, char **argv)
{
if (argc < 2) return EXIT_FAILURE;
int err;
if ((err = register_all_ciphers()) != CRYPT_OK) {
die(err);
}
if ((err = register_all_hashes()) != CRYPT_OK) {
die(err);
}
char pem[100 * 72];
size_t w = 0;
const char *b64_start = "-----BEGIN OPENSSH PRIVATE KEY-----";
const char *b64_end = "-----END OPENSSH PRIVATE KEY-----";
char buf[72];
FILE *f = fopen(argv[1], "r");
if (f == NULL) DIE("fopen sez no");
while (fgets(buf, sizeof(buf), f)) {
const char *start = strstr(buf, b64_start);
if (start == buf) break;
}
while (fgets(buf, sizeof(buf), f)) {
char *end = strstr(buf, b64_end);
if (end != NULL) {
if (end == buf) break;
*end = '\0';
}
size_t l = strlcpy(pem + w, buf, sizeof(pem) - w);
if (l == 0) {
DIE("strlcpy sez no");
}
w += l;
}
unsigned char b64_decoded[sizeof(pem)];
unsigned long b64_decoded_len = sizeof(pem);
if ((err = base64_sane_decode(pem, w, b64_decoded, &b64_decoded_len)) != CRYPT_OK) {
die(err);
}
print_hex("decoded", b64_decoded, b64_decoded_len);
void *magic = strstr((const char*)b64_decoded, "openssh-key-v1");
if (magic == NULL) DIE("magic not found");
if (magic != b64_decoded) DIE("magic not at the beginning");
struct ssh_decode_ctx dec_ctx;
dec_ctx.orig = b64_decoded;
dec_ctx.len = b64_decoded_len;
dec_ctx.rd = strlen("openssh-key-v1") + 1;
ulong32 l = read_length_and_advance(1, &dec_ctx);
const struct ssh_blockcipher *c = NULL;
for (size_t i = 0; i < sizeof(ssh_ciphers)/sizeof(ssh_ciphers[0]); ++i) {
if (memcmp(dec_ctx.cur, ssh_ciphers[i].name, l) == 0) {
c = &ssh_ciphers[i];
break;
}
}
if (c == NULL) DIE("can't find cipher");
dec_ctx.rd += l;
l = read_length_and_advance(1, &dec_ctx);
if (memcmp(dec_ctx.cur, "bcrypt", l) != 0) DIE("unsupported kdf");
dec_ctx.rd += l;
l = read_length_and_advance(1, &dec_ctx);
unsigned char salt[32];
unsigned long salt_len = read_length_and_advance(1, &dec_ctx);
memcpy(salt, dec_ctx.cur, salt_len);
dec_ctx.rd += salt_len;
ulong32 rounds = read_length_and_advance(0, &dec_ctx);
ulong32 num_pubkeys = read_length_and_advance(1, &dec_ctx);
l = read_length_and_advance(1, &dec_ctx);
l = read_length_and_advance(1, &dec_ctx);
if (memcmp(dec_ctx.cur, "ssh-ed25519", l) != 0) DIE("unsupported pka");
dec_ctx.rd += l;
unsigned char pubkey[32];
unsigned long pubkey_len = read_length_and_advance(1, &dec_ctx);
memcpy(pubkey, dec_ctx.cur, pubkey_len);
dec_ctx.rd += pubkey_len;
l = read_length_and_advance(1, &dec_ctx);
unsigned char decrypted_privkey[sizeof(pem)];
unsigned long decrypted_privkey_len = sizeof(decrypted_privkey);
int cipher = find_cipher(c->cipher);
unsigned char symkey[128];
unsigned long symkey_len = c->len + cipher_descriptor[cipher].block_length;
if (sizeof(symkey) < symkey_len) DIE("too small");
if ((err = bcrypt("abc123", 6, salt, salt_len, rounds, find_hash("sha512"), symkey, &symkey_len)) != CRYPT_OK) {
die(err);
}
symmetric_CBC cbc_ctx;
if ((err = cbc_start(cipher, symkey + c->len, symkey, c->len, 0, &cbc_ctx)) != CRYPT_OK) {
die(err);
}
if ((err = cbc_decrypt(dec_ctx.cur, decrypted_privkey, l, &cbc_ctx)) != CRYPT_OK) {
die(err);
}
decrypted_privkey_len = l;
print_hex("decrypted", decrypted_privkey, decrypted_privkey_len);
dec_ctx.orig = decrypted_privkey;
dec_ctx.len = decrypted_privkey_len;
dec_ctx.rd = 0;
ulong32 check1 = read_length_and_advance(0, &dec_ctx);
ulong32 check2 = read_length_and_advance(0, &dec_ctx);
if (check1 != check2) DIE("decrypt failed");
l = read_length_and_advance(1, &dec_ctx);
if (memcmp(dec_ctx.cur, "ssh-ed25519", l) != 0) DIE("unsupported pka");
dec_ctx.rd += l;
l = read_length_and_advance(1, &dec_ctx);
if (memcmp(dec_ctx.cur, pubkey, l) != 0) DIE("pubkey's don't match");
dec_ctx.rd += l;
unsigned char privkey[64];
unsigned long privkey_len = read_length_and_advance(1, &dec_ctx);
memcpy(privkey, dec_ctx.cur, privkey_len);
if (memcmp(&privkey[32], pubkey, 32) != 0) DIE("pubkey in privkey doesn't match");
curve25519_key k;
if ((err = ed25519_set_key(privkey, 32, &privkey[32], 32, &k)) != CRYPT_OK) {
die(err);
}
return EXIT_SUCCESS;
}
/* ref: $Format:%D$ */
/* git commit: $Format:%H$ */
/* commit time: $Format:%ai$ */

View file

@ -174,10 +174,11 @@ USEABLE_DEMOS = ltcrypt sizes constants
TEST_DEMOS = small tv_gen
# Demos that are in one config broken
# aesgcm - can't be built with LTC_EASY
# openssl-enc - can't be built with LTC_EASY
# timing - not really broken, but older gcc builds spit warnings
BROKEN_DEMOS = aesgcm openssl-enc timing
# aesgcm - can't be built with LTC_EASY
# openssl-enc - can't be built with LTC_EASY
# openssh-privkey - can't be built with LTC_EASY
# timing - not really broken, but older gcc builds spit warnings
BROKEN_DEMOS = aesgcm openssl-enc openssh-privkey timing
# Combine demos in groups
UNBROKEN_DEMOS = $(TEST_DEMOS) $(USEABLE_DEMOS) $(USEFUL_DEMOS)