Prechádzať zdrojové kódy

re-factor openssh-privkey demo into library functions

This adds two new API functions
* `pem_decode_openssh()`
* `pem_decode_openssh_filehandle()`

It also introduces the following two new types:
* a new union type `ltc_pka_key` which can hold any PKA key type.
* a `password_ctx` type with a callback to retrieve a password
  if necessary.

Signed-off-by: Steffen Jaeckel <[email protected]>
Steffen Jaeckel 3 rokov pred
rodič
commit
cf71fffbd9

+ 15 - 453
demos/openssh-privkey.c

@@ -1,64 +1,17 @@
-/* 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.
- */
+/* LibTomCrypt, modular cryptographic library -- Tom St Denis */
+/* SPDX-License-Identifier: Unlicense */
 
 /**
   @file openssh-privkey.c
   OpenSSH Private Key decryption demo, Steffen Jaeckel
-
-  The basic format of the key is described here:
-  https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.key
 */
 
-#define _GNU_SOURCE
-
-#include <bsd/string.h>
-#include <tomcrypt_private.h>
+#include <tomcrypt.h>
 #include <stdarg.h>
 
 static int verbose = 0;
 
-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, " ");
-      }
-  }
-}
-
+#if defined(LTC_PEM_SSH)
 static void print_err(const char *fmt, ...)
 {
    va_list args;
@@ -79,370 +32,21 @@ static void die_(int err, int line)
 #define die(i) do { die_(i, __LINE__); } while(0)
 #define DIE(s, ...) do { verbose = 1; print_err("%3d: " s "\n", __LINE__, ##__VA_ARGS__); exit(EXIT_FAILURE); } while(0)
 
-
-static void check_padding(const unsigned char *p, unsigned long len)
-{
-   unsigned char pad = 0x1u;
-   while (len != 0) {
-      if (*p != pad) DIE("pad wrong 0x%02x != 0x%02x", *p, pad);
-      p++;
-      pad++;
-      len--;
-   }
-}
-
-typedef struct pka_key_ {
-   enum ltc_oid_id id;
-   union {
-      curve25519_key ed25519;
-      ecc_key ecdsa;
-      rsa_key rsa;
-   } u;
-} pka_key;
-
-enum blockcipher_mode {
-   none, cbc, ctr, stream, gcm
-};
-struct ssh_blockcipher {
-   const char *name;
-   const char *algo;
-   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[] =
-{
-   { "none", "", 0, none },
-   { "aes256-cbc", "aes", 256 / 8, cbc },
-   { 0 },
-};
-
-struct kdf_options {
-   const char *name;
-   const struct ssh_blockcipher *cipher;
-   unsigned char salt[64];
-   ulong32 saltlen;
-   ulong32 num_rounds;
-   const char *pass;
-   unsigned long passlen;
-};
-
-int ssh_find_init_ecc(const char *pka, pka_key *key)
-{
-   int err;
-   const char* prefix = "ecdsa-sha2-";
-   size_t prefixlen = strlen(prefix);
-   const ltc_ecc_curve *cu;
-   if (strstr(pka, prefix) == NULL) return CRYPT_PK_INVALID_TYPE;
-   if ((err = ecc_find_curve(pka + prefixlen, &cu)) != CRYPT_OK) return err;
-   return ecc_set_curve(cu, &key->u.ecdsa);
-}
-
-int ssh_decode_ecdsa(const unsigned char *in, unsigned long *inlen, pka_key *key)
-{
-   int err;
-   unsigned char groupname[64], group[512], privkey[512];
-   unsigned long groupnamelen = sizeof(groupname), grouplen = sizeof(group), privkeylen = sizeof(privkey);
-
-   if ((err = ssh_decode_sequence_multi(in, inlen,
-                                        LTC_SSHDATA_STRING, groupname, &groupnamelen,
-                                        LTC_SSHDATA_STRING, group, &grouplen,
-                                        LTC_SSHDATA_STRING, privkey, &privkeylen,
-                                        LTC_SSHDATA_EOL)) != CRYPT_OK) {
-      die(err);
-   }
-
-   if ((err = ecc_set_key(privkey, privkeylen, PK_PRIVATE, &key->u.ecdsa)) != CRYPT_OK) {
-      die(err);
-   }
-
-   key->id = PKA_EC;
-
-   zeromem(groupname, sizeof(groupname));
-   zeromem(group, sizeof(group));
-   zeromem(privkey, sizeof(privkey));
-
-   return err;
-}
-
-int ssh_decode_ed25519(const unsigned char *in, unsigned long *inlen, pka_key *key)
-{
-   int err;
-   unsigned char pubkey[2048], privkey[2048];
-   unsigned long pubkeylen = sizeof(pubkey), privkeylen = sizeof(privkey);
-
-   if ((err = ssh_decode_sequence_multi(in, inlen,
-                                        LTC_SSHDATA_STRING, pubkey, &pubkeylen,
-                                        LTC_SSHDATA_STRING, privkey, &privkeylen,
-                                        LTC_SSHDATA_EOL)) != CRYPT_OK) {
-      die(err);
-   }
-
-   if ((err = ed25519_import_raw(&privkey[32], 32, PK_PRIVATE, &key->u.ed25519)) != CRYPT_OK) {
-      die(err);
-   }
-
-   key->id = PKA_ED25519;
-
-   zeromem(pubkey, sizeof(pubkey));
-   zeromem(privkey, sizeof(privkey));
-
-   return err;
-}
-
-int ssh_decode_rsa(const unsigned char *in, unsigned long *inlen, pka_key *key)
-{
-   int err;
-   void *tmp1, *tmp2;
-   if ((err = mp_init_multi(&tmp1, &tmp2, NULL)) != CRYPT_OK) {
-      die(err);
-   }
-   if ((err = rsa_init(&key->u.rsa)) != CRYPT_OK) {
-      die(err);
-   }
-
-   if ((err = ssh_decode_sequence_multi(in, inlen,
-                                        LTC_SSHDATA_MPINT, key->u.rsa.N,
-                                        LTC_SSHDATA_MPINT, key->u.rsa.e,
-                                        LTC_SSHDATA_MPINT, key->u.rsa.d,
-                                        LTC_SSHDATA_MPINT, key->u.rsa.qP,
-                                        LTC_SSHDATA_MPINT, key->u.rsa.q,
-                                        LTC_SSHDATA_MPINT, key->u.rsa.p,
-                                        LTC_SSHDATA_EOL)) != CRYPT_OK) {
-      die(err);
-   }
-
-   if ((err = mp_sub_d(key->u.rsa.p, 1,  tmp1)) != CRYPT_OK)                     { die(err); } /* tmp1 = q-1 */
-   if ((err = mp_sub_d(key->u.rsa.q, 1,  tmp2)) != CRYPT_OK)                     { die(err); } /* tmp2 = p-1 */
-   if ((err = mp_mod( key->u.rsa.d,  tmp1,  key->u.rsa.dP)) != CRYPT_OK)         { die(err); } /* dP = d mod p-1 */
-   if ((err = mp_mod( key->u.rsa.d,  tmp2,  key->u.rsa.dQ)) != CRYPT_OK)         { die(err); } /* dQ = d mod q-1 */
-
-   key->id = PKA_RSA;
-
-   mp_clear_multi(tmp2, tmp1, NULL);
-
-   return err;
-}
-
-struct ssh_pka {
-   const char *name;
-   int (*init)(const char*, pka_key*);
-   int (*decode)(const unsigned char*, unsigned long*, pka_key*);
-};
-
-struct ssh_pka ssh_pkas[] = {
-                             { "ssh-ed25519", NULL,              ssh_decode_ed25519 },
-                             { "ssh-rsa",     NULL,              ssh_decode_rsa },
-                             { NULL,          ssh_find_init_ecc, ssh_decode_ecdsa },
-};
-
-int ssh_decode_private_key(const unsigned char *in, unsigned long *inlen, pka_key *key)
-{
-   int err;
-   ulong32 check1, check2;
-   unsigned char pka[64], comment[256];
-   unsigned long pkalen = sizeof(pka), commentlen = sizeof(comment);
-   unsigned long remaining, cur_len;
-   const unsigned char *p;
-   size_t n;
-
-   LTC_ARGCHK(in    != NULL);
-   LTC_ARGCHK(inlen != NULL);
-   LTC_ARGCHK(key   != NULL);
-
-   p = in;
-   cur_len = *inlen;
-
-   if ((err = ssh_decode_sequence_multi(p, &cur_len,
-                                        LTC_SSHDATA_UINT32, &check1,
-                                        LTC_SSHDATA_UINT32, &check2,
-                                        LTC_SSHDATA_STRING, pka, &pkalen,
-                                        LTC_SSHDATA_EOL)) != CRYPT_OK) {
-      die(err);
-   }
-   if (check1 != check2) DIE("decrypt failed");
-
-   p += cur_len;
-   remaining = *inlen - cur_len;
-   cur_len = remaining;
-
-   for (n = 0; n < sizeof(ssh_pkas)/sizeof(ssh_pkas[0]); ++n) {
-      if (ssh_pkas[n].name != NULL) {
-         if (XSTRCMP((char*)pka, ssh_pkas[n].name) != 0) continue;
-      } else {
-         if ((ssh_pkas[n].init == NULL) ||
-               (ssh_pkas[n].init((char*)pka, key) != CRYPT_OK)) continue;
-      }
-      if ((err = ssh_pkas[n].decode(p, &cur_len, key)) != CRYPT_OK) {
-         die(err);
-      }
-      break;
-   }
-   if (n == sizeof(ssh_pkas)/sizeof(ssh_pkas[0])) DIE("unsupported pka %s", pka);
-
-   p += cur_len;
-   remaining -= cur_len;
-   cur_len = remaining;
-
-   if ((err = ssh_decode_sequence_multi(p, &cur_len,
-                                        LTC_SSHDATA_STRING, comment, &commentlen,
-                                        LTC_SSHDATA_EOL)) != CRYPT_OK) {
-      die(err);
-   }
-
-   printf("comment: %s\n", comment);
-
-   p += cur_len;
-   remaining -= cur_len;
-
-   check_padding(p, remaining);
-
-   return err;
-}
-
-int ssh_decrypt_private_keys(unsigned char *in, unsigned long *inlen, struct kdf_options *opts)
+static int password_get(void **p, unsigned long *l, void *u)
 {
-   int err, cipher;
-   unsigned char symkey[128];
-   unsigned long symkey_len;
-   symmetric_CBC cbc_ctx;
-
-   LTC_ARGCHK(in    != NULL);
-   LTC_ARGCHK(inlen != NULL);
-   LTC_ARGCHK(opts  != NULL);
-
-   cipher = find_cipher(opts->cipher->algo);
-   symkey_len = opts->cipher->len + cipher_descriptor[cipher].block_length;
-
-   if (sizeof(symkey) < symkey_len) DIE("too small");
-
-   if ((err = bcrypt_pbkdf_openbsd(opts->pass, opts->passlen, opts->salt, opts->saltlen, opts->num_rounds, find_hash("sha512"), symkey, &symkey_len)) != CRYPT_OK) {
-      die(err);
-   }
-
-   if ((err = cbc_start(cipher, symkey + opts->cipher->len, symkey, opts->cipher->len, 0, &cbc_ctx)) != CRYPT_OK) {
-      die(err);
-   }
-   if ((err = cbc_decrypt(in, in, *inlen, &cbc_ctx)) != CRYPT_OK) {
-      die(err);
-   }
-   print_hex("decrypted", in, *inlen);
-
-   zeromem(symkey, sizeof(symkey));
-   zeromem(&cbc_ctx, sizeof(cbc_ctx));
-
-   return err;
-}
-
-int ssh_decode_header(unsigned char *in, unsigned long *inlen, struct kdf_options *opts)
-{
-   int err;
-   unsigned char ciphername[64], kdfname[64], kdfoptions[128], pubkey1[2048];
-   unsigned long ciphernamelen = sizeof(ciphername), kdfnamelen = sizeof(kdfname);
-   unsigned long kdfoptionslen = sizeof(kdfoptions), pubkey1len = sizeof(pubkey1);
-   ulong32 num_keys;
-   size_t i;
-
-   void *magic = strstr((const char*)in, "openssh-key-v1");
-   size_t slen = strlen("openssh-key-v1");
-   unsigned char *start = &in[slen + 1];
-   unsigned long len = *inlen - slen - 1;
-
-   if (magic == NULL) DIE("magic not found");
-   if (magic != in) DIE("magic not at the beginning");
-
-   if ((err = ssh_decode_sequence_multi(start, &len,
-                                        LTC_SSHDATA_STRING, ciphername, &ciphernamelen,
-                                        LTC_SSHDATA_STRING, kdfname, &kdfnamelen,
-                                        LTC_SSHDATA_STRING, kdfoptions, &kdfoptionslen,
-                                        LTC_SSHDATA_UINT32, &num_keys,
-                                        LTC_SSHDATA_STRING, pubkey1, &pubkey1len,
-                                        LTC_SSHDATA_EOL)) != CRYPT_OK) {
-      die(err);
-   }
-   if (num_keys != 1) DIE("more than 1 pubkey not supported");
-
-   print_hex("public key", pubkey1, pubkey1len);
-
-   *inlen = len + slen + 1;
-
-   for (i = 0; i < sizeof(ssh_ciphers)/sizeof(ssh_ciphers[0]); ++i) {
-      if (XSTRCMP((char*)ciphername, ssh_ciphers[i].name) == 0) {
-         opts->cipher = &ssh_ciphers[i];
-         break;
-      }
-   }
-   if (opts->cipher == NULL) DIE("can't find algo");
-
-   if (XSTRCMP((char*)kdfname, "none") == 0) {
-      /* NOP */
-      opts->name = "none";
-   } else if (XSTRCMP((char*)kdfname, "bcrypt") == 0) {
-      opts->name = "bcrypt";
-      opts->saltlen = sizeof(opts->salt);
-      len = kdfoptionslen;
-      if ((err = ssh_decode_sequence_multi(kdfoptions, &len,
-                                           LTC_SSHDATA_STRING, opts->salt, &opts->saltlen,
-                                           LTC_SSHDATA_UINT32, &opts->num_rounds,
-                                           LTC_SSHDATA_EOL)) != CRYPT_OK) {
-         die(err);
-      }
-      if (len != kdfoptionslen) DIE("unused data %lu", kdfoptionslen-len);
-   } else {
-      DIE("unsupported kdf %s", kdfname);
-   }
-
-   return err;
-}
-
-
-void read_openssh_private_key(FILE *f, char *pem, size_t *w)
-{
-   const char *openssh_privkey_start = "-----BEGIN OPENSSH PRIVATE KEY-----";
-   const char *openssh_privkey_end = "-----END OPENSSH PRIVATE KEY-----";
-   char buf[81];
-   char *end;
-   size_t n = 0;
-
-   while (fgets(buf, sizeof(buf), f)) {
-      const char *start = strstr(buf, openssh_privkey_start);
-      if (start != NULL) {
-         size_t l;
-         start += strlen(openssh_privkey_start);
-         l = strlcpy(pem + n, start, *w - n);
-         n += l;
-         break;
-      }
-   }
-   while (fgets(buf, sizeof(buf), f)) {
-      size_t l = strlcpy(pem + n, buf, *w - n);
-      if (l == 0) {
-         DIE("strlcpy sez no");
-      }
-      n += l;
-   }
-   end = strstr(pem, openssh_privkey_end);
-   if (end == NULL) DIE("could not find PEM end-tag");
-   *end = '\0';
-   *w = end - pem;
+   (void)u;
+   *p = strdup("abc123");
+   *l = strlen(*p);
+   return 0;
 }
 
 int main(int argc, char **argv)
 {
    int err;
 
-   char pem[100 * 72];
-   size_t plen = sizeof(pem);
    FILE *f = NULL;
-   unsigned char b64_decoded[sizeof(pem)], privkey[sizeof(pem)];
-   unsigned long b64_decoded_len = sizeof(pem), privkey_len = sizeof(privkey);
-   struct kdf_options opts;
-   unsigned char *p;
-   unsigned long len;
-   pka_key k;
+   ltc_pka_key k;
+   password_ctx pw_ctx = { .callback = password_get };
 
    if ((err = register_all_ciphers()) != CRYPT_OK) {
       die(err);
@@ -458,53 +62,11 @@ int main(int argc, char **argv)
    else f = stdin;
    if (f == NULL) DIE("fopen sez no");
 
-   read_openssh_private_key(f, pem, &plen);
-
-   if ((err = base64_sane_decode(pem, plen, b64_decoded, &b64_decoded_len)) != CRYPT_OK) {
+   if ((err = pem_decode_openssh_filehandle(f, &k, &pw_ctx))) {
       die(err);
    }
-
-   p = b64_decoded;
-   plen = b64_decoded_len;
-   len = plen;
-
-   print_hex("decoded", p, plen);
-
-   if ((err = ssh_decode_header(p, &len, &opts)) != CRYPT_OK) {
-      die(err);
-   }
-   p += len;
-   plen -= len;
-   len = plen;
-
-   print_hex("remaining", p, plen);
-
-   if ((err = ssh_decode_sequence_multi(p, &len,
-                                        LTC_SSHDATA_STRING, privkey, &privkey_len,
-                                        LTC_SSHDATA_EOL)) != CRYPT_OK) {
-      die(err);
-   }
-   p += len;
-   plen -= len;
-
-   opts.pass = "abc123";
-   opts.passlen = 6;
-
-   if (XSTRCMP(opts.name, "none") != 0) {
-      len = privkey_len;
-      if ((err = ssh_decrypt_private_keys(privkey, &len, &opts)) != CRYPT_OK) {
-         die(err);
-      }
-   }
-
-   len = privkey_len;
-   if ((err = ssh_decode_private_key(privkey, &len, &k)) != CRYPT_OK) {
-      die(err);
-   }
-
    return EXIT_SUCCESS;
 }
-
-/* ref:         $Format:%D$ */
-/* git commit:  $Format:%H$ */
-/* commit time: $Format:%ai$ */
+#else
+int main(void) { return EXIT_FAILURE; }
+#endif

+ 23 - 7
src/headers/tomcrypt_custom.h

@@ -522,6 +522,8 @@
 
 #define LTC_PBES
 
+#define LTC_PEM
+
 #endif /* LTC_NO_MISC */
 
 /* cleanup */
@@ -566,6 +568,27 @@
 #endif
 #endif /* LTC_MECC */
 
+#ifndef LTC_NO_FILE
+   /* buffer size for reading from a file via fread(..) */
+   #ifndef LTC_FILE_READ_BUFSIZE
+   #define LTC_FILE_READ_BUFSIZE 8192
+   #endif
+#endif
+
+#if defined(LTC_PEM)
+   /* Size of the decoded data buffer */
+   #ifndef LTC_PEM_READ_BUFSIZE
+      #ifdef LTC_FILE_READ_BUFSIZE
+         #define LTC_PEM_READ_BUFSIZE LTC_FILE_READ_BUFSIZE
+      #else
+         #define LTC_PEM_READ_BUFSIZE 4096
+      #endif
+   #endif
+   #if defined(LTC_SSH)
+      #define LTC_PEM_SSH
+   #endif
+#endif
+
 #if defined(LTC_DER)
    #ifndef LTC_DER_MAX_RECURSION
       /* Maximum recursion limit when processing nested ASN.1 types. */
@@ -708,13 +731,6 @@
 /* define this if you use Valgrind, note: it CHANGES the way SOBER-128 and RC4 work (see the code) */
 /* #define LTC_VALGRIND */
 
-#ifndef LTC_NO_FILE
-   /* buffer size for reading from a file via fread(..) */
-   #ifndef LTC_FILE_READ_BUFSIZE
-   #define LTC_FILE_READ_BUFSIZE 8192
-   #endif
-#endif
-
 /* ECC backwards compatibility */
 #if !defined(LTC_ECC_SECP112R1) && defined(LTC_ECC112)
 #define LTC_ECC_SECP112R1

+ 13 - 0
src/headers/tomcrypt_misc.h

@@ -159,6 +159,19 @@ int padding_pad(unsigned char *data, unsigned long length, unsigned long* padded
 int padding_depad(const unsigned char *data, unsigned long *length, unsigned long mode);
 #endif  /* LTC_PADDING */
 
+#ifdef LTC_PEM
+typedef struct {
+   int (*callback)(void **, unsigned long *, void *);
+   void *userdata;
+} password_ctx;
+
+#ifdef LTC_SSH
+int pem_decode_openssh_filehandle(FILE *f, ltc_pka_key *k, password_ctx *pw_ctx);
+int pem_decode_openssh(void *buf, unsigned long len, ltc_pka_key *k, password_ctx *pw_ctx);
+#endif
+
+#endif /* LTC_PEM */
+
 #ifdef LTC_SSH
 typedef enum ssh_data_type_ {
    LTC_SSHDATA_EOL,

+ 32 - 0
src/headers/tomcrypt_pk.h

@@ -485,6 +485,38 @@ int dsa_shared_secret(void          *private_key, void *base,
                       unsigned char *out,         unsigned long *outlen);
 #endif /* LTC_MDSA */
 
+
+enum ltc_pka_id {
+   LTC_PKA_UNDEF = 0,
+   LTC_PKA_RSA,
+   LTC_PKA_DSA,
+   LTC_PKA_EC,
+   LTC_PKA_CURVE25519,
+   LTC_PKA_DH,
+};
+
+typedef struct {
+   union {
+#ifdef LTC_CURVE25519
+      curve25519_key curve25519;
+#endif
+#ifdef LTC_MDH
+      dh_key dh;
+#endif
+#ifdef LTC_MDSA
+      dsa_key dsa;
+#endif
+#ifdef LTC_MECC
+      ecc_key ecc;
+#endif
+#ifdef LTC_MRSA
+      rsa_key rsa;
+#endif
+   } u;
+   enum ltc_pka_id id;
+} ltc_pka_key;
+
+
 #ifdef LTC_DER
 /* DER handling */
 

+ 53 - 0
src/headers/tomcrypt_private.h

@@ -247,6 +247,56 @@ int base64_encode_pem(const unsigned char *in,  unsigned long inlen,
                                      char *out, unsigned long *outlen,
                             unsigned int  flags);
 
+/* PEM related */
+
+struct password {
+   /* usually a `char*` but could also contain binary data
+    * so use a `void*` + length to be on the safe side.
+    */
+   void *pw;
+   unsigned long l;
+};
+
+struct str {
+   char *p;
+   unsigned long len;
+};
+
+#define SET_STR(n, s) n.p = s, n.len = XSTRLEN(s)
+#define SET_CSTR(n, s) n.p = (char*)s, n.len = XSTRLEN(s)
+
+enum more_headers {
+   no,
+   yes,
+   maybe
+};
+
+struct pem_headers {
+   const struct {
+      struct str start, end;
+      enum more_headers has_more_headers;
+   };
+};
+
+struct bufp {
+   /* `end` points to one byte after the last
+    * element of the allocated buffer
+    */
+   char *p, *r, *end;
+};
+
+#define SET_BUFP(n, d, l) n.p = d, n.r = d, n.end = (char*)d + l + 1
+
+struct get_char {
+   int (*get)(struct get_char*);
+   union {
+      FILE *f;
+      struct bufp buf;
+   };
+};
+
+/* others */
+
 void copy_or_zeromem(const unsigned char* src, unsigned char* dest, unsigned long len, int coz);
 
 int pbes_decrypt(const pbes_arg  *arg, unsigned char *dec_data, unsigned long *dec_size);
@@ -254,6 +304,9 @@ int pbes_decrypt(const pbes_arg  *arg, unsigned char *dec_data, unsigned long *d
 int pbes1_extract(const ltc_asn1_list *s, pbes_arg *res);
 int pbes2_extract(const ltc_asn1_list *s, pbes_arg *res);
 
+int pem_get_char_from_file(struct get_char *g);
+int pem_get_char_from_buf(struct get_char *g);
+int pem_read(void *pem, unsigned long *w, struct pem_headers *hdr, struct get_char *g);
 
 /* tomcrypt_pk.h */
 

+ 7 - 0
src/misc/crypt/crypt.c

@@ -470,9 +470,16 @@ const char *crypt_build_settings =
     " PBES1 "
     " PBES2 "
 #endif
+#if defined(LTC_PEM)
+    " PEM "
+    " " NAME_VALUE(LTC_PEM_READ_BUFSIZE) " "
+#endif
 #if defined(LTC_SSH)
     " SSH "
 #endif
+#if defined(LTC_PEM_SSH)
+    " OpenSSH-PEM "
+#endif
 #if defined(LTC_DEVRANDOM)
     " LTC_DEVRANDOM "
 #endif

+ 129 - 0
src/misc/pem/pem_decode.c

@@ -0,0 +1,129 @@
+/* LibTomCrypt, modular cryptographic library -- Tom St Denis */
+/* SPDX-License-Identifier: Unlicense */
+#include "tomcrypt_private.h"
+
+/**
+  @file pem_decode.c
+  Decode and import a PEM file, Steffen Jaeckel
+*/
+
+#ifdef LTC_PEM
+
+/* Encrypted PEM files */
+#define PEM_DECODE_BUFSZ 72
+
+int pem_get_char_from_file(struct get_char *g)
+{
+   return getc(g->f);
+}
+
+int pem_get_char_from_buf(struct get_char *g)
+{
+   int ret;
+   if (g->buf.r == g->buf.end) {
+      return -1;
+   }
+   ret = *g->buf.r;
+   g->buf.r++;
+   return ret;
+}
+
+static char* s_get_line(char *buf, unsigned long *buflen, struct get_char *g)
+{
+   unsigned long blen = 0;
+   int c = -1, c_;
+   while(blen < *buflen) {
+      c_ = c;
+      c = g->get(g);
+      if (c == '\n') {
+         buf[blen] = '\0';
+         if (c_ == '\r') {
+            buf[--blen] = '\0';
+         }
+         *buflen = blen;
+         return buf;
+      }
+      if (c == -1 || c == '\0') {
+         buf[blen] = '\0';
+         *buflen = blen;
+         return buf;
+      }
+      buf[blen] = c;
+      blen++;
+   }
+   return NULL;
+}
+
+static LTC_INLINE int s_fits_buf(void *dest, unsigned long to_write, void *end)
+{
+   unsigned char *d = dest;
+   unsigned char *e = end;
+   unsigned char *w = d + to_write;
+   if (w < d || w > e)
+      return 0;
+   return 1;
+}
+
+static int s_pem_decode_headers(struct pem_headers *hdr, struct get_char *g)
+{
+   return CRYPT_OK;
+}
+
+int pem_read(void *pem, unsigned long *w, struct pem_headers *hdr, struct get_char *g)
+{
+   char buf[PEM_DECODE_BUFSZ];
+   char *wpem = pem;
+   char *end = wpem + *w;
+   unsigned long slen, linelen;
+   int err, hdr_ok = 0;
+   int would_overflow = 0;
+
+   linelen = sizeof(buf);
+   if (s_get_line(buf, &linelen, g) == NULL) {
+      return CRYPT_INVALID_PACKET;
+   }
+   if (hdr->start.len != linelen || XMEMCMP(buf, hdr->start.p, hdr->start.len)) {
+      return CRYPT_INVALID_PACKET;
+   }
+
+   if ((err = s_pem_decode_headers(hdr, g)) != CRYPT_OK)
+      return err;
+
+   /* Read the base64 encoded part of the PEM */
+   slen = sizeof(buf);
+   while (s_get_line(buf, &slen, g)) {
+      if (slen == hdr->end.len && !XMEMCMP(buf, hdr->end.p, slen)) {
+         hdr_ok = 1;
+         break;
+      }
+      if (!would_overflow && s_fits_buf(wpem, slen, end)) {
+         XMEMCPY(wpem, buf, slen);
+      } else {
+         would_overflow = 1;
+      }
+      wpem += slen;
+      slen = sizeof(buf);
+   }
+   if (!hdr_ok)
+      return CRYPT_INVALID_PACKET;
+
+   if (would_overflow || !s_fits_buf(wpem, 1, end)) {
+      /* NUL termination */
+      wpem++;
+      /* prevent a wrap-around */
+      if (wpem < (char*)pem)
+         return CRYPT_OVERFLOW;
+      *w = wpem - (char*)pem;
+      return CRYPT_BUFFER_OVERFLOW;
+   }
+
+   *w = wpem - (char*)pem;
+   *wpem++ = '\0';
+
+   if ((err = base64_strict_decode(pem, *w, pem, w)) != CRYPT_OK) {
+      return err;
+   }
+   return CRYPT_OK;
+}
+
+#endif /* LTC_PEM */

+ 426 - 0
src/misc/pem/pem_ssh.c

@@ -0,0 +1,426 @@
+/* LibTomCrypt, modular cryptographic library -- Tom St Denis */
+/* SPDX-License-Identifier: Unlicense */
+#include "tomcrypt_private.h"
+
+/**
+  @file pem_ssh.c
+  SSH specific functionality to process PEM files, Steffen Jaeckel
+
+  The basic format of the key is described here:
+  https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.key
+*/
+
+#if defined(LTC_PEM_SSH)
+
+enum blockcipher_mode {
+   none, cbc, ctr, stream, gcm
+};
+struct ssh_blockcipher {
+   const char *name;
+   const char *algo;
+   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[] =
+{
+   { "none", "", 0, none },
+   { "aes256-cbc", "aes", 256 / 8, cbc },
+   { 0 },
+};
+
+struct kdf_options {
+   const char *name;
+   const struct ssh_blockcipher *cipher;
+   unsigned char salt[64];
+   ulong32 saltlen;
+   ulong32 num_rounds;
+   struct password pw;
+};
+
+#ifdef LTC_MECC
+int ssh_find_init_ecc(const char *pka, ltc_pka_key *key)
+{
+   int err;
+   const char* prefix = "ecdsa-sha2-";
+   unsigned long prefixlen = XSTRLEN(prefix);
+   const ltc_ecc_curve *cu;
+   if (strstr(pka, prefix) == NULL) return CRYPT_PK_INVALID_TYPE;
+   if ((err = ecc_find_curve(pka + prefixlen, &cu)) != CRYPT_OK) return err;
+   return ecc_set_curve(cu, &key->u.ecc);
+}
+
+int ssh_decode_ecdsa(const unsigned char *in, unsigned long *inlen, ltc_pka_key *key)
+{
+   int err;
+   unsigned char groupname[64], group[512], privkey[512];
+   unsigned long groupnamelen = sizeof(groupname), grouplen = sizeof(group), privkeylen = sizeof(privkey);
+
+   if ((err = ssh_decode_sequence_multi(in, inlen,
+                                        LTC_SSHDATA_STRING, groupname, &groupnamelen,
+                                        LTC_SSHDATA_STRING, group, &grouplen,
+                                        LTC_SSHDATA_STRING, privkey, &privkeylen,
+                                        LTC_SSHDATA_EOL,    NULL)) != CRYPT_OK) {
+      goto cleanup;
+   }
+
+   if ((err = ecc_set_key(privkey, privkeylen, PK_PRIVATE, &key->u.ecc)) != CRYPT_OK) {
+      goto cleanup;
+   }
+
+   key->id = LTC_PKA_EC;
+
+cleanup:
+   zeromem(groupname, sizeof(groupname));
+   zeromem(group, sizeof(group));
+   zeromem(privkey, sizeof(privkey));
+
+   return err;
+}
+#endif
+
+#ifdef LTC_CURVE25519
+int ssh_decode_ed25519(const unsigned char *in, unsigned long *inlen, ltc_pka_key *key)
+{
+   int err;
+   unsigned char pubkey[2048], privkey[2048];
+   unsigned long pubkeylen = sizeof(pubkey), privkeylen = sizeof(privkey);
+
+   if ((err = ssh_decode_sequence_multi(in, inlen,
+                                        LTC_SSHDATA_STRING, pubkey, &pubkeylen,
+                                        LTC_SSHDATA_STRING, privkey, &privkeylen,
+                                        LTC_SSHDATA_EOL,    NULL)) != CRYPT_OK) {
+      goto cleanup;
+   }
+
+   if ((err = ed25519_import_raw(&privkey[32], 32, PK_PRIVATE, &key->u.curve25519)) != CRYPT_OK) {
+      goto cleanup;
+   }
+
+   key->id = LTC_PKA_CURVE25519;
+
+cleanup:
+   zeromem(pubkey, sizeof(pubkey));
+   zeromem(privkey, sizeof(privkey));
+
+   return err;
+}
+#endif
+
+#ifdef LTC_MRSA
+int ssh_decode_rsa(const unsigned char *in, unsigned long *inlen, ltc_pka_key *key)
+{
+   int err;
+   void *tmp1, *tmp2;
+   if ((err = mp_init_multi(&tmp1, &tmp2, NULL)) != CRYPT_OK) {
+      goto cleanup;
+   }
+   if ((err = rsa_init(&key->u.rsa)) != CRYPT_OK) {
+      goto cleanup;
+   }
+
+   if ((err = ssh_decode_sequence_multi(in, inlen,
+                                        LTC_SSHDATA_MPINT, key->u.rsa.N,
+                                        LTC_SSHDATA_MPINT, key->u.rsa.e,
+                                        LTC_SSHDATA_MPINT, key->u.rsa.d,
+                                        LTC_SSHDATA_MPINT, key->u.rsa.qP,
+                                        LTC_SSHDATA_MPINT, key->u.rsa.q,
+                                        LTC_SSHDATA_MPINT, key->u.rsa.p,
+                                        LTC_SSHDATA_EOL,    NULL)) != CRYPT_OK) {
+      goto cleanup;
+   }
+
+   if ((err = mp_sub_d(key->u.rsa.p, 1,  tmp1)) != CRYPT_OK)                     { goto cleanup; } /* tmp1 = q-1 */
+   if ((err = mp_sub_d(key->u.rsa.q, 1,  tmp2)) != CRYPT_OK)                     { goto cleanup; } /* tmp2 = p-1 */
+   if ((err = mp_mod( key->u.rsa.d,  tmp1,  key->u.rsa.dP)) != CRYPT_OK)         { goto cleanup; } /* dP = d mod p-1 */
+   if ((err = mp_mod( key->u.rsa.d,  tmp2,  key->u.rsa.dQ)) != CRYPT_OK)         { goto cleanup; } /* dQ = d mod q-1 */
+
+   key->id = LTC_PKA_RSA;
+
+cleanup:
+   mp_clear_multi(tmp2, tmp1, NULL);
+
+   return err;
+}
+#endif
+
+struct ssh_pka {
+   const char *name;
+   int (*init)(const char*, ltc_pka_key*);
+   int (*decode)(const unsigned char*, unsigned long*, ltc_pka_key*);
+};
+
+struct ssh_pka ssh_pkas[] = {
+#ifdef LTC_CURVE25519
+                             { "ssh-ed25519", NULL,              ssh_decode_ed25519 },
+#endif
+#ifdef LTC_MRSA
+                             { "ssh-rsa",     NULL,              ssh_decode_rsa },
+#endif
+#ifdef LTC_MECC
+                             { NULL,          ssh_find_init_ecc, ssh_decode_ecdsa },
+#endif
+};
+
+static int s_decode_private_key(const unsigned char *in, unsigned long *inlen, ltc_pka_key *key)
+{
+   int err;
+   ulong32 check1, check2;
+   unsigned char pka[64], comment[256];
+   unsigned long pkalen = sizeof(pka), commentlen = sizeof(comment);
+   unsigned long remaining, cur_len;
+   const unsigned char *p;
+   unsigned long n;
+
+   LTC_ARGCHK(in    != NULL);
+   LTC_ARGCHK(inlen != NULL);
+   LTC_ARGCHK(key   != NULL);
+
+   p = in;
+   cur_len = *inlen;
+
+   if ((err = ssh_decode_sequence_multi(p, &cur_len,
+                                        LTC_SSHDATA_UINT32, &check1,
+                                        LTC_SSHDATA_UINT32, &check2,
+                                        LTC_SSHDATA_STRING, pka, &pkalen,
+                                        LTC_SSHDATA_EOL,    NULL)) != CRYPT_OK) {
+      return err;
+   }
+   if (check1 != check2) {
+      return CRYPT_INVALID_PACKET;
+   }
+
+   p += cur_len;
+   remaining = *inlen - cur_len;
+   cur_len = remaining;
+
+   for (n = 0; n < sizeof(ssh_pkas)/sizeof(ssh_pkas[0]); ++n) {
+      if (ssh_pkas[n].name != NULL) {
+         if (XSTRCMP((char*)pka, ssh_pkas[n].name) != 0) continue;
+      } else {
+         if ((ssh_pkas[n].init == NULL) ||
+               (ssh_pkas[n].init((char*)pka, key) != CRYPT_OK)) continue;
+      }
+      if ((err = ssh_pkas[n].decode(p, &cur_len, key)) != CRYPT_OK) {
+         return err;
+      }
+      break;
+   }
+   if (n == sizeof(ssh_pkas)/sizeof(ssh_pkas[0])) {
+      return CRYPT_PK_INVALID_TYPE;
+   }
+
+   p += cur_len;
+   remaining -= cur_len;
+   cur_len = remaining;
+
+   if ((err = ssh_decode_sequence_multi(p, &cur_len,
+                                        LTC_SSHDATA_STRING, comment, &commentlen,
+                                        LTC_SSHDATA_EOL,    NULL)) != CRYPT_OK) {
+      return err;
+   }
+
+   p += cur_len;
+   remaining -= cur_len;
+
+   return remaining ? padding_depad(p, &remaining, LTC_PAD_SSH) : CRYPT_OK;
+}
+
+static int s_decrypt_private_keys(unsigned char *in, unsigned long *inlen, struct kdf_options *opts)
+{
+   int err, cipher;
+   unsigned char symkey[128];
+   unsigned long symkey_len;
+   symmetric_CBC cbc_ctx;
+
+   LTC_ARGCHK(in    != NULL);
+   LTC_ARGCHK(inlen != NULL);
+   LTC_ARGCHK(opts  != NULL);
+
+   cipher = find_cipher(opts->cipher->algo);
+   if (cipher == -1) {
+      return CRYPT_INVALID_CIPHER;
+   }
+   symkey_len = opts->cipher->len + cipher_descriptor[cipher].block_length;
+
+   if (sizeof(symkey) < symkey_len) {
+      return CRYPT_OVERFLOW;
+   }
+
+   if ((err = bcrypt_pbkdf_openbsd(opts->pw.pw, opts->pw.l, opts->salt, opts->saltlen, opts->num_rounds, find_hash("sha512"), symkey, &symkey_len)) != CRYPT_OK) {
+      return err;
+   }
+
+   if ((err = cbc_start(cipher, symkey + opts->cipher->len, symkey, opts->cipher->len, 0, &cbc_ctx)) != CRYPT_OK) {
+      goto cleanup;
+   }
+   if ((err = cbc_decrypt(in, in, *inlen, &cbc_ctx)) != CRYPT_OK) {
+      goto cleanup;
+   }
+   if ((err = cbc_done(&cbc_ctx)) != CRYPT_OK) {
+      goto cleanup;
+   }
+
+cleanup:
+   zeromem(symkey, sizeof(symkey));
+   zeromem(&cbc_ctx, sizeof(cbc_ctx));
+
+   return err;
+}
+
+static int s_decode_header(unsigned char *in, unsigned long *inlen, struct kdf_options *opts)
+{
+   int err;
+   unsigned char ciphername[64], kdfname[64], kdfoptions[128], pubkey1[2048];
+   unsigned long ciphernamelen = sizeof(ciphername), kdfnamelen = sizeof(kdfname);
+   unsigned long kdfoptionslen = sizeof(kdfoptions), pubkey1len = sizeof(pubkey1);
+   ulong32 num_keys;
+   unsigned long i;
+
+   void *magic = strstr((const char*)in, "openssh-key-v1");
+   unsigned long slen = XSTRLEN("openssh-key-v1");
+   unsigned char *start = &in[slen + 1];
+   unsigned long len = *inlen - slen - 1;
+
+   if (magic == NULL || magic != in) {
+      return CRYPT_INVALID_PACKET;
+   }
+
+   if ((err = ssh_decode_sequence_multi(start, &len,
+                                        LTC_SSHDATA_STRING, ciphername, &ciphernamelen,
+                                        LTC_SSHDATA_STRING, kdfname, &kdfnamelen,
+                                        LTC_SSHDATA_STRING, kdfoptions, &kdfoptionslen,
+                                        LTC_SSHDATA_UINT32, &num_keys,
+                                        LTC_SSHDATA_STRING, pubkey1, &pubkey1len,
+                                        LTC_SSHDATA_EOL,    NULL)) != CRYPT_OK) {
+      return err;
+   }
+   if (num_keys != 1) {
+      return CRYPT_INVALID_PACKET;
+   }
+
+   *inlen = len + slen + 1;
+
+   for (i = 0; i < sizeof(ssh_ciphers)/sizeof(ssh_ciphers[0]); ++i) {
+      if (XSTRCMP((char*)ciphername, ssh_ciphers[i].name) == 0) {
+         opts->cipher = &ssh_ciphers[i];
+         break;
+      }
+   }
+   if (opts->cipher == NULL) {
+      return CRYPT_INVALID_CIPHER;
+   }
+
+   if (XSTRCMP((char*)kdfname, "none") == 0) {
+      /* NOP */
+      opts->name = "none";
+   } else if (XSTRCMP((char*)kdfname, "bcrypt") == 0) {
+      opts->name = "bcrypt";
+      opts->saltlen = sizeof(opts->salt);
+      len = kdfoptionslen;
+      if ((err = ssh_decode_sequence_multi(kdfoptions, &len,
+                                           LTC_SSHDATA_STRING, opts->salt, &opts->saltlen,
+                                           LTC_SSHDATA_UINT32, &opts->num_rounds,
+                                           LTC_SSHDATA_EOL,    NULL)) != CRYPT_OK) {
+         return err;
+      }
+      if (len != kdfoptionslen) {
+         return CRYPT_INPUT_TOO_LONG;
+      }
+   } else {
+      return CRYPT_INVALID_PACKET;
+   }
+
+   return err;
+}
+
+static const struct pem_headers pem_openssh =
+   {
+     SET_CSTR(.start, "-----BEGIN OPENSSH PRIVATE KEY-----"),
+     SET_CSTR(.end, "-----END OPENSSH PRIVATE KEY-----"),
+     .has_more_headers = 0
+   };
+
+static int s_decode_openssh(struct get_char *g, ltc_pka_key *k, password_ctx *pw_ctx)
+{
+   unsigned char *pem = NULL, *p, *privkey = NULL;
+   unsigned long w, l, privkey_len;
+   int err;
+   struct pem_headers hdr = pem_openssh;
+   struct kdf_options opts = { 0 };
+   w = LTC_PEM_READ_BUFSIZE * 2;
+retry:
+   pem = XREALLOC(pem, w);
+   err = pem_read(pem, &w, &hdr, g);
+   if (err == CRYPT_BUFFER_OVERFLOW) {
+      goto retry;
+   } else if (err != CRYPT_OK) {
+      goto cleanup;
+   }
+   l = w;
+   if ((err = s_decode_header(pem, &w, &opts)) != CRYPT_OK) {
+      goto cleanup;
+   }
+   p = pem + w;
+   l -= w;
+   w = l;
+
+   privkey_len = l;
+   privkey = XMALLOC(privkey_len);
+
+   if ((err = ssh_decode_sequence_multi(p, &w,
+                                        LTC_SSHDATA_STRING, privkey, &privkey_len,
+                                        LTC_SSHDATA_EOL,    NULL)) != CRYPT_OK) {
+      goto cleanup;
+   }
+
+   if (XSTRCMP(opts.name, "none") != 0) {
+      /* hard-coded pass for demo keys */
+      if (!pw_ctx || !pw_ctx->callback) {
+         err = CRYPT_INVALID_ARG;
+         goto cleanup;
+      }
+      if (pw_ctx->callback(&opts.pw.pw, &opts.pw.l, pw_ctx->userdata)) {
+         err = CRYPT_ERROR;
+         goto cleanup;
+      }
+      w = privkey_len;
+      if ((err = s_decrypt_private_keys(privkey, &privkey_len, &opts)) != CRYPT_OK) {
+         goto cleanup;
+      }
+   }
+
+   w = privkey_len;
+   if ((err = s_decode_private_key(privkey, &w, k)) != CRYPT_OK) {
+      goto cleanup;
+   }
+
+cleanup:
+   if (opts.pw.pw) {
+      zeromem(opts.pw.pw, opts.pw.l);
+      XFREE(opts.pw.pw);
+   }
+   if (privkey) {
+      zeromem(privkey, privkey_len);
+      XFREE(privkey);
+   }
+   XFREE(pem);
+   return err;
+}
+
+int pem_decode_openssh_filehandle(FILE *f, ltc_pka_key *k, password_ctx *pw_ctx)
+{
+   struct get_char g = { .get = pem_get_char_from_file, .f = f };
+   return s_decode_openssh(&g, k, pw_ctx);
+}
+
+int pem_decode_openssh(void *buf, unsigned long len, ltc_pka_key *k, password_ctx *pw_ctx)
+{
+   struct get_char g = { .get = pem_get_char_from_buf, SET_BUFP(.buf, buf, len) };
+   return s_decode_openssh(&g, k, pw_ctx);
+}
+
+#endif /* defined(LTC_PEM_SSH) */