Browse Source

Add support for reading `authorized_keys` files

This also changes the requirements when calling `ecc_find_curve()` that
the `cu` argument can be NULL.

Signed-off-by: Steffen Jaeckel <[email protected]>
Steffen Jaeckel 1 year ago
parent
commit
478f43fc56

+ 33 - 0
doc/crypt.tex

@@ -7504,6 +7504,39 @@ with \texttt{size*} being of type \texttt{unsigned long*}.
 
 
 
+\subsection{OpenSSH authorized\_keys files}
+
+\index{authorized\_keys}
+\index{ssh\_read\_authorized\_keys\_filehandle}
+\index{ssh\_read\_authorized\_keys}
+OpenSSH uses a simple storage format for public keys, which stores a public key per line in a regular text file.
+To process such a file the following API can be used.
+
+\begin{verbatim}
+int ssh_read_authorized_keys_filehandle(FILE *f,
+                                        ssh_authorized_key_cb cb, void *ctx);
+int ssh_read_authorized_keys(const void *buf, unsigned long len,
+                             ssh_authorized_key_cb cb, void *ctx);
+\end{verbatim}
+
+\index{ssh\_authorized\_key\_cb}
+For each key found in the file the callback as described below will be called.
+
+\begin{verbatim}
+/**
+   Callback function for each key in an `authorized_keys` file.
+
+   This function takes ownership of the `k` parameter passed.
+   `k` must be free'd by calling `pka_key_destroy(&k)`.
+
+   @param k        Pointer to the PKA key.
+   @param comment  Pointer to a string with the comment.
+   @param ctx      The `ctx` pointer as passed to the read function.
+*/
+typedef int (*ssh_authorized_key_cb)(ltc_pka_key *k, const char *comment, void *ctx);
+\end{verbatim}
+
+
 
 \mysection{PEM Files}
 \index{PEM}

+ 25 - 5
src/headers/tomcrypt_misc.h

@@ -160,16 +160,36 @@ 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, const password_ctx *pw_ctx);
+/* Buffer-based API */
 int pem_decode(const void *buf, unsigned long len, ltc_pka_key *k, const password_ctx *pw_ctx);
-
-int pem_decode_pkcs_filehandle(FILE *f, ltc_pka_key *k, const password_ctx *pw_ctx);
 int pem_decode_pkcs(const void *buf, unsigned long len, ltc_pka_key *k, const password_ctx *pw_ctx);
 
 #ifdef LTC_SSH
-int pem_decode_openssh_filehandle(FILE *f, ltc_pka_key *k, const password_ctx *pw_ctx);
+/**
+   Callback function for each key in an `authorized_keys` file.
+
+   This function takes ownership of the `k` parameter passed.
+   `k` must be free'd by calling `pka_key_destroy(&k)`.
+
+   @param k        Pointer to the PKA key.
+   @param comment  Pointer to a string with the comment.
+   @param ctx      The `ctx` pointer as passed to the read function.
+*/
+typedef int (*ssh_authorized_key_cb)(ltc_pka_key *k, const char *comment, void *ctx);
+
 int pem_decode_openssh(const void *buf, unsigned long len, ltc_pka_key *k, const password_ctx *pw_ctx);
-#endif
+int ssh_read_authorized_keys(const void *buf, unsigned long len, ssh_authorized_key_cb cb, void *ctx);
+#endif /* LTC_SSH */
+
+/* FILE*-based API */
+#ifndef LTC_NO_FILE
+int pem_decode_filehandle(FILE *f, ltc_pka_key *k, const password_ctx *pw_ctx);
+int pem_decode_pkcs_filehandle(FILE *f, ltc_pka_key *k, const password_ctx *pw_ctx);
+#ifdef LTC_SSH
+int pem_decode_openssh_filehandle(FILE *f, ltc_pka_key *k, const password_ctx *pw_ctx);
+int ssh_read_authorized_keys_filehandle(FILE *f, ssh_authorized_key_cb cb, void *ctx);
+#endif /* LTC_SSH */
+#endif /* LTC_NO_FILE */
 
 #endif /* LTC_PEM */
 

