>From 38d6a6417c5afa2b643cd61e400e7785ac1857df Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jcholast@redhat.com>
Date: Mon, 27 Aug 2012 04:43:55 -0400
Subject: [PATCH 2/2] SSH: Parse OpenSSH formatted public keys

---
 src/responder/ssh/sshsrv.c                  |   8 ++
 src/sss_client/ssh/sss_ssh_authorizedkeys.c |   7 ++
 src/util/sss_ssh.c                          | 178 ++++++++++++++++++++++++++++
 src/util/sss_ssh.h                          |   3 +
 4 files changed, 196 insertions(+)

diff --git a/src/responder/ssh/sshsrv.c b/src/responder/ssh/sshsrv.c
index a423231..7bc7751 100644
--- a/src/responder/ssh/sshsrv.c
+++ b/src/responder/ssh/sshsrv.c
@@ -21,6 +21,7 @@
 #include <popt.h>
 
 #include "util/util.h"
+#include "util/sss_ssh.h"
 #include "confdb/confdb.h"
 #include "monitor/monitor_interfaces.h"
 #include "responder/common/responder.h"
@@ -96,6 +97,13 @@ int ssh_process_init(TALLOC_CTX *mem_ctx,
         return ENOMEM;
     }
 
+    ret = sss_ssh_init(ssh_ctx);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_FATAL_FAILURE,
+              ("fatal error initializing OpenSSH public key regex\n"));
+        return ret;
+    }
+
     ssh_cmds = get_ssh_cmds();
     ret = sss_process_init(ssh_ctx, ev, cdb,
                            ssh_cmds,
diff --git a/src/sss_client/ssh/sss_ssh_authorizedkeys.c b/src/sss_client/ssh/sss_ssh_authorizedkeys.c
index 74b9693..fd8bfd8 100644
--- a/src/sss_client/ssh/sss_ssh_authorizedkeys.c
+++ b/src/sss_client/ssh/sss_ssh_authorizedkeys.c
@@ -67,6 +67,13 @@ int main(int argc, const char **argv)
         goto fini;
     }
 
+    ret = sss_ssh_init(mem_ctx);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              ("sss_ssh_init() failed (%d): %s\n", ret, strerror(ret)));
+        return ret;
+    }
+
     /* parse parameters */
     pc = poptGetContext(NULL, argc, argv, long_options, 0);
     poptSetOtherOptionHelp(pc, "USER");
diff --git a/src/util/sss_ssh.c b/src/util/sss_ssh.c
index a713eab..667bb1a 100644
--- a/src/util/sss_ssh.c
+++ b/src/util/sss_ssh.c
@@ -19,12 +19,83 @@
 */
 
 #include <arpa/inet.h>
+#include <pcre.h>
 
 #include "db/sysdb.h"
 #include "util/util.h"
 #include "util/crypto/sss_crypto.h"
 #include "util/sss_ssh.h"
 
