Browse Source

add support for regular PEM files

This adds support to decode plain and encrypted PEM files.

Either in OpenSSL specific format or PKCS#8

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

+ 8 - 0
src/headers/tomcrypt_custom.h

@@ -114,6 +114,8 @@
 
 
    #define LTC_NO_MISC
    #define LTC_NO_MISC
    #define LTC_BASE64
    #define LTC_BASE64
+   #define LTC_BASE16
+   #define LTC_PEM
 #endif /* LTC_EASY */
 #endif /* LTC_EASY */
 
 
 /* The minimal set of functionality to run the tests */
 /* The minimal set of functionality to run the tests */
@@ -576,6 +578,12 @@
 #endif
 #endif
 
 
 #if defined(LTC_PEM)
 #if defined(LTC_PEM)
+   /* Size of the line-buffer */
+   #ifndef LTC_PEM_DECODE_BUFSZ
+      #define LTC_PEM_DECODE_BUFSZ 72
+   #elif LTC_PEM_DECODE_BUFSZ < 72
+      #error "LTC_PEM_DECODE_BUFSZ shall not be < 72 bytes"
+   #endif
    /* Size of the decoded data buffer */
    /* Size of the decoded data buffer */
    #ifndef LTC_PEM_READ_BUFSIZE
    #ifndef LTC_PEM_READ_BUFSIZE
       #ifdef LTC_FILE_READ_BUFSIZE
       #ifdef LTC_FILE_READ_BUFSIZE

+ 3 - 1
src/headers/tomcrypt_misc.h

@@ -160,10 +160,12 @@ int padding_depad(const unsigned char *data, unsigned long *length, unsigned lon
 #endif  /* LTC_PADDING */
 #endif  /* LTC_PADDING */
 
 
 #ifdef LTC_PEM
 #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);
 
 
 #ifdef LTC_SSH
 #ifdef LTC_SSH
 int pem_decode_openssh_filehandle(FILE *f, ltc_pka_key *k, password_ctx *pw_ctx);
 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);
+int pem_decode_openssh(const void *buf, unsigned long len, ltc_pka_key *k, password_ctx *pw_ctx);
 #endif
 #endif
 
 
 #endif /* LTC_PEM */
 #endif /* LTC_PEM */

+ 26 - 5
src/headers/tomcrypt_private.h

@@ -259,6 +259,13 @@ struct password {
    void *pw;
    void *pw;
    unsigned long l;
    unsigned long l;
 };
 };
