Browse Source

split-up into multiple C files

... and slightly optimize multiple things, e.g. `DEK-Info` decoding

Signed-off-by: Steffen Jaeckel <[email protected]>
Steffen Jaeckel 3 years ago
parent
commit
87a1758f2f

+ 2 - 2
src/headers/tomcrypt_misc.h

@@ -160,8 +160,8 @@ int padding_depad(const unsigned char *data, unsigned long *length, unsigned lon
 #endif  /* LTC_PADDING */
 
 #ifdef LTC_PEM
-int pem_decode_filehandle(FILE *f, ltc_pka_key *k, password_ctx *pw_ctx);
-int pem_decode(const void *buf, unsigned long len, ltc_pka_key *k, password_ctx *pw_ctx);
+int pem_decode_pkcs_filehandle(FILE *f, ltc_pka_key *k, password_ctx *pw_ctx);
+int pem_decode_pkcs(const void *buf, unsigned long len, ltc_pka_key *k, password_ctx *pw_ctx);
 
 #ifdef LTC_SSH
 int pem_decode_openssh_filehandle(FILE *f, ltc_pka_key *k, password_ctx *pw_ctx);

+ 17 - 3
src/headers/tomcrypt_private.h

@@ -252,6 +252,7 @@ int base64_encode_pem(const unsigned char *in,  unsigned long inlen,
 
 /* PEM related */
 
+#ifdef LTC_PEM
 struct password {
    /* usually a `char*` but could also contain binary data
     * so use a `void*` + length to be on the safe side.
@@ -275,7 +276,12 @@ struct str {
 #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)
 #define COPY_STR(n, s, l) do { XMEMCPY(n.p, s, l); n.len = l; } while(0)
-#define FREE_STR(n) do { n.p = NULL; n.len = 0; } while(0)
+#define RESET_STR(n) do { n.p = NULL; n.len = 0; } while(0)
+
+struct dek_info_from_str {
+   const struct str id;
+   struct dek_info info;
+};
 
 enum more_headers {
    no,
@@ -299,14 +305,21 @@ struct pem_headers {
    struct password *pw;
 };
 
+extern const struct pem_header_id pem_std_headers[];
+extern const unsigned long pem_std_headers_num;
+extern const struct str pem_proc_type_encrypted;
+extern const struct str pem_dek_info_start;
+extern const struct dek_info_from_str pem_dek_infos[];
+extern const unsigned long pem_dek_infos_num;
+
 struct bufp {
    /* `end` points to one byte after the last
     * element of the allocated buffer
     */
-   char *p, *r, *end;
+   char *start, *work, *end;
 };
 
-#define SET_BUFP(n, d, l) n.p = (char*)d, n.r = (char*)d, n.end = (char*)d + l + 1
+#define SET_BUFP(n, d, l) n.start = (char*)d, n.work = (char*)d, n.end = (char*)d + l + 1
 
 struct get_char {
    int (*get)(struct get_char*);
@@ -317,6 +330,7 @@ struct get_char {
    struct str unget_buf;
    char unget_buf_[LTC_PEM_DECODE_BUFSZ];
 };
+#endif
 
 /* others */
 

+ 67 - 0
src/misc/pem/pem.c

@@ -0,0 +1,67 @@
+/* LibTomCrypt, modular cryptographic library -- Tom St Denis */
+/* SPDX-License-Identifier: Unlicense */
+#include "tomcrypt_private.h"
+
+/**
+  @file pem.c
+  Const declarations for PEM, Steffen Jaeckel
+*/
+
+#ifdef LTC_PEM
+
+const struct pem_header_id pem_std_headers[] = {
+   {
+     /* PKCS#8 encrypted */
+     SET_CSTR(.start, "-----BEGIN ENCRYPTED PRIVATE KEY-----"),
+     SET_CSTR(.end, "-----END ENCRYPTED PRIVATE KEY-----"),
+     .has_more_headers = no,
+     .encrypted = 1,
+     .pkcs8 = 1,
+   },
+   {
+     /* PKCS#8 plain */
+     SET_CSTR(.start, "-----BEGIN PRIVATE KEY-----"),
+     SET_CSTR(.end, "-----END PRIVATE KEY-----"),
+     .has_more_headers = no,
+     .pkcs8 = 1,
+   },
+   /* Regular plain or encrypted private keys */
+   {
+     SET_CSTR(.start, "-----BEGIN RSA PRIVATE KEY-----"),
+     SET_CSTR(.end, "-----END RSA PRIVATE KEY-----"),
+     .has_more_headers = maybe,
+     .pka = LTC_PKA_RSA,
+   },
+   {
+     SET_CSTR(.start, "-----BEGIN EC PRIVATE KEY-----"),
+     SET_CSTR(.end, "-----END EC PRIVATE KEY-----"),
+     .has_more_headers = maybe,
+     .pka = LTC_PKA_EC,
+   },
+   {
+     SET_CSTR(.start, "-----BEGIN DSA PRIVATE KEY-----"),
+     SET_CSTR(.end, "-----END DSA PRIVATE KEY-----"),
+     .has_more_headers = maybe,
+     .pka = LTC_PKA_DSA,
+   },
+};
+const unsigned long pem_std_headers_num = sizeof(pem_std_headers)/sizeof(pem_std_headers[0]);
+
+
+/* Encrypted PEM files */
+const struct str pem_proc_type_encrypted = { SET_CSTR(, "Proc-Type: 4,ENCRYPTED") };
+const struct str pem_dek_info_start = { SET_CSTR(, "DEK-Info: ") };
+const struct dek_info_from_str pem_dek_infos[] =
+   {
+      { SET_CSTR(.id, "AES-128-CBC,"),      .info.alg = "aes",      .info.keylen = 128 / 8, },
+      { SET_CSTR(.id, "AES-192-CBC,"),      .info.alg = "aes",      .info.keylen = 192 / 8, },
+      { SET_CSTR(.id, "AES-256-CBC,"),      .info.alg = "aes",      .info.keylen = 256 / 8, },
+      { SET_CSTR(.id, "CAMELLIA-128-CBC,"), .info.alg = "camellia", .info.keylen = 128 / 8, },
+      { SET_CSTR(.id, "CAMELLIA-192-CBC,"), .info.alg = "camellia", .info.keylen = 192 / 8, },
+      { SET_CSTR(.id, "CAMELLIA-256-CBC,"), .info.alg = "camellia", .info.keylen = 256 / 8, },
+      { SET_CSTR(.id, "DES-EDE3-CBC,"),     .info.alg = "3des",     .info.keylen = 192 / 8, },
+      { SET_CSTR(.id, "DES-CBC,"),          .info.alg = "des",      .info.keylen = 64 / 8, },
+   };
+const unsigned long pem_dek_infos_num = sizeof(pem_dek_infos)/sizeof(pem_dek_infos[0]);
+
+#endif /* LTC_PEM */

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

@@ -1,420 +0,0 @@
-/* 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
-
-struct dek_info_from_str {
-   const struct str id;
-   struct dek_info info;
-};
-
-/* Encrypted PEM files */
-static const struct str proc_type_encrypted = { SET_CSTR(, "Proc-Type: 4,ENCRYPTED") };
-static const struct dek_info_from_str dek_infos[] =
-   {
-      { SET_CSTR(.id, "DEK-Info: AES-128-CBC,"),  .info.alg = "aes",  .info.keylen = 128 / 8, },
-      { SET_CSTR(.id, "DEK-Info: AES-192-CBC,"),  .info.alg = "aes",  .info.keylen = 192 / 8, },
-      { SET_CSTR(.id, "DEK-Info: AES-256-CBC,"),  .info.alg = "aes",  .info.keylen = 256 / 8, },
-      { SET_CSTR(.id, "DEK-Info: CAMELLIA-128-CBC,"),  .info.alg = "camellia",  .info.keylen = 128 / 8, },
-      { SET_CSTR(.id, "DEK-Info: CAMELLIA-192-CBC,"),  .info.alg = "camellia",  .info.keylen = 192 / 8, },
-      { SET_CSTR(.id, "DEK-Info: CAMELLIA-256-CBC,"),  .info.alg = "camellia",  .info.keylen = 256 / 8, },
-      { SET_CSTR(.id, "DEK-Info: DES-EDE3-CBC,"), .info.alg = "3des", .info.keylen = 192 / 8, },
-      { SET_CSTR(.id, "DEK-Info: DES-CBC,"),      .info.alg = "des",  .info.keylen = 64 / 8, },
-   };
-
-static int s_decrypt_pem(unsigned char *pem, unsigned long *l, const struct pem_headers *hdr)
-{
-   unsigned char iv[MAXBLOCKSIZE], key[MAXBLOCKSIZE];
-   unsigned long ivlen, klen;
-   int err;
-   symmetric_CBC cbc_ctx;
-
-   if (hdr->info.keylen > sizeof(key)) {
-      return CRYPT_BUFFER_OVERFLOW;
-   }
-   if (!hdr->pw->pw) {
-      return CRYPT_INVALID_ARG;
-   }
-
-   ivlen = sizeof(iv);
-   if ((err = base16_decode(hdr->info.iv, XSTRLEN(hdr->info.iv), iv, &ivlen)) != CRYPT_OK) {
-      return err;
-   }
-   klen = hdr->info.keylen;
-   if ((err = pkcs_5_alg1_openssl(hdr->pw->pw, hdr->pw->l, iv, 1, find_hash("md5"), key, &klen))) {
-      return err;
-   }
-
-   if ((err = cbc_start(find_cipher(hdr->info.alg), iv, key, klen, 0, &cbc_ctx)) != CRYPT_OK) {
-      goto error_out;
-   }
-   if ((err = cbc_decrypt(pem, pem, *l, &cbc_ctx)) != CRYPT_OK) {
-      goto error_out;
-   }
-   if ((err = cbc_done(&cbc_ctx)) != CRYPT_OK) {
-      goto error_out;
-   }
-   if ((err = padding_depad(pem, l, LTC_PAD_PKCS7 | cbc_ctx.blocklen)) != CRYPT_OK) {
-      goto error_out;
-   }
-
-error_out:
-   zeromem(key, sizeof(key));
-   zeromem(iv, sizeof(iv));
-   return err;
-}
-
-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 void s_unget_line(char *buf, unsigned long buflen, struct get_char *g)
-{
-   if (buflen > sizeof(g->unget_buf_))
-      return;
-   g->unget_buf.p = g->unget_buf_;
-   COPY_STR(g->unget_buf, buf, buflen);
-}
-
-static char* s_get_line(char *buf, unsigned long *buflen, struct get_char *g)
-{
-   unsigned long blen = 0;
-   int c = -1, c_;
-   if (g->unget_buf.p) {
-      if (*buflen < g->unget_buf.len) {
-         return NULL;
-      }
-      XMEMCPY(buf, g->unget_buf.p, g->unget_buf.len);
-      *buflen = g->unget_buf.len;
-      FREE_STR(g->unget_buf);
-      return buf;
-   }
-   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)
-{
-   char buf[LTC_PEM_DECODE_BUFSZ];
-   unsigned long slen, tmplen, n;
-   int has_more_headers = hdr->id->has_more_headers == no ? 0 : 3;
-
-   /* Make sure the PEM has the appropriate extension headers if required.
-    *
-    * ```
-    * Proc-Type: 4,ENCRYPTED[\r]\n
-    * DEK-Info: <algorithm>,<IV>[\r]\n
-    * [\r]\n
-    * ```
-    */
-   while (has_more_headers) {
-      slen = sizeof(buf);
-      if (!s_get_line(buf, &slen, g) || (has_more_headers > 1 && slen == 0)) {
-         return CRYPT_INVALID_PACKET;
-      }
-      switch (has_more_headers) {
-         case 3:
-            if (XMEMCMP(buf, proc_type_encrypted.p, proc_type_encrypted.len)) {
-               s_unget_line(buf, slen, g);
-               if (hdr->id->has_more_headers == maybe)
-                  return CRYPT_OK;
-               else
-                  return CRYPT_INVALID_PACKET;
-            }
-            hdr->encrypted = 1;
-            break;
-         case 2:
-            hdr->info.alg = NULL;
-            for (n = 0; n < sizeof(dek_infos)/sizeof(dek_infos[0]); ++n) {
-               if (slen >= dek_infos[n].id.len && !XMEMCMP(buf, dek_infos[n].id.p, dek_infos[n].id.len)) {
-                  hdr->info = dek_infos[n].info;
-                  tmplen = XSTRLEN(buf + dek_infos[n].id.len);
-                  if (tmplen > sizeof(hdr->info.iv))
-                     return CRYPT_INVALID_KEYSIZE;
-                  XMEMCPY(hdr->info.iv, buf + dek_infos[n].id.len, tmplen);
-                  break;
-               }
-            }
-            if (hdr->info.alg == NULL) {
-               return CRYPT_INVALID_CIPHER;
-            }
-            break;
-         case 1:
-            /* Make sure that there's an empty line in between */
-            if (buf[0] != '\0')
-               return CRYPT_INVALID_PACKET;
-            break;
-         default:
-            return CRYPT_INVALID_CIPHER;
-      }
-      has_more_headers--;
-   }
-   return CRYPT_OK;
-}
-
-int pem_read(void *pem, unsigned long *w, struct pem_headers *hdr, struct get_char *g)
-{
-   char buf[LTC_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->id->start.len != linelen || XMEMCMP(buf, hdr->id->start.p, hdr->id->start.len)) {
-      s_unget_line(buf, linelen, g);
-      return CRYPT_INVALID_PACKET;
-   }
-
-   hdr->encrypted = hdr->id->encrypted;
-   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->id->end.len && !XMEMCMP(buf, hdr->id->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;
-}
-
-static const struct pem_header_id pem_std_headers[] = {
-   {
-     /* PKCS#8 encrypted */
-     SET_CSTR(.start, "-----BEGIN ENCRYPTED PRIVATE KEY-----"),
-     SET_CSTR(.end, "-----END ENCRYPTED PRIVATE KEY-----"),
-     .has_more_headers = no,
-     .encrypted = 1,
-     .pkcs8 = 1,
-   },
-   {
-     /* PKCS#8 plain */
-     SET_CSTR(.start, "-----BEGIN PRIVATE KEY-----"),
-     SET_CSTR(.end, "-----END PRIVATE KEY-----"),
-     .has_more_headers = no,
-     .pkcs8 = 1,
-   },
-   /* Regular plain or encrypted private keys */
-   {
-     SET_CSTR(.start, "-----BEGIN RSA PRIVATE KEY-----"),
-     SET_CSTR(.end, "-----END RSA PRIVATE KEY-----"),
-     .has_more_headers = maybe,
-     .pka = LTC_PKA_RSA,
-   },
-   {
-     SET_CSTR(.start, "-----BEGIN EC PRIVATE KEY-----"),
-     SET_CSTR(.end, "-----END EC PRIVATE KEY-----"),
-     .has_more_headers = maybe,
-     .pka = LTC_PKA_EC,
-   },
-   {
-     SET_CSTR(.start, "-----BEGIN DSA PRIVATE KEY-----"),
-     SET_CSTR(.end, "-----END DSA PRIVATE KEY-----"),
-     .has_more_headers = maybe,
-     .pka = LTC_PKA_DSA,
-   },
-};
-typedef int (*pkcs8_import)(const unsigned char *in, unsigned long inlen,
-                                   password_ctx *pw_ctx,
-                                           void *key);
-typedef struct {
-   enum ltc_oid_id id;
-   pkcs8_import fn;
-} p8_import_st;
-
-static int s_decode(struct get_char *g, ltc_pka_key *k, password_ctx *pw_ctx)
-{
-   unsigned char *pem = NULL;
-   unsigned long w, l, n;
-   int err = CRYPT_ERROR;
-   struct pem_headers hdr = { 0 };
-   struct password pw;
-   ltc_asn1_list *p8_asn1 = NULL;
-   w = LTC_PEM_READ_BUFSIZE * 2;
-retry:
-   pem = XREALLOC(pem, w);
-   for (n = 0; n < sizeof(pem_std_headers)/sizeof(pem_std_headers[0]); ++n) {
-      hdr.id = &pem_std_headers[n];
-      err = pem_read(pem, &w, &hdr, g);
-      if (err == CRYPT_BUFFER_OVERFLOW) {
-         goto retry;
-      } else if (err == CRYPT_OK) {
-         break;
-      }
-      hdr.id = NULL;
-   }
-   /* id not found */
-   if (hdr.id == NULL)
-      goto cleanup;
-   l = w;
-   if (hdr.id->pkcs8) {
-      enum ltc_oid_id pka;
-      ltc_asn1_list *alg_id, *priv_key;
-      if ((err = pkcs8_decode_flexi(pem, l, pw_ctx, &p8_asn1)) != CRYPT_OK) {
-         goto cleanup;
-      }
-      if ((err = pkcs8_get_children(p8_asn1, &pka, &alg_id, &priv_key)) != CRYPT_OK) {
-         goto cleanup;
-      }
-      switch (pka) {
-         case LTC_OID_DSA:
-            err = dsa_import_pkcs8_asn1(alg_id, priv_key, &k->u.dsa);
-            k->id = LTC_PKA_DSA;
-            break;
-         case LTC_OID_RSA:
-            err = rsa_import_pkcs8_asn1(alg_id, priv_key, &k->u.rsa);
-            k->id = LTC_PKA_RSA;
-            break;
-         case LTC_OID_EC:
-            err = ecc_import_pkcs8_asn1(alg_id, priv_key, &k->u.ecc);
-            k->id = LTC_PKA_EC;
-            break;
-         case LTC_OID_ED25519:
-            err = ed25519_import_pkcs8_asn1(alg_id, priv_key, &k->u.curve25519);
-            k->id = LTC_PKA_CURVE25519;
-            break;
-         case LTC_OID_X25519:
-            err = x25519_import_pkcs8_asn1(alg_id, priv_key, &k->u.curve25519);
-            k->id = LTC_PKA_CURVE25519;
-            break;
-         default:
-            err = CRYPT_PK_INVALID_TYPE;
-      }
-      goto cleanup;
-   } else if (hdr.encrypted) {
-      LTC_ARGCHK(pw_ctx           != NULL);
-      LTC_ARGCHK(pw_ctx->callback != NULL);
-
-      hdr.pw = &pw;
-      if (pw_ctx->callback(&hdr.pw->pw, &hdr.pw->l, pw_ctx->userdata)) {
-         err = CRYPT_ERROR;
-         goto cleanup;
-      }
-
-      if ((err = s_decrypt_pem(pem, &l, &hdr)) != CRYPT_OK) {
-         goto cleanup;
-      }
-   }
-   switch (hdr.id->pka) {
-      case LTC_OID_DSA:
-         err = dsa_import(pem, l, &k->u.dsa);
-         k->id = LTC_PKA_DSA;
-         break;
-      case LTC_OID_RSA:
-         err = rsa_import(pem, l, &k->u.rsa);
-         k->id = LTC_PKA_RSA;
-         break;
-      case LTC_OID_EC:
-         err = ecc_import_openssl(pem, l, &k->u.ecc);
-         k->id = LTC_PKA_EC;
-         break;
-      default:
-         err = CRYPT_PK_INVALID_TYPE;
-         goto cleanup;
-   }
-
-cleanup:
-   if (p8_asn1) {
-      der_sequence_free(p8_asn1);
-   }
-   if (hdr.pw) {
-      zeromem(hdr.pw->pw, hdr.pw->l);
-      XFREE(hdr.pw->pw);
-   }
-   XFREE(pem);
-   return err;
-}
-
-int pem_decode_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(&g, k, pw_ctx);
-}
-
-int pem_decode(const 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(&g, k, pw_ctx);
-
-}
-
-#endif /* LTC_PEM */

+ 198 - 0
src/misc/pem/pem_pkcs.c

@@ -0,0 +1,198 @@
+/* LibTomCrypt, modular cryptographic library -- Tom St Denis */
+/* SPDX-License-Identifier: Unlicense */
+#include "tomcrypt_private.h"
+
+/**
+  @file pem_decode.c
+  Decode a PEM file, Steffen Jaeckel
+*/
+
+#ifdef LTC_PEM
+
+static int s_decrypt_pem(unsigned char *pem, unsigned long *l, const struct pem_headers *hdr)
+{
+   unsigned char iv[MAXBLOCKSIZE], key[MAXBLOCKSIZE];
+   unsigned long ivlen, klen;
+   int err;
+   symmetric_CBC cbc_ctx;
+
+   if (hdr->info.keylen > sizeof(key)) {
+      return CRYPT_BUFFER_OVERFLOW;
+   }
+   if (!hdr->pw->pw) {
+      return CRYPT_INVALID_ARG;
+   }
+
+   ivlen = sizeof(iv);
+   if ((err = base16_decode(hdr->info.iv, XSTRLEN(hdr->info.iv), iv, &ivlen)) != CRYPT_OK) {
+      return err;
+   }
+   klen = hdr->info.keylen;
+   if ((err = pkcs_5_alg1_openssl(hdr->pw->pw, hdr->pw->l, iv, 1, find_hash("md5"), key, &klen))) {
+      return err;
+   }
+
+   if ((err = cbc_start(find_cipher(hdr->info.alg), iv, key, klen, 0, &cbc_ctx)) != CRYPT_OK) {
+      goto error_out;
+   }
+   if ((err = cbc_decrypt(pem, pem, *l, &cbc_ctx)) != CRYPT_OK) {
+      goto error_out;
+   }
+   if ((err = cbc_done(&cbc_ctx)) != CRYPT_OK) {
+      goto error_out;
+   }
+   if ((err = padding_depad(pem, l, LTC_PAD_PKCS7 | cbc_ctx.blocklen)) != CRYPT_OK) {
+      goto error_out;
+   }
+
+error_out:
+   zeromem(key, sizeof(key));
+   zeromem(iv, sizeof(iv));
+   return err;
+}
+typedef int (*pkcs8_import)(const unsigned char *in, unsigned long inlen,
+                                   password_ctx *pw_ctx,
+                                           void *key);
+typedef struct {
+   enum ltc_oid_id id;
+   pkcs8_import fn;
+} p8_import_st;
+
+static int s_decode(struct get_char *g, ltc_pka_key *k, password_ctx *pw_ctx)
+{
+   unsigned char *pem = NULL;
+   unsigned long w, l, n;
+   int err = CRYPT_ERROR;
+   struct pem_headers hdr = { 0 };
+   struct password pw;
+   ltc_asn1_list *p8_asn1 = NULL;
+   w = LTC_PEM_READ_BUFSIZE * 2;
+retry:
+   pem = XREALLOC(pem, w);
+   for (n = 0; n < pem_std_headers_num; ++n) {
+      hdr.id = &pem_std_headers[n];
+      err = pem_read(pem, &w, &hdr, g);
+      if (err == CRYPT_BUFFER_OVERFLOW) {
+         goto retry;
+      } else if (err == CRYPT_OK) {
+         break;
+      }
+      hdr.id = NULL;
+   }
+   /* id not found */
+   if (hdr.id == NULL)
+      goto cleanup;
+   l = w;
+   if (hdr.id->pkcs8) {
+      enum ltc_oid_id pka;
+      ltc_asn1_list *alg_id, *priv_key;
+      if ((err = pkcs8_decode_flexi(pem, l, pw_ctx, &p8_asn1)) != CRYPT_OK) {
+         goto cleanup;
+      }
+      if ((err = pkcs8_get_children(p8_asn1, &pka, &alg_id, &priv_key)) != CRYPT_OK) {
+         goto cleanup;
+      }
+      switch (pka) {
+#ifdef LTC_MDSA
+         case LTC_OID_DSA:
+            err = dsa_import_pkcs8_asn1(alg_id, priv_key, &k->u.dsa);
+            k->id = LTC_PKA_DSA;
+            break;
+#endif
+#ifdef LTC_MRSA
+         case LTC_OID_RSA:
+            err = rsa_import_pkcs8_asn1(alg_id, priv_key, &k->u.rsa);
+            k->id = LTC_PKA_RSA;
+            break;
+#endif
+#ifdef LTC_MECC
+         case LTC_OID_EC:
+            err = ecc_import_pkcs8_asn1(alg_id, priv_key, &k->u.ecc);
+            k->id = LTC_PKA_EC;
+            break;
+#endif
+#ifdef LTC_CURVE25519
+         case LTC_OID_ED25519:
+            err = ed25519_import_pkcs8_asn1(alg_id, priv_key, &k->u.curve25519);
+            k->id = LTC_PKA_CURVE25519;
+            break;
+         case LTC_OID_X25519:
+            err = x25519_import_pkcs8_asn1(alg_id, priv_key, &k->u.curve25519);
+            k->id = LTC_PKA_CURVE25519;
+            break;
+#endif
+         default:
+            err = CRYPT_PK_INVALID_TYPE;
+      }
+      goto cleanup;
+   } else if (hdr.encrypted) {
+      LTC_ARGCHK(pw_ctx           != NULL);
+      LTC_ARGCHK(pw_ctx->callback != NULL);
+
+      hdr.pw = &pw;
+      if (pw_ctx->callback(&hdr.pw->pw, &hdr.pw->l, pw_ctx->userdata)) {
+         err = CRYPT_ERROR;
+         goto cleanup;
+      }
+
+      if ((err = s_decrypt_pem(pem, &l, &hdr)) != CRYPT_OK) {
+         goto cleanup;
+      }
+   }
+   switch (hdr.id->pka) {
+#ifdef LTC_MDSA
+      case LTC_OID_DSA:
+         err = dsa_import(pem, l, &k->u.dsa);
+         k->id = LTC_PKA_DSA;
+         break;
+#endif
+#ifdef LTC_MRSA
+      case LTC_OID_RSA:
+         err = rsa_import(pem, l, &k->u.rsa);
+         k->id = LTC_PKA_RSA;
+         break;
+#endif
+#ifdef LTC_MECC
+      case LTC_OID_EC:
+         err = ecc_import_openssl(pem, l, &k->u.ecc);
+         k->id = LTC_PKA_EC;
+         break;
+#endif
+      default:
+         err = CRYPT_PK_INVALID_TYPE;
+         goto cleanup;
+   }
+
+cleanup:
+   if (p8_asn1) {
+      der_sequence_free(p8_asn1);
+   }
+   if (hdr.pw) {
+      zeromem(hdr.pw->pw, hdr.pw->l);
+      XFREE(hdr.pw->pw);
+   }
+   XFREE(pem);
+   return err;
+}
+
+int pem_decode_pkcs_filehandle(FILE *f, ltc_pka_key *k, password_ctx *pw_ctx)
+{
+   LTC_ARGCHK(f != NULL);
+   LTC_ARGCHK(k != NULL);
+   {
+      struct get_char g = { .get = pem_get_char_from_file, .f = f };
+      return s_decode(&g, k, pw_ctx);
+   }
+}
+
+int pem_decode_pkcs(const void *buf, unsigned long len, ltc_pka_key *k, password_ctx *pw_ctx)
+{
+   LTC_ARGCHK(buf != NULL);
+   LTC_ARGCHK(k != NULL);
+   {
+      struct get_char g = { .get = pem_get_char_from_buf, SET_BUFP(.buf, buf, len) };
+      return s_decode(&g, k, pw_ctx);
+   }
+}
+
+#endif /* LTC_PEM */

+ 202 - 0
src/misc/pem/pem_read.c

@@ -0,0 +1,202 @@
+/* LibTomCrypt, modular cryptographic library -- Tom St Denis */
+/* SPDX-License-Identifier: Unlicense */
+#include "tomcrypt_private.h"
+
+/**
+  @file pem_read.c
+  Read and interpret a PEM file, Steffen Jaeckel
+*/
+
+#ifdef LTC_PEM
+
+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.work == g->buf.end) {
+      return -1;
+   }
+   ret = *g->buf.work;
+   g->buf.work++;
+   return ret;
+}
+
+static void s_unget_line(char *buf, unsigned long buflen, struct get_char *g)
+{
+   if (buflen > sizeof(g->unget_buf_))
+      return;
+   g->unget_buf.p = g->unget_buf_;
+   COPY_STR(g->unget_buf, buf, buflen);
+}
+
+static char* s_get_line(char *buf, unsigned long *buflen, struct get_char *g)
+{
+   unsigned long blen = 0;
+   int c = -1, c_;
+   if (g->unget_buf.p) {
+      if (*buflen < g->unget_buf.len) {
+         return NULL;
+      }
+      XMEMCPY(buf, g->unget_buf.p, g->unget_buf.len);
+      *buflen = g->unget_buf.len;
+      RESET_STR(g->unget_buf);
+      return buf;
+   }
+   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)
+{
+   char buf[LTC_PEM_DECODE_BUFSZ], *alg_start;
+   unsigned long slen, tmplen, n;
+   int has_more_headers = hdr->id->has_more_headers == no ? 0 : 3;
+
+   /* Make sure the PEM has the appropriate extension headers if required.
+    *
+    * ```
+    * Proc-Type: 4,ENCRYPTED[\r]\n
+    * DEK-Info: <algorithm>,<IV>[\r]\n
+    * [\r]\n
+    * ```
+    */
+   while (has_more_headers) {
+      slen = sizeof(buf);
+      if (!s_get_line(buf, &slen, g) || (has_more_headers > 1 && slen == 0)) {
+         return CRYPT_INVALID_PACKET;
+      }
+      switch (has_more_headers) {
+         case 3:
+            if (XMEMCMP(buf, pem_proc_type_encrypted.p, pem_proc_type_encrypted.len)) {
+               s_unget_line(buf, slen, g);
+               if (hdr->id->has_more_headers == maybe)
+                  return CRYPT_OK;
+               else
+                  return CRYPT_INVALID_PACKET;
+            }
+            hdr->encrypted = 1;
+            break;
+         case 2:
+            hdr->info.alg = NULL;
+            if (XMEMCMP(buf, pem_dek_info_start.p, pem_dek_info_start.len))
+               return CRYPT_INVALID_PACKET;
+            alg_start = &buf[pem_dek_info_start.len];
+            for (n = 0; n < pem_dek_infos_num; ++n) {
+               if (slen >= pem_dek_infos[n].id.len + pem_dek_info_start.len && !XMEMCMP(alg_start, pem_dek_infos[n].id.p, pem_dek_infos[n].id.len)) {
+                  hdr->info = pem_dek_infos[n].info;
+                  tmplen = XSTRLEN(alg_start + pem_dek_infos[n].id.len);
+                  if (tmplen > sizeof(hdr->info.iv))
+                     return CRYPT_INVALID_KEYSIZE;
+                  XMEMCPY(hdr->info.iv, alg_start + pem_dek_infos[n].id.len, tmplen);
+                  break;
+               }
+            }
+            if (hdr->info.alg == NULL) {
+               return CRYPT_INVALID_CIPHER;
+            }
+            break;
+         case 1:
+            /* Make sure that there's an empty line in between */
+            if (buf[0] != '\0')
+               return CRYPT_INVALID_PACKET;
+            break;
+         default:
+            return CRYPT_INVALID_CIPHER;
+      }
+      has_more_headers--;
+   }
+   return CRYPT_OK;
+}
+
+int pem_read(void *pem, unsigned long *w, struct pem_headers *hdr, struct get_char *g)
+{
+   char buf[LTC_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->id->start.len != linelen || XMEMCMP(buf, hdr->id->start.p, hdr->id->start.len)) {
+      s_unget_line(buf, linelen, g);
+      return CRYPT_INVALID_PACKET;
+   }
+
+   hdr->encrypted = hdr->id->encrypted;
+   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->id->end.len && !XMEMCMP(buf, hdr->id->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 */

+ 2 - 2
tests/pem_test.c

@@ -77,7 +77,7 @@ static int s_pem_decode(const void *in, unsigned long inlen, void *key)
    password_ctx pw_ctx;
    int err;
    pw_ctx.callback = password_get;
-   if ((err = pem_decode(in, inlen, key, &pw_ctx)) != CRYPT_OK) {
+   if ((err = pem_decode_pkcs(in, inlen, key, &pw_ctx)) != CRYPT_OK) {
       return err;
    }
    return s_key_cmp(key);
@@ -88,7 +88,7 @@ static int s_pem_decode_f(FILE *f, void *key)
    password_ctx pw_ctx;
    int err;
    pw_ctx.callback = password_get;
-   if ((err = pem_decode_filehandle(f, key, &pw_ctx)) != CRYPT_OK) {
+   if ((err = pem_decode_pkcs_filehandle(f, key, &pw_ctx)) != CRYPT_OK) {
       return err;
    }
    return s_key_cmp(key);