+#define OPENSSH_RE ( \
+"   ^                                               " \
+"   [\\t ]*                                         " \
+"   (?:                                             " \
+"       (?<options>                                 " \
+"           [-0-9A-Za-z]+                           " \
+"           (?:=\"(?:[^\\x00\\n\\r\"]|\\\")*\")?    " \
+"           (?:,(?&options))?                       " \
+"       )                                           " \
+"       [\\t ]+                                     " \
+"   )?                                              " \
+"   (?<keytype>                                     " \
+"       [!-+--?A-~]+                                " \
+"       (?:@[-.0-9A-Za-z]+)?                        " \
+"   )                                               " \
+"   [\\t ]+                                         " \
+"   (?<key>                                         " \
+"       [+/0-9A-Za-z]+=*                            " \
+"   )                                               " \
+"   (?:                                             " \
+"       [\\t ]+                                     " \
+"       (?<comment>                                 " \
+"           [^\\x00\\n\\r]*?                        " \
+"       )                                           " \
+"   )?                                              " \
+"   [\\t ]*                                         " \
+"   $                                               " \
+)
+#define OPENSSH_RE_OPTIONS PCRE_EXTENDED
+
+struct sss_ssh_global_ctx {
+    pcre *openssh_re;
+};
+
+static struct sss_ssh_global_ctx *global_ctx = NULL;
+
+static int
+sss_ssh_global_ctx_destructor(struct sss_ssh_global_ctx *ctx)
+{
+    if (ctx->openssh_re) {
+        pcre_free(ctx->openssh_re);
+    }
+    return 0;
+}
+
+errno_t
+sss_ssh_init(TALLOC_CTX *mem_ctx)
+{
+    const char *errstr;
+    int errpos;
+
+    global_ctx = talloc_zero(mem_ctx, struct sss_ssh_global_ctx);
+    if (!global_ctx) {
+        return ENOMEM;
+    }
+
+    global_ctx->openssh_re = pcre_compile(OPENSSH_RE, OPENSSH_RE_OPTIONS,
+                                          &errstr, &errpos, NULL);
+    if (!global_ctx->openssh_re) {
+        DEBUG(SSSDBG_CRIT_FAILURE,
+            ("pcre_compile() failed at offset %d: %s\n", errpos, errstr));
+        talloc_free(global_ctx);
+        return EFAULT;
+    }
+
+    talloc_set_destructor(global_ctx, sss_ssh_global_ctx_destructor);
+
+    return EOK;
+}
+
 errno_t
 sss_ssh_make_ent(TALLOC_CTX *mem_ctx,
                  struct ldb_message *msg,
@@ -142,6 +213,102 @@ sss_ssh_get_pubkey_algorithm(TALLOC_CTX *mem_ctx,
     return EOK;
 }
 
+static errno_t
+sss_ssh_parse_openssh_pubkey(TALLOC_CTX *mem_ctx,
+                             const char *pubkeystr,
+                             struct sss_ssh_pubkey *result)
+{
+    TALLOC_CTX *tmp_ctx;
+    int ovec[30];
+    int ret, strcount, i;
+    const char *substr;
+    const char *fieldnames[3] = {"options", "keytype", "key"};
+    char *fields[3];
+    struct sss_ssh_pubkey pubkey;
+    char *algo;
+
+    if (!global_ctx) {
+        DEBUG(SSSDBG_CRIT_FAILURE, ("sss_ssh_init() was not called\n"));
+        return EFAULT;
+    }
+
+    tmp_ctx = talloc_new(NULL);
+    if (!tmp_ctx) {
+        return ENOMEM;
+    }
+
+    ret = pcre_exec(global_ctx->openssh_re, NULL,
+                    pubkeystr, strlen(pubkeystr), 0,
+                    0, ovec, 30);
+    if (ret <= 0) {
+        if (!ret) {
+            return EFAULT;
+        } else if (ret == PCRE_ERROR_NOMEMORY) {
+            return ENOMEM;
+        } else {
+            return EINVAL;
+        }
+    }
+
+    strcount = ret;
+
+    for (i = 0; i < 3; i++) {
+        ret = pcre_get_named_substring(global_ctx->openssh_re, pubkeystr,
+                                       ovec, strcount, fieldnames[i], &substr);
+        if (ret < 0) {
+            if (ret == PCRE_ERROR_NOMEMORY) {
+                ret = ENOMEM;
+            } else {
+                ret = EINVAL;
+            }
+            goto done;
+        } else if (ret > 0) {
+            fields[i] = talloc_strdup(tmp_ctx, substr);
+            if (!fields[i]) {
+                ret = ENOMEM;
+                goto done;
+            }
+        } else {
+            fields[i] = NULL;
+        }
+        pcre_free_substring(substr);
+    }
+
+    /*
+    The options and keytype fields and keytype and key fields might be
+    indistinguishable in an OpenSSH-formatted public key. In order to tell them
+    apart, we need to inspect both the key and keytype fields for public key
+    data.
+    */
+    for (i = 2; i >= 1; i--) {
+        pubkey.data = sss_base64_decode(tmp_ctx, fields[i], &pubkey.data_len);
+        if (!pubkey.data) {
+            ret = ENOMEM;
+            continue;
+        }
+
+        ret = sss_ssh_get_pubkey_algorithm(tmp_ctx, &pubkey, &algo);
+        if (ret != EOK) {
+            continue;
+        }
+
+        if (strcmp(algo, fields[i - 1]) != 0) {
+            ret = EINVAL;
+            continue;
+        }
+
+        result->data = talloc_steal(mem_ctx, pubkey.data);
+        result->data_len = pubkey.data_len;
+        ret = EOK;
+        break;
+    }
+
+done:
+    talloc_free(tmp_ctx);
+
+    return ret;
+}
+
 errno_t
 sss_ssh_format_pubkey(TALLOC_CTX *mem_ctx,
                       struct sss_ssh_ent *ent,
@@ -152,6 +319,8 @@ sss_ssh_format_pubkey(TALLOC_CTX *mem_ctx,
 {
     TALLOC_CTX *tmp_ctx;
     errno_t ret;
+    size_t len;
+    struct sss_ssh_pubkey parsed;
     char *blob;
     char *algo;
     char *out = NULL;
@@ -165,6 +334,15 @@ sss_ssh_format_pubkey(TALLOC_CTX *mem_ctx,
         return ENOMEM;
     }
 
+    len = strnlen((const char *)pubkey->data, pubkey->data_len + 1);
+    if (len == pubkey->data_len) {
+        ret = sss_ssh_parse_openssh_pubkey(tmp_ctx, (const char *)pubkey->data,
+                                           &parsed);
+        if (ret == EOK) {
+            pubkey = &parsed;
+        }
+    }
+
     blob = sss_base64_encode(tmp_ctx, pubkey->data, pubkey->data_len);
     if (!blob) {
         ret = ENOMEM;
diff --git a/src/util/sss_ssh.h b/src/util/sss_ssh.h
index ef663d9..89f437a 100644
--- a/src/util/sss_ssh.h
+++ b/src/util/sss_ssh.h
@@ -37,6 +37,9 @@ struct sss_ssh_ent {
 };
 
 errno_t
+sss_ssh_init(TALLOC_CTX *mem_ctx);
+
+errno_t
 sss_ssh_make_ent(TALLOC_CTX *mem_ctx,
                  struct ldb_message *msg,
                  struct sss_ssh_ent **result);
-- 
1.7.11.4