+ 1 - 0
src/headers/tomcrypt_pk.h

@@ -554,6 +554,7 @@ typedef struct {
 } ltc_pka_key;
 
 void pka_key_free(ltc_pka_key *key);
+void pka_key_destroy(ltc_pka_key **key);
 
 #ifdef LTC_DER
 /* DER handling */

+ 227 - 16
src/misc/pem/pem_ssh.c

@@ -59,14 +59,21 @@ struct kdf_options {
 };
 
 #ifdef LTC_MECC
-static int s_ssh_find_init_ecc(const char *pka, ltc_pka_key *key)
+static int s_ssh_find_ecc(const char *pka, const ltc_ecc_curve **curve)
 {
    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;
+   if ((err = ecc_find_curve(pka + prefixlen, curve)) != CRYPT_OK) return err;
+   return CRYPT_OK;
+}
+
+static int s_ssh_find_init_ecc(const char *pka, ltc_pka_key *key)
+{
+   int err;
+   const ltc_ecc_curve *cu;
+   if ((err = s_ssh_find_ecc(pka, &cu)) != CRYPT_OK) return err;
    return ecc_set_curve(cu, &key->u.ecc);
 }
 
@@ -310,32 +317,50 @@ cleanup:
 #endif
 
 struct ssh_pka {
-   const char *name;
+   struct str name;
+   enum ltc_pka_id id;
+   int (*find)(const char*, const ltc_ecc_curve **);
    int (*init)(const char*, ltc_pka_key*);
    int (*decode)(const unsigned char*, unsigned long*, ltc_pka_key*, enum pem_flags);
 };
 
 struct ssh_pka ssh_pkas[] = {
 #ifdef LTC_CURVE25519
-                             { "ssh-ed25519", NULL,                s_ssh_decode_ed25519 },
+                             { SET_CSTR(.name, "ssh-ed25519"),
+                               LTC_PKA_ED25519,
+                               NULL,
+                               NULL,
+                               s_ssh_decode_ed25519 },
 #endif
 #ifdef LTC_MRSA
-                             { "ssh-rsa",     NULL,                s_ssh_decode_rsa },
+                             { SET_CSTR(.name, "ssh-rsa"),
+                               LTC_PKA_RSA,
+                               NULL,
+                               NULL,
+                               s_ssh_decode_rsa },
 #endif
 #ifdef LTC_MDSA
-                             { "ssh-dss",     NULL,                s_ssh_decode_dsa },
+                             { SET_CSTR(.name, "ssh-dss"),
+                               LTC_PKA_DSA,
+                               NULL,
+                               NULL,
+                               s_ssh_decode_dsa },
 #endif
 #ifdef LTC_MECC
-                             { NULL,          s_ssh_find_init_ecc, s_ssh_decode_ecdsa },
+                             { { NULL, 0 },
+                               LTC_PKA_EC,
+                               s_ssh_find_ecc,
+                               s_ssh_find_init_ecc,
+                               s_ssh_decode_ecdsa },
 #endif
 };
 