+struct dek_info {
+   const char *alg;
+   unsigned long keylen;
+   /* should use `MAXBLOCKSIZE` here, but all supported
+    * blockciphers require max 16 bytes IV */
+   char iv[16 * 2 + 1];
+};
 
 
 struct str {
 struct str {
    char *p;
    char *p;
@@ -267,6 +274,8 @@ struct str {
 
 
 #define SET_STR(n, s) n.p = s, n.len = XSTRLEN(s)
 #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 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)
 
 
 enum more_headers {
 enum more_headers {
    no,
    no,
@@ -274,11 +283,20 @@ enum more_headers {
    maybe
    maybe
 };
 };
 
 
+struct pem_header_id {
+   struct str start, end;
+   enum more_headers has_more_headers;
+   int encrypted;
+   enum ltc_pka_id pka;
+   int pkcs8;
+   int (*decrypt)(void *, unsigned long *, void *);
+};
+
 struct pem_headers {
 struct pem_headers {
-   const struct {
-      struct str start, end;
-      enum more_headers has_more_headers;
-   };
+   const struct pem_header_id *id;
+   int encrypted;
+   struct dek_info info;
+   struct password *pw;
 };
 };
 
 
 struct bufp {
 struct bufp {
@@ -288,7 +306,7 @@ struct bufp {
    char *p, *r, *end;
    char *p, *r, *end;
 };
 };
 
 
-#define SET_BUFP(n, d, l) n.p = d, n.r = d, n.end = (char*)d + l + 1
+#define SET_BUFP(n, d, l) n.p = (char*)d, n.r = (char*)d, n.end = (char*)d + l + 1
 
 
 struct get_char {
 struct get_char {
    int (*get)(struct get_char*);
    int (*get)(struct get_char*);
@@ -296,6 +314,8 @@ struct get_char {
       FILE *f;
       FILE *f;
       struct bufp buf;
       struct bufp buf;
    };
    };
+   struct str unget_buf;
+   char unget_buf_[LTC_PEM_DECODE_BUFSZ];
 };
 };
 
 
 /* others */
 /* others */
@@ -328,6 +348,7 @@ void rsa_shrink_key(rsa_key *key);
 int rsa_make_key_bn_e(prng_state *prng, int wprng, int size, void *e,
 int rsa_make_key_bn_e(prng_state *prng, int wprng, int size, void *e,
                       rsa_key *key); /* used by op-tee */
                       rsa_key *key); /* used by op-tee */
 int rsa_import_pkcs1(const unsigned char *in, unsigned long inlen, rsa_key *key);
 int rsa_import_pkcs1(const unsigned char *in, unsigned long inlen, rsa_key *key);
+int rsa_import_pkcs8_asn1(ltc_asn1_list *alg_id, ltc_asn1_list *priv_key, rsa_key *key);
 #endif /* LTC_MRSA */
 #endif /* LTC_MRSA */
 
 
 /* ---- DH Routines ---- */
 /* ---- DH Routines ---- */

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

@@ -472,6 +472,7 @@ const char *crypt_build_settings =
 #endif
 #endif
 #if defined(LTC_PEM)
 #if defined(LTC_PEM)
     " PEM "
     " PEM "
+    " " NAME_VALUE(LTC_PEM_DECODE_BUFSZ) " "
     " " NAME_VALUE(LTC_PEM_READ_BUFSIZE) " "
     " " NAME_VALUE(LTC_PEM_READ_BUFSIZE) " "
 #endif
 #endif
 #if defined(LTC_SSH)
 #if defined(LTC_SSH)

+ 287 - 4
src/misc/pem/pem_decode.c

@@ -9,8 +9,66 @@
 
 
 #ifdef LTC_PEM
 #ifdef LTC_PEM
 
 
+struct dek_info_from_str {
+   const struct str id;
+   struct dek_info info;
+};
+
 /* Encrypted PEM files */
 /* Encrypted PEM files */
-#define PEM_DECODE_BUFSZ 72
+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)
 int pem_get_char_from_file(struct get_char *g)
 {
 {
@@ -28,10 +86,27 @@ int pem_get_char_from_buf(struct get_char *g)
    return ret;
    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)
 static char* s_get_line(char *buf, unsigned long *buflen, struct get_char *g)
 {
 {
    unsigned long blen = 0;
    unsigned long blen = 0;
    int c = -1, c_;
    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) {
    while(blen < *buflen) {
       c_ = c;
       c_ = c;
       c = g->get(g);
       c = g->get(g);
@@ -66,12 +141,66 @@ static LTC_INLINE int s_fits_buf(void *dest, unsigned long to_write, void *end)
 
 
 static int s_pem_decode_headers(struct pem_headers *hdr, struct get_char *g)
 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;
    return CRYPT_OK;
 }
 }
 
 
 int pem_read(void *pem, unsigned long *w, struct pem_headers *hdr, struct get_char *g)
 int pem_read(void *pem, unsigned long *w, struct pem_headers *hdr, struct get_char *g)
 {
 {
-   char buf[PEM_DECODE_BUFSZ];
+   char buf[LTC_PEM_DECODE_BUFSZ];
    char *wpem = pem;
    char *wpem = pem;
    char *end = wpem + *w;
    char *end = wpem + *w;
    unsigned long slen, linelen;
    unsigned long slen, linelen;
@@ -82,17 +211,19 @@ int pem_read(void *pem, unsigned long *w, struct pem_headers *hdr, struct get_ch
    if (s_get_line(buf, &linelen, g) == NULL) {
    if (s_get_line(buf, &linelen, g) == NULL) {
       return CRYPT_INVALID_PACKET;
       return CRYPT_INVALID_PACKET;
    }
    }
-   if (hdr->start.len != linelen || XMEMCMP(buf, hdr->start.p, hdr->start.len)) {
+   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;
       return CRYPT_INVALID_PACKET;
    }
    }
 
 
+   hdr->encrypted = hdr->id->encrypted;
    if ((err = s_pem_decode_headers(hdr, g)) != CRYPT_OK)
    if ((err = s_pem_decode_headers(hdr, g)) != CRYPT_OK)
       return err;
       return err;
 
 
    /* Read the base64 encoded part of the PEM */
    /* Read the base64 encoded part of the PEM */
    slen = sizeof(buf);
    slen = sizeof(buf);
    while (s_get_line(buf, &slen, g)) {
    while (s_get_line(buf, &slen, g)) {
-      if (slen == hdr->end.len && !XMEMCMP(buf, hdr->end.p, slen)) {
+      if (slen == hdr->id->end.len && !XMEMCMP(buf, hdr->id->end.p, slen)) {
          hdr_ok = 1;
          hdr_ok = 1;
          break;
          break;
       }
       }
@@ -126,4 +257,156 @@ int pem_read(void *pem, unsigned long *w, struct pem_headers *hdr, struct get_ch
    return CRYPT_OK;
    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_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_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 */
 #endif /* LTC_PEM */

+ 4 - 4
src/misc/pem/pem_ssh.c

@@ -337,7 +337,7 @@ static int s_decode_header(unsigned char *in, unsigned long *inlen, struct kdf_o
    return err;
    return err;
 }
 }
 
 
-static const struct pem_headers pem_openssh =
+static const struct pem_header_id pem_openssh =
    {
    {
      SET_CSTR(.start, "-----BEGIN OPENSSH PRIVATE KEY-----"),
      SET_CSTR(.start, "-----BEGIN OPENSSH PRIVATE KEY-----"),
      SET_CSTR(.end, "-----END OPENSSH PRIVATE KEY-----"),
      SET_CSTR(.end, "-----END OPENSSH PRIVATE KEY-----"),
@@ -349,7 +349,7 @@ static int s_decode_openssh(struct get_char *g, ltc_pka_key *k, password_ctx *pw
    unsigned char *pem = NULL, *p, *privkey = NULL;
    unsigned char *pem = NULL, *p, *privkey = NULL;
    unsigned long w, l, privkey_len;
    unsigned long w, l, privkey_len;
    int err;
    int err;
-   struct pem_headers hdr = pem_openssh;
+   struct pem_headers hdr = { .id = &pem_openssh };
    struct kdf_options opts = { 0 };
    struct kdf_options opts = { 0 };
    w = LTC_PEM_READ_BUFSIZE * 2;
    w = LTC_PEM_READ_BUFSIZE * 2;
 retry:
 retry:
@@ -391,6 +391,7 @@ retry:
       if ((err = s_decrypt_private_keys(privkey, &privkey_len, &opts)) != CRYPT_OK) {
       if ((err = s_decrypt_private_keys(privkey, &privkey_len, &opts)) != CRYPT_OK) {
          goto cleanup;
          goto cleanup;
       }
       }
+      zeromem(opts.pw.pw, opts.pw.l);
    }
    }
 
 
    w = privkey_len;
    w = privkey_len;
@@ -400,7 +401,6 @@ retry:
 
 
 cleanup:
 cleanup:
    if (opts.pw.pw) {
    if (opts.pw.pw) {
-      zeromem(opts.pw.pw, opts.pw.l);
       XFREE(opts.pw.pw);
       XFREE(opts.pw.pw);
    }
    }
    if (privkey) {
    if (privkey) {
@@ -417,7 +417,7 @@ int pem_decode_openssh_filehandle(FILE *f, ltc_pka_key *k, password_ctx *pw_ctx)
    return s_decode_openssh(&g, k, pw_ctx);
    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)
+int pem_decode_openssh(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) };
    struct get_char g = { .get = pem_get_char_from_buf, SET_BUFP(.buf, buf, len) };
    return s_decode_openssh(&g, k, pw_ctx);
    return s_decode_openssh(&g, k, pw_ctx);