-static int s_decode_key(const unsigned char *in, unsigned long *inlen, ltc_pka_key *key, enum pem_flags type)
+static int s_decode_key(const unsigned char *in, unsigned long *inlen, ltc_pka_key *key, char **comment, enum pem_flags type)
 {
    int err;
    ulong32 check1, check2;
-   unsigned char pka[64], comment[256];
-   unsigned long pkalen = sizeof(pka), commentlen = sizeof(comment);
+   unsigned char pka[64];
+   unsigned long pkalen = sizeof(pka);
    unsigned long remaining, cur_len;
    const unsigned char *p;
    unsigned long n;
@@ -374,8 +399,9 @@ static int s_decode_key(const unsigned char *in, unsigned long *inlen, ltc_pka_k
    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;
+      if (ssh_pkas[n].name.p != NULL) {
+         if (pkalen != ssh_pkas[n].name.len
+               || XMEMCMP(pka, ssh_pkas[n].name.p, ssh_pkas[n].name.len) != 0) continue;
       } else {
          if ((ssh_pkas[n].init == NULL) ||
                (ssh_pkas[n].init((char*)pka, key) != CRYPT_OK)) continue;
@@ -393,12 +419,22 @@ static int s_decode_key(const unsigned char *in, unsigned long *inlen, ltc_pka_k
    remaining -= cur_len;
    cur_len = remaining;
 
-   if (cur_len != 0) {
+   if (cur_len != 0 && comment) {
+      unsigned long commentlen = cur_len;
+      char *c = XMALLOC(commentlen);
+      if (c == NULL) {
+         return CRYPT_MEM;
+      }
       if ((err = ssh_decode_sequence_multi(p, &cur_len,
-                                           LTC_SSHDATA_STRING, comment, &commentlen,
+                                           LTC_SSHDATA_STRING, c, &commentlen,
                                            LTC_SSHDATA_EOL,    NULL)) != CRYPT_OK) {
          return err;
       }
+      if (commentlen == 0) {
+         XFREE(c);
+      } else {
+         *comment = c;
+      }
    }
 
    p += cur_len;
@@ -407,6 +443,144 @@ static int s_decode_key(const unsigned char *in, unsigned long *inlen, ltc_pka_k
    return remaining ? padding_depad(p, &remaining, LTC_PAD_SSH) : CRYPT_OK;
 }
 
+static LTC_INLINE void skip_spaces(char **r, unsigned long *l)
+{
+   while(*l && (**r == ' ' || **r == '\t')) {
+      (*r)++;
+      (*l)--;
+   }
+}
+
+static LTC_INLINE void skip_chars(char **r, unsigned long *l)
+{
+   while(*l && (**r != ' ' && **r != '\t')) {
+      (*l)--;
+      if (**r == '\n' || **r == '\r') {
+         *l = 0;
+      } else {
+         (*r)++;
+      }
+   }
+}
+
+static LTC_INLINE void skip_to_eol(char **r, unsigned long *l)
+{
+   while(*l && (**r != '\n' && **r != '\r')) {
+      (*l)--;
+      (*r)++;
+   }
+}
+
+static int s_parse_line(char *line, unsigned long *len, ltc_pka_key *key, char **comment)
+{
+   int err;
+   unsigned long n, rlen, olen;
+   enum authorized_keys_elements {
+      ake_algo_name = 0,
+      ake_b64_encoded_key = 1,
+      ake_comment = 2
+   };
+   struct str elements[3] = { 0 };
+   char *r = line;
+   unsigned char *buf = NULL;
+
+   rlen = *len;
+   /* Chop up string into the three authorized_keys_elements */
+   for (n = 0; n < sizeof(elements)/sizeof(elements[0]) && rlen; ++n) {
+      skip_spaces(&r, &rlen);
+      elements[n].p = r;
+      if (n != 2)
+         skip_chars(&r, &rlen);
+      else
+         skip_to_eol(&r, &rlen);
+      elements[n].len = r - elements[n].p;
+      *r = '\0';
+      r++;
+   }
+
+   for (n = 0; n < sizeof(ssh_pkas)/sizeof(ssh_pkas[0]); ++n) {
+      if (ssh_pkas[n].name.p != NULL) {
+         if (elements[ake_algo_name].len != ssh_pkas[n].name.len
+               || XMEMCMP(elements[ake_algo_name].p, ssh_pkas[n].name.p, ssh_pkas[n].name.len) != 0) continue;
+      } else {
+         if ((ssh_pkas[n].find == NULL) ||
+               (ssh_pkas[n].find(elements[ake_algo_name].p, NULL) != CRYPT_OK)) continue;
+      }
+      olen = elements[ake_b64_encoded_key].len;
+      buf = XMALLOC(olen);
+      if (buf == NULL) {
+         return CRYPT_MEM;
+      }
+      if ((err = base64_strict_decode(elements[ake_b64_encoded_key].p, elements[ake_b64_encoded_key].len, buf, &olen)) == CRYPT_OK) {
+         err = s_decode_key(buf, &olen, key, comment, pf_public);
+         if (err == CRYPT_OK && key->id != ssh_pkas[n].id) {
+            err = CRYPT_PK_INVALID_TYPE;
+         }
+      }
+      XFREE(buf);
+
+      if (err == CRYPT_OK) {
+         /* Only use the comment that was maybe in the text we just processed, in case when
+          * there was no comment inside the SSH key.
+          */
+         if (*comment == NULL && elements[ake_comment].p) {
+            *comment = XMALLOC(elements[ake_comment].len + 1);
+            if (*comment == NULL) {
+               return CRYPT_MEM;
+            }
+            XMEMCPY(*comment, elements[ake_comment].p, elements[ake_comment].len);
+            (*comment)[elements[ake_comment].len] = '\0';
+         }
+         *len = r - line;
+         return CRYPT_OK;
+      }
+   }
+   return CRYPT_PK_INVALID_TYPE;
+}
+
+static int s_read_authorized_keys(const void *buf, unsigned long len, ssh_authorized_key_cb cb, void *ctx)
+{
+   char *s;
+   int err;
+   unsigned long clen = len;
+   ltc_pka_key *key = XCALLOC(1, sizeof(*key));
+   char *comment = NULL;
+   void *cpy = XMALLOC(len);
+   if (key == NULL || cpy == NULL) {
+      if (cpy)
+         XFREE(cpy);
+      if (key)
+         XFREE(key);
+      return CRYPT_MEM;
+   }
+   XMEMCPY(cpy, buf, len);
+   s = cpy;
+   while (clen && (err = s_parse_line(s, &clen, key, &comment)) == CRYPT_OK) {
+      if (cb(key, comment, ctx)) {
+         break;
+      }
+      s += clen;
+      len -= clen;
+      clen = len;
+      key = XCALLOC(1, sizeof(*key));
+      if (key == NULL) {
+         err = CRYPT_MEM;
+         break;
+      }
+      if (comment) {
+         XFREE(comment);
+         comment = NULL;
+      }
+   }
+   if (comment)
+      XFREE(comment);
+   if (cpy)
+      XFREE(cpy);
+   if (key)
+      XFREE(key);
+   return err;
+}
+
 static int s_decrypt_private_keys(unsigned char *in, unsigned long *inlen,
                                   unsigned char *tag, unsigned long taglen,
                                   struct kdf_options *opts)
@@ -574,6 +748,9 @@ retry:
 
       privkey_len = l;
       privkey = XMALLOC(privkey_len);
+      if (privkey == NULL) {
+         return CRYPT_MEM;
+      }
 
       if ((err = ssh_decode_sequence_multi(p, &w,
                                            LTC_SSHDATA_STRING, privkey, &privkey_len,
@@ -604,7 +781,7 @@ retry:
       p = privkey;
       w = privkey_len;
    }
-   if ((err = s_decode_key(p, &w, k, hdr.id->flags)) != CRYPT_OK) {
+   if ((err = s_decode_key(p, &w, k, NULL, hdr.id->flags)) != CRYPT_OK) {
       goto cleanup;
    }
 
@@ -628,6 +805,32 @@ int pem_decode_openssh_filehandle(FILE *f, ltc_pka_key *k, const password_ctx *p
       return s_decode_openssh(&g, k, pw_ctx);
    }
 }
+
+int ssh_read_authorized_keys_filehandle(FILE *f, ssh_authorized_key_cb cb, void *ctx)
+{
+   size_t tot_data;
+   void *buf;
+   int err;
+
+   LTC_ARGCHK(f != NULL);
+   LTC_ARGCHK(cb != NULL);
+
+   fseek(f, 0, SEEK_END);
+   tot_data = ftell(f);
+   rewind(f);
+   buf = XMALLOC(tot_data);
+   if (buf == NULL) {
+      return CRYPT_MEM;
+   }
+   if (fread(buf, 1, tot_data, f) != tot_data) {
+      err = CRYPT_ERROR;
+   } else {
+      err = s_read_authorized_keys(buf, tot_data, cb, ctx);
+   }
+   XFREE(buf);
+
+   return err;
+}
 #endif /* LTC_NO_FILE */
 
 int pem_decode_openssh(const void *buf, unsigned long len, ltc_pka_key *k, const password_ctx *pw_ctx)
@@ -641,4 +844,12 @@ int pem_decode_openssh(const void *buf, unsigned long len, ltc_pka_key *k, const
    }
 }
 
+int ssh_read_authorized_keys(const void *buf, unsigned long len, ssh_authorized_key_cb cb, void *ctx)
+{
+   LTC_ARGCHK(buf != NULL);
+   LTC_ARGCHK(len != 0);
+   LTC_ARGCHK(cb != NULL);
+
+   return s_read_authorized_keys(buf, len, cb, ctx);
+}
 #endif /* defined(LTC_PEM_SSH) */

+ 2 - 3
src/pk/ecc/ecc_find_curve.c

@@ -211,10 +211,9 @@ int ecc_find_curve(const char *name_or_oid, const ltc_ecc_curve **cu)
    int i, j;
    const char *OID = NULL;
 
-   LTC_ARGCHK(cu != NULL);
    LTC_ARGCHK(name_or_oid != NULL);
 
-   *cu = NULL;
+   if (cu) *cu = NULL;
 
    for (i = 0; s_curve_names[i].OID != NULL && !OID; i++) {
       if (XSTRCMP(s_curve_names[i].OID, name_or_oid) == 0) {
@@ -230,7 +229,7 @@ int ecc_find_curve(const char *name_or_oid, const ltc_ecc_curve **cu)
    if (OID != NULL) {
       for (i = 0; ltc_ecc_curves[i].prime != NULL; i++) {
          if (XSTRCMP(ltc_ecc_curves[i].OID, OID) == 0) {
-            *cu = &ltc_ecc_curves[i];
+            if (cu) *cu = &ltc_ecc_curves[i];
             return CRYPT_OK;
          }
       }

+ 13 - 0
src/pk/pka_key_free.c → src/pk/pka_key.c

@@ -2,6 +2,19 @@
 /* SPDX-License-Identifier: Unlicense */
 #include "tomcrypt_private.h"
 
+void pka_key_destroy(ltc_pka_key **key)
+{
+   LTC_ARGCHKVD(key != NULL);
+
+   if (!*key)
+      return;
+
+   pka_key_free(*key);
+   zeromem(*key, sizeof(**key));
+   XFREE(*key);
+   *key = NULL;
+}
+
 void pka_key_free(ltc_pka_key *key)
 {
    LTC_ARGCHKVD(key != NULL);

+ 11 - 0
tests/pem/pubkeys/authorized_keys/all.pub

@@ -0,0 +1,11 @@
+ssh-dss AAAAB3NzaC1kc3MAAACBAKU8/avmkFeGnSqwYG7dZnQlG+01QNaxu3F5v0NcL/SRUW7IdpUq8t14siK0mA6yjphLhOf5t8gugTEVBllP86ANSbFigH7WN3v6ydJWqm60pNhNHN//50cnNtIsXbxeq3VtsI64pkH1OJqeZDHLmu73k4T0EKOzsylSfF/wtVBJAAAAFQChpubLHViwPB+jSvUb8e4THS7PBQAAAIAJD1PMCiTCQa1xyD/NCWOajCufTOIzKAhm6l+nlBVPiKI+262XpYt127Ke4mPL8XJBizoTjSQN08uHMg/8L6W/cdO2aZ+mhkBnS1xAm83DAwqLrDraR1w/4QRFxr5Vbyy8qnejrPjTJobBN1BGsv84wHkjmoCn6pFIfkGYeATlJgAAAIAHYPU1zMVBTDWru7SNC4G2UyWGWYYLjLytBVHfQmBa51CmqrSs2kCfGLGA1ynfYENsxcJq9nsXrb4i17H5BHJFkH0g7BUDpeBeLr8gsK3WgfqWwtZsDkltObw9chUD/siK6q/dk/fSIB2Ho0inev7k68Z5ZkNI4XOwuEssAVhmwA== This is a test key!
+ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBI4QIXX6Mg0NWD6OvWqb/L88sTs56hqs5sXkAh6vr9NleFczaijCUyn/9qLz/CfWg1pqj3KgOiFZ4ByT3xYfJIs= This is a test key!
+ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBJZCFyqAiU2okJefOdynhpMGIakbXkkmywbUzl4jfAdCUaQg+lFDVrO5GSk4NlERd8iFqT++/fvydsCc2gkTr2H7HCLVbiEezPiqXBq0dckzBymK2LCHM9BkJtuOm4hmNA== This is a test key!
+ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAFk35srteP9twCwYKvU9ovMBi77Dd6lEBPrFaMEb0CZdZ5MC3nSqflGHRWkSbUpjdPdO7cYQNpK9YXHbNSO5hbU1gFZgyiGFxwJYYz8NAjedBXMgyH4JWplK5FQm5P5cvaglItC9qkKioUXhCc67YMYBtivXlUe0PgIq6kbHTqbX6+5Nw== This is a test key!
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIL0XsiFcRDp6Hpsoak8OdiiBMJhM2UKszNTxoGS7dJ++ This is a test key!
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQC458k0igFgcvkrDjUKo0gIgPW0+7QEPck7/DXIpGAkHzHDTQLu7/IzxBDCL7iQhF2eQwaR/s9SXjs2DV/OT3SXM3QjJO4PS3nHkzfP7Zjqm9LGSicBgR8x5LPmxoNVA3qOInMP5xhhgbKYYusuBMAlpEgq/T9uCvLIuvRnGhBTDw== This is a test key!
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAwQCkYHef/kFhdtEwG4YZfanYY92cmDX9gqw7OE2dxMyQyj5Eyn/ztaIIERJGv2BG7TlEi8Gd83wFvSrqk4diHycQm2FbLtp258buy/PYCY3cvxYL1imUxt6aPeLphzxQpt2dSwCom6M5Dq/sxB4YV+lvVpWZEuIFzsfGtqiLO7U4x80lXC1430MtN5Z6nYTOVtktYkFJCmMSThYice6L4RHdTNejQZlmKGfLEKeR3wTwBIfLO9XFkSlHz2Uk4q/Clr0= This is a test key!
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDdxjN0+2xc7pdreBqYc/mDRC8OuWrS7wre5fEUBpylorQjbhtUVUU+Ox/6RSZbbeNg3EvcWTT7NQY71w7LGSgiNK9bgU1K1516zpvY0F2IGgx9xZvvIJVELSXQRFgIBSFesamVZvJRd5+szgq1jmMTibo9fyPJoZLPys5SFxPHDeyKUz8lzYHVmuOhzOhSjSNHQGVP0Hoe8N7dQaFU40A8JSsUh567vgjXlts13KErGaBey244oe4J+xlwzWHsigAj1DoCGMaDdSQ8WfE/BEUJq5mNEp+GxQe4hHMwbbg2RJZZ8kFRKOiCUicQkVCFA3yWao+5odcgyI03sqfnrgkb This is a test key!
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCjoUqRX9tJ6YVj+E7Y0D9oy2SCUtoLmNqovMOc/bWB+Fg8GgEQNp99XvH6p+/XDIY4Mm91Q9o1rydWnAnHKs/9LOySpOUNbbflDsFstN8ZrqqyuPiIoZqawQvYexk0/0ZyNckA9fWAKZDJom1N5DutBK55Dgq40yHkpAB6nlqdWLY8Iab0V4LKTtNBGydrdPR8A10I54kDkFxx82NOZNJ9hq7QYYJDsL5Le/fB+PqIksP5qo2lfaPJe9xtx7m4+FIJRQQOLrFef/K0tO1Zv0U5dVNHmWWhyTN4tnwoEX4pZ7YkHHemcBFiX4xt70/zpvG5cuIPMwJIA5P72w7A5ifKSoTLrkQGy7yW/ezyxrm42Ng5jNQb+jsiuQhcnU/IKip7xO1K2zxSPhcwDRRIPGGwkCfMrr5PG4EVn9PJCeW/ydQpOm4ysJay8AZxyQohP1jRasus+qUGovOHD6rjiDrg0lDpJY7Cf6EtPU/Z0GFyf5WY4952lRiawIXLi0VOAexG/s/n+eIiwTiFOtnmURjRjLJ3TlkVy/C9Iw6TBaJsF9DW/MN4BeYMSAVodVjqWJyLQc7ulbymguMuNnkhDjQ1XR0SXNw9fzT9lE+NC+EOwxysOu+y9ErV6lA8mExwtmu/a9zHUnyunXUz8ZVFh6mUhP0Tc7X/F/Q2XTTxzzC4jw== This is a test key!
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAYQDYebeGQFCnlQiNRE7r9UEbjr+DQMTdw1ZHGB2w6xD/DzKem8761GdCpqsLrGaw2D7aSIoP1B5Sz870YoVWHn6Ao7Hvm17V3Kxfn4B01GNQTM5+L26mdYs5iJNGu/ltUdc= comment for rsa/768 key
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAEAQCpX3u2NWcx7n4Hoe7/AYqLItYRwcstbTuve0yTZ6HHcwKs6RChChunSVne2fkxecuLdpiAZNrdja7sQxqL0wDi+vjWucLml0hDQ2ipPy4/849DmQeNVh11oRoUkA6HpI5oSr5LTJGpXinT/CRMP3lU4oM7GXwyOslHBJsQz3xwiZgjC9dsU06FrZLxby0noB5EkGYHWIU7TABogVrl+q8aqiFIli6oUW4QGlefQNbv1yn/P1xkk/JDpN7BZW7XX1cbAc19p+MqVuMekI34bjHMoVU5evY4WqmtfWRIoounAclCuiOue/qrYMoXwo64JrMaX19nIl65jrDqfjCtRQtWq6i9D/pTF2LzERgEkLU9xV6stIXKkcpjyErBrvlKlRHdXWhkGN1XonTK32GSPOxsDkRsyjOCrfMlTkcuOunIBKzpFWF0N9m6H6jh/mDdatvQ1t72clgTgQI1rrtxh4RzbiKThq7GEuQn5aVjsj/Z0q+qeY71t7lNF1kaaLa44kPWcmSMqv9us0U90mgSn+J/KtFnBBDzs4gQsn54BEyZUhF/qOl4FfpgzR6+KzdQRfXlGY2LFHTlPbdyJnsBlRs3ld2W3lghbNNi4VZsLt0syDdyaW2wr3atWlFsq5XxkGY7/lyRzHlWRCIAbSSZgDz/aWBBdpvg54AtIvRJysO1nZdfD0b1NLjD8oGt3hftWyUn/6/UigW36AfqgsJjHly2mpSW53lYJHktsrPfrQDNT3J/SftKpGTjU5Rwxz41JSQwEifM5LomG/MGrw5+kJGdp4GhlRSqf25lR3BRSjSI6r7ofYPGOCSc9TWjnEifv1HtcH08mzd92Bu6WXCWFpXxerf9oLSWYlZDLUl9nxdpclGaqQyyVXwFWKKUieu0+wg/u2YryrQ1JgTsAlWFYh8/So9yWyqXcwFkKpL0cWf1sRbRTuLzhUIyc31MNWyVN1P+j1HGKEveKVfGTxiaNx8qxEFlsssNVmFwHvLB7sX7P8hMXjZxevTekKLkKR+//EEQp8qwjHDlOPWm1tQPuKMHsbDcjxyXqFO6HMEJhfeV0OWK6tBMlypSjM8+8fmqFBSiJGmXM1S0v/8S6D2Q/3OrnFD5XLOsuubTYeSqJNzIaLSNvHvGk1tJRqc4qQjDRXI8ZQ+QGZJBl3ejHUM9f1cNMApCom4RfcZqvK0NTesb4OhFF7K3V8Bb6DfYaW76TtJQYtN/sGAweSrifzXZ3XaBHelCrh91kozzKrEcJYeGtUfR9o823U3pTk5bX7QvECgLuP6G5L8Lc6V8VI8iXhV6SfEVN9ecwh6nLsUHimqS5IvdynbqQ9rj3+ZhaVQiHmrF1n3jrxnEIY0IWSUdUwH5 This is a test key!

+ 1 - 0
tests/pem/pubkeys/authorized_keys/ssh_ed25519_openssh-no_comment.pub

@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIL0XsiFcRDp6Hpsoak8OdiiBMJhM2UKszNTxoGS7dJ++

+ 1 - 0
tests/pem/ssh/authorized_keys-invalid/ssh_ed25519_openssh-wrong_type.pub

@@ -0,0 +1 @@
+ssh-rsa AAAAC3NzaC1lZDI1NTE5AAAAIL0XsiFcRDp6Hpsoak8OdiiBMJhM2UKszNTxoGS7dJ++

+ 26 - 0
tests/pem_test.c

@@ -24,6 +24,27 @@ static int s_pem_decode_ssh_f(FILE *f, void *key)
    return pem_decode_openssh_filehandle(f, key, &pw_ctx);
 }
 
+int s_authorized_key_cb(ltc_pka_key *k, const char *comment, void *ctx)
+{
+   LTC_UNUSED_PARAM(comment);
+   LTC_UNUSED_PARAM(ctx);
+   pka_key_destroy(&k);
+   return 0;
+}
+static int s_read_authorized_keys(const void *in, unsigned long inlen, void *ctx)
+{
+   return ssh_read_authorized_keys(in, inlen, s_authorized_key_cb, ctx);
+}
+static int s_read_authorized_keys_f(FILE *f, void *ctx)
+{
+   return ssh_read_authorized_keys_filehandle(f, s_authorized_key_cb, ctx);
+}
+static int s_read_invalid_authorized_keys(const void *in, unsigned long inlen, void *ctx)
+{
+   SHOULD_FAIL(ssh_read_authorized_keys(in, inlen, s_authorized_key_cb, ctx));
+   return CRYPT_OK;
+}
+
 #endif
 
 static int password_get(void **p, unsigned long *l, void *u)
@@ -140,9 +161,14 @@ int pem_test(void)
 #ifdef LTC_SSH
    DO(test_process_dir("tests/pem/ssh", &key, s_pem_decode_ssh, NULL, (dir_cleanup_cb)pka_key_free, "pem_ssh_test"));
    DO(test_process_dir("tests/pem/ssh", &key, NULL, s_pem_decode_ssh_f, (dir_cleanup_cb)pka_key_free, "pem_ssh_test_filehandle"));
+   DO(test_process_dir("tests/pem/ssh/authorized_keys", &key, s_read_authorized_keys, NULL, (dir_cleanup_cb)pka_key_free, "pem_ssh_authorized_keys_test"));
+   DO(test_process_dir("tests/pem/ssh/authorized_keys", &key, NULL, s_read_authorized_keys_f, (dir_cleanup_cb)pka_key_free, "pem_ssh_authorized_keys_test"));
+   DO(test_process_dir("tests/pem/ssh/authorized_keys-invalid", &key, s_read_invalid_authorized_keys, NULL, NULL, "pem_ssh_authorized_keys_invalid_test"));
    DO(test_process_dir("tests/pem/ssh/extra", &key, s_pem_decode_ssh, NULL, (dir_cleanup_cb)pka_key_free, "pem_ssh_test+extra"));
    DO(test_process_dir("tests/pem/pubkeys", &key, s_pem_only_decode, NULL, (dir_cleanup_cb)pka_key_free, "pem_pubkeys_test"));
    DO(test_process_dir("tests/pem/pubkeys", &key, NULL, s_pem_only_decode_f, (dir_cleanup_cb)pka_key_free, "pem_pubkeys_test_filehandle"));
+   DO(test_process_dir("tests/pem/pubkeys/authorized_keys", &key, s_read_authorized_keys, NULL, (dir_cleanup_cb)pka_key_free, "pem_pubkeys_authorized_keys_test"));
+   DO(test_process_dir("tests/pem/pubkeys/authorized_keys", &key, NULL, s_read_authorized_keys_f, (dir_cleanup_cb)pka_key_free, "pem_pubkeys_authorized_keys_test"));
 #endif
    DO(test_process_dir("tests/pem", &key, s_pem_only_decode, NULL, (dir_cleanup_cb)pka_key_free, "pem_test"));
    DO(test_process_dir("tests/pem", &key, NULL, s_pem_only_decode_f, (dir_cleanup_cb)pka_key_free, "pem_test_filehandle"));