[seahorse/bpereto/seahorse-feature-hkps] pgp: add HKPS support




commit a9c379a7df8114387bb9a5a604deedbfc9e4557d
Author: Benjamin Pereto <benjamin sandchaschte ch>
Date:   Wed Apr 29 19:02:48 2020 +0200

    pgp: add HKPS support
    
    Add support for HKPS keyservers, which runs HKP over TLS.
    
    Use machine readable format for HKP protocol instead of filtering HTML.

 pgp/seahorse-hkp-source.c    | 307 ++++++++++++++++++++++---------------------
 pgp/seahorse-server-source.c |  77 ++++++-----
 2 files changed, 197 insertions(+), 187 deletions(-)
---
diff --git a/pgp/seahorse-hkp-source.c b/pgp/seahorse-hkp-source.c
index 35d741f7..569b5c1c 100644
--- a/pgp/seahorse-hkp-source.c
+++ b/pgp/seahorse-hkp-source.c
@@ -71,32 +71,28 @@ G_DEFINE_TYPE (SeahorseHKPSource, seahorse_hkp_source, SEAHORSE_TYPE_SERVER_SOUR
  *
  * Returns: A #SoupUri with server, port and paths
  */
-static SoupURI*
+static SoupURI *
 get_http_server_uri (SeahorseHKPSource *self, const char *path)
 {
     g_autoptr(SoupURI) uri = NULL;
     g_autofree gchar *server = NULL;
-    gchar *port;
+    g_autofree char *conf_uri = NULL;
 
     g_object_get (self, "key-server", &server, NULL);
     g_return_val_if_fail (server != NULL, NULL);
-
-    uri = soup_uri_new (NULL);
-    soup_uri_set_scheme (uri, SOUP_URI_SCHEME_HTTP);
-
-    /* If it already has a port then use that */
-    port = strchr (server, ':');
-    if (port) {
-        *port++ = '\0';
-        soup_uri_set_port (uri, atoi (port));
-    } else {
-        /* default HKP port */
-        soup_uri_set_port (uri, 11371);
+    g_object_get (self, "uri", &conf_uri, NULL);
+
+    if (strncasecmp (conf_uri, "hkp:", 4) == 0) {
+        g_autofree char *t = g_strdup_printf ("http:%s", conf_uri + 4);
+        uri = soup_uri_new (t);
+    } else if (strncasecmp (conf_uri, "hkps:", 5) == 0) {
+        g_autofree char *t = g_strdup_printf ("https:%s", conf_uri + 5);
+        uri = soup_uri_new (t);
     }
 
-    soup_uri_set_host (uri, server);
     soup_uri_set_path (uri, path);
 
+    g_debug ("HTTP Server URI: %s", soup_uri_to_string(uri, FALSE));
     return g_steal_pointer (&uri);
 }
 
@@ -196,54 +192,31 @@ dehtmlize (gchar *line)
     }
 }
 
-/**
- * parse_hkp_date:
- * @text: The date string to parse, YYYY-MM-DD
- *
+/*
+ * @flags: combintation of [rei] representing the key's status
  *
+ * Parses the flags from the HKP output
  *
- * Returns: 0 on error or the timestamp
+ * returns 0 on error or a combination of seahorse flags based on input
  */
-static unsigned int
-parse_hkp_date (const gchar *text)
-{
-    int year, month, day;
-    struct tm tmbuf;
-    time_t stamp;
-
-    if (strlen (text) != 10 || text[4] != '-' || text[7] != '-')
-        return 0;
-
-    /* YYYY-MM-DD */
-    sscanf (text, "%4d-%2d-%2d", &year, &month, &day);
-
-    /* some basic checks */
-    if (year < 1970 || month < 1 || month > 12 || day < 1 || day > 31)
-        return 0;
-
-    memset (&tmbuf, 0, sizeof tmbuf);
-    tmbuf.tm_mday = day;
-    tmbuf.tm_mon = month - 1;
-    tmbuf.tm_year = year - 1900;
-    tmbuf.tm_isdst = -1;
-
-    stamp = mktime (&tmbuf);
-    return stamp == (time_t)-1 ? 0 : stamp;
-}
-
-static const gchar *
-get_fingerprint_string (const gchar *line)
+static guint
+parse_hkp_flags (char *flags)
 {
-    const gchar *p;
-
-    p = line;
-    while (*p && g_ascii_isspace (*p))
-        p++;
-
-    if (g_ascii_strncasecmp (p, "fingerprint=", 12) == 0)
-        return p + 12;
-
-    return NULL;
+    char flag = 0;
+    for (char *f = flags; f && *f; f++) {
+        switch (*f) {
+            case 'r':
+                flag |= SEAHORSE_FLAG_REVOKED;
+                break;
+            case 'e':
+                flag |= SEAHORSE_FLAG_EXPIRED;
+                break;
+            case 'd':
+                flag |= SEAHORSE_FLAG_DISABLED;
+                break;
+        }
+    }
+    return flag;
 }
 
 /**
@@ -256,157 +229,190 @@ get_fingerprint_string (const gchar *line)
 static GList*
 parse_hkp_index (const gchar *response)
 {
-    /* Luckily enough, both the HKP server and NAI HKP interface to their
-     * LDAP server are close enough in output so the same function can
-     * parse them both. */
-
-    /* pub  2048/<a href="/pks/lookup?op=get&search=0x3CB3B415">3CB3B415</a> 1998/04/03 David M. Shaw &lt;<a 
href="/pks/lookup?op=get&search=0x3CB3B415">dshaw jabberwocky com</a>&gt; */
-
+    /*
+     * Use The OpenPGP HTTP Keyserver Protocol (HKP) to search and get keys
+     * https://tools.ietf.org/html/draft-shaw-openpgp-hkp-00#section-5
+     */
     g_auto(GStrv) lines = NULL;
+    g_auto(GStrv) columns = NULL;
     gchar **l;
-
     SeahorsePgpKey *key = NULL;
-    SeahorsePgpSubkey *subkey_with_id = NULL;
     GList *keys = NULL;
     GList *subkeys = NULL;
     GList *uids = NULL;
     SeahorseFlags flags;
+    gboolean has_uid = FALSE;
+    char *uid_string;
+    guint key_total = 0, key_count = 0;
 
     lines = g_strsplit (response, "\n", 0);
     for (l = lines; *l; l++) {
-        gchar *line, *t;
+        char *line;
 
         line = *l;
-        dehtmlize (line);
 
         g_debug ("%s", line);
 
-        /* Start a new key */
-        if (g_ascii_strncasecmp (line, "pub ", 4) == 0) {
-            g_auto(GStrv) v = NULL;
+        if (strlen(line) == 0) {
+          g_debug ("HKP Parser: skip empty line");
+          continue;
+        }
+
+        /* split the line using hkp delimiter */
+        columns = g_strsplit_set(line, ":", 7);
+
+        /* info header */
+        if (g_ascii_strncasecmp (columns[0], "info", 4) == 0) {
+            if (!columns[1] && !columns[2]) {
+                g_debug("HKP Parse: Invalid info line: %s", line);
+            } else {
+                key_total = strtol (columns[2], NULL, 10);
+            }
+
+        /* start a new key */
+        /* pub:<keyid>:<algo>:<keylen>:<creationdate>:<expirationdate>:<flags> */
+        } else if (g_ascii_strncasecmp (columns[0], "pub", 3) == 0) {
             gchar *fingerprint, *fpr = NULL;
             const gchar *algo;
-            gboolean has_uid = TRUE;
             SeahorsePgpSubkey *subkey;
+            long created = 0, expired = 0;
 
-            t = line + 4;
-            while (*t && g_ascii_isspace (*t))
-                t++;
+            key_count++;
 
-            v = g_strsplit_set (t, " ", 3);
-            if (!v[0] || !v[1] || !v[2]) {
+            /* reset previous key */
+            if (key) {
+                g_debug ("HKP Parse: previous key found");
+                if (has_uid) {
+                  seahorse_pgp_key_set_uids (SEAHORSE_PGP_KEY (key), uids);
+                  g_list_free_full (uids, g_object_unref);
+                  seahorse_pgp_key_set_subkeys (SEAHORSE_PGP_KEY (key), subkeys);
+                  g_list_free_full (subkeys, g_object_unref);
+                  seahorse_pgp_key_realize (SEAHORSE_PGP_KEY (key));
+                  keys = g_list_prepend (keys, key);
+                } else {
+                  g_debug ("HKP Parse: no uid found");
+                }
+                uids = subkeys = NULL;
+                has_uid = FALSE;
+                key = NULL;
+            }
+
+            if (!columns[0] || !columns[1] || !columns[2] || !columns[3] || !columns[4]) {
                 g_message ("Invalid key line from server: %s", line);
                 continue;
             }
 
-            flags = SEAHORSE_FLAG_EXPORTABLE;
-
             /* Cut the length and fingerprint */
-            fpr = strchr (v[0], '/');
+            fpr = columns[1];
             if (fpr == NULL) {
                 g_message ("couldn't find key fingerprint in line from server: %s", line);
-                fpr = "";
-            } else {
-                *(fpr++) = 0;
             }
 
             /* Check out the key type */
-            switch (g_ascii_toupper (v[0][strlen (v[0]) - 1])) {
-            case 'D':
-                algo = "DSA";
-                break;
-            case 'R':
-                algo = "RSA";
-                break;
-            default:
-                algo = "";
-                break;
-            };
-
-            /* Format the date for our parse function */
-            g_strdelimit (v[1], "/", '-');
+            switch (strtol (columns[2], NULL, 10)) {
+                case 1:
+                case 2:
+                case 3:
+                     algo = "RSA";
+                    break;
+                case 17:
+                    algo = "DSA";
+                    break;
+                default:
+                   break;
+            }
+            g_debug ("Algo: %s", algo);
 
-            /* Cleanup the UID */
-            g_strstrip (v[2]);
+            /* set dates */
+            /* created */
+            if (!columns[4]) {
+                g_debug ("HKP Parse: No created date for key on line: %s",
+                         line);
+            } else {
+                created = strtol (columns[4], NULL, 10);
+            }
 
-            if (g_ascii_strcasecmp (v[2], "*** KEY REVOKED ***") == 0) {
-                flags |= SEAHORSE_FLAG_REVOKED;
-                has_uid = FALSE;
+            /* expires (optional) */
+            if (columns[5]) {
+                expired = strtol (columns[5], NULL, 10);
             }
 
-            if (key) {
-                seahorse_pgp_key_set_uids (SEAHORSE_PGP_KEY (key), uids);
-                g_list_free_full (uids, g_object_unref);
-                seahorse_pgp_key_set_subkeys (SEAHORSE_PGP_KEY (key), subkeys);
-                g_list_free_full (subkeys, g_object_unref);
-                seahorse_pgp_key_realize (SEAHORSE_PGP_KEY (key));
-                uids = subkeys = NULL;
-                subkey_with_id = NULL;
-                key = NULL;
+            /* set flags (optional) */
+            flags = SEAHORSE_FLAG_EXPORTABLE;
+            if (columns[6]) {
+                flags |= parse_hkp_flags (columns[6]);
             }
 
+            /* create key */
+            g_debug ("HKP Parse: found new key");
             key = seahorse_pgp_key_new ();
-            keys = g_list_prepend (keys, key);
             g_object_set (key, "object-flags", flags, NULL);
 
             /* Add all the info to the key */
             subkey = seahorse_pgp_subkey_new ();
             seahorse_pgp_subkey_set_keyid (subkey, fpr);
-            subkey_with_id = subkey;
 
             fingerprint = seahorse_pgp_subkey_calc_fingerprint (fpr);
             seahorse_pgp_subkey_set_fingerprint (subkey, fingerprint);
             g_free (fingerprint);
 
             seahorse_pgp_subkey_set_flags (subkey, flags);
-            seahorse_pgp_subkey_set_created (subkey, parse_hkp_date (v[1]));
-            seahorse_pgp_subkey_set_length (subkey, strtol (v[0], NULL, 10));
+
+            seahorse_pgp_subkey_set_created (subkey, created);
+            seahorse_pgp_subkey_set_expires (subkey, expired);
+            seahorse_pgp_subkey_set_length (subkey, strtol (columns[3], NULL, 10));
             seahorse_pgp_subkey_set_algorithm (subkey, algo);
             subkeys = g_list_prepend (subkeys, subkey);
 
-            /* And the UID if one was found */
-            if (has_uid) {
-                SeahorsePgpUid *uid = seahorse_pgp_uid_new (key, v[2]);
-                uids = g_list_prepend (uids, uid);
-            }
-
         /* A UID for the key */
-        } else if (key && g_ascii_strncasecmp (line, "    ", 4) == 0) {
+        } else if (g_ascii_strncasecmp (columns[0], "uid", 3) == 0) {
             SeahorsePgpUid *uid;
 
-            g_strstrip (line);
-            uid = seahorse_pgp_uid_new (key, line);
-            uids = g_list_prepend (uids, uid);
-
-        /* Signatures */
-        } else if (key && g_ascii_strncasecmp (line, "sig ", 4) == 0) {
-            /* TODO: Implement signatures */
+            if (!key) {
+                g_debug ("HKP Parse: Warning: seen uid line before keyline, skipping");
+                g_strfreev (columns);
+                continue;
+            }
 
-        } else if (key && subkey_with_id) {
-            const char *fingerprint_str;
-            g_autofree gchar *pretty_fingerprint = NULL;
+            g_debug ("HKP Parse: handle uid");
+            has_uid = TRUE;
 
-            fingerprint_str = get_fingerprint_string (line);
-            if (fingerprint_str == NULL)
+            if (!columns[0] || !columns[1] || !columns[2]) {
+                g_message ("HKP Parse: Invalid uid line from server: %s", line);
                 continue;
+            }
 
-            pretty_fingerprint = seahorse_pgp_subkey_calc_fingerprint (fingerprint_str);
+            uid_string = g_uri_unescape_string (columns[1], NULL);
+            g_debug ("HKP Parse: decoded uid string: %s", uid_string);
 
-            /* FIXME: we don't check that the fingerprint actually matches
-             * the key's ID.  We also don't validate the fingerprint at
-             * all; the keyserver may have returned some garbage and we
-             * don't notice. */
-            if (pretty_fingerprint[0] != 0)
-                seahorse_pgp_subkey_set_fingerprint (subkey_with_id, pretty_fingerprint);
+            uid = seahorse_pgp_uid_new (key, uid_string);
+            uids = g_list_prepend (uids, uid);
         }
     }
 
+    /* handle last entry */
     if (key) {
-        seahorse_pgp_key_set_uids (SEAHORSE_PGP_KEY (key), g_list_reverse (uids));
-        g_list_free_full (uids, g_object_unref);
-        seahorse_pgp_key_set_subkeys (SEAHORSE_PGP_KEY (key), g_list_reverse (subkeys));
-        g_list_free_full (subkeys, g_object_unref);
-        seahorse_pgp_key_realize (SEAHORSE_PGP_KEY (key));
+        if (has_uid) {
+            seahorse_pgp_key_set_uids (SEAHORSE_PGP_KEY (key), uids);
+            g_list_free_full (uids, g_object_unref);
+            seahorse_pgp_key_set_subkeys (SEAHORSE_PGP_KEY (key), subkeys);
+            g_list_free_full (subkeys, g_object_unref);
+            seahorse_pgp_key_realize (SEAHORSE_PGP_KEY (key));
+            keys = g_list_prepend (keys, key);
+        } else {
+            g_debug ("HKP Parse: No UID found.");
+        }
+        uids = subkeys = NULL;
+        has_uid = FALSE;
+        key = NULL;
+    }
+
+    if (key_total != 0 && key_total != key_count) {
+        g_message ("HKP Parse; Warning: Issue during HKP parsing, "
+                   "only %d keys were parsed out of %d", key_count, key_total);
+
+    } else {
+        g_debug ("HKP Parse: %d keys parsed successfully", key_count);
     }
 
     return keys;
@@ -641,13 +647,14 @@ seahorse_hkp_source_search_async (SeahorseServerSource *source,
 
     form = g_hash_table_new (g_str_hash, g_str_equal);
     g_hash_table_insert (form, "op", "index");
+    g_hash_table_insert (form, "options", "mr");
 
     if (is_hex_keyid (match)) {
         strncpy (hexfpr, "0x", 3);
         strncpy (hexfpr + 2, match, 9);
         g_hash_table_insert (form, "search", hexfpr);
     } else {
-        g_hash_table_insert (form, "search", (char *)match);
+        g_hash_table_insert (form, "search", (char *) match);
     }
 
     g_hash_table_insert (form, "fingerprint", "on");
@@ -994,6 +1001,7 @@ seahorse_hkp_source_class_init (SeahorseHKPSourceClass *klass)
     server_class->import_finish = seahorse_hkp_source_import_finish;
 
     seahorse_servers_register_type ("hkp", _("HTTP Key Server"), seahorse_hkp_is_valid_uri);
+    seahorse_servers_register_type ("hkps", _("HTTPS Key Server"), seahorse_hkp_is_valid_uri);
 }
 /**
  * seahorse_hkp_source_new:
@@ -1032,6 +1040,9 @@ seahorse_hkp_is_valid_uri (const gchar *uri)
         g_autofree gchar *t = g_strdup_printf ("http:%s", uri + 4);
         soup = soup_uri_new (t);
     /* Not 'hkp', but maybe 'http' */
+    } else if (strncasecmp (uri, "hkps:", 5) == 0) {
+        g_autofree gchar *t = g_strdup_printf ("https:%s", uri + 5);
+        soup = soup_uri_new (t);
     } else {
         soup = soup_uri_new (uri);
     }
diff --git a/pgp/seahorse-server-source.c b/pgp/seahorse-server-source.c
index c23ddb1e..70201ffe 100644
--- a/pgp/seahorse-server-source.c
+++ b/pgp/seahorse-server-source.c
@@ -245,14 +245,14 @@ seahorse_server_source_place_iface (SeahorsePlaceIface *iface)
 * PROP_KEY_SERVER, PROP_URI
 *
 **/
-static void 
-seahorse_server_set_property (GObject *object, guint prop_id, 
+static void
+seahorse_server_set_property (GObject *object, guint prop_id,
                               const GValue *value, GParamSpec *pspec)
 {
     SeahorseServerSource *ssrc = SEAHORSE_SERVER_SOURCE (object);
     SeahorseServerSourcePrivate *priv =
         seahorse_server_source_get_instance_private (ssrc);
- 
+
     switch (prop_id) {
     case PROP_LABEL:
         seahorse_server_source_set_label (SEAHORSE_PLACE (ssrc),
@@ -270,7 +270,7 @@ seahorse_server_set_property (GObject *object, guint prop_id,
         break;
     default:
         break;
-    }  
+    }
 }
 
 /**
@@ -283,7 +283,7 @@ seahorse_server_set_property (GObject *object, guint prop_id,
 * PROP_KEY_SERVER, PROP_URI
 *
 **/
-static void 
+static void
 seahorse_server_get_property (GObject *obj,
                               guint prop_id,
                               GValue *value,
@@ -373,7 +373,7 @@ seahorse_server_source_collection_init (GcrCollectionIface *iface)
 *
 * Returns FALSE if the separation failed
 **/
-static gboolean 
+static gboolean
 parse_keyserver_uri (char *uri, const char **scheme, const char **host)
 {
     int assume_ldap = 0;
@@ -423,59 +423,58 @@ parse_keyserver_uri (char *uri, const char **scheme, const char **host)
  *
  * Returns: A new SeahorseServerSource or NULL
  */
-SeahorseServerSource* 
+SeahorseServerSource*
 seahorse_server_source_new (const gchar *server)
 {
     SeahorseServerSource *ssrc = NULL;
     const gchar *scheme;
     const gchar *host;
     gchar *uri, *t;
-    
+
     g_return_val_if_fail (server && server[0], NULL);
-    
+
     uri = g_strdup (server);
-        
+
     if (!parse_keyserver_uri (uri, &scheme, &host)) {
         g_warning ("invalid uri passed: %s", server);
 
-        
+
     } else {
-        
-#ifdef WITH_LDAP       
-        /* LDAP Uris */ 
-        if (g_ascii_strcasecmp (scheme, "ldap") == 0) 
-            ssrc = SEAHORSE_SERVER_SOURCE (seahorse_ldap_source_new (server, host));
-        else
+
+#ifdef WITH_LDAP
+    /* LDAP Uris */
+    if (g_ascii_strcasecmp (scheme, "ldap") == 0)
+        ssrc = SEAHORSE_SERVER_SOURCE (seahorse_ldap_source_new (server, host));
+    else
 #endif /* WITH_LDAP */
-        
+
 #ifdef WITH_HKP
-        /* HKP Uris */
-        if (g_ascii_strcasecmp (scheme, "hkp") == 0) {
-            
-            ssrc = SEAHORSE_SERVER_SOURCE (seahorse_hkp_source_new (server, host));
+    /* HKP Uris */
+    if (g_ascii_strcasecmp (scheme, "hkp") == 0 ||
+        g_ascii_strcasecmp (scheme, "hkps") == 0) {
+
+        ssrc = SEAHORSE_SERVER_SOURCE (seahorse_hkp_source_new (server, host));
 
-        /* HTTP Uris */
-        } else if (g_ascii_strcasecmp (scheme, "http") == 0 ||
-                   g_ascii_strcasecmp (scheme, "https") == 0) {
+    /* HTTP Uris */
+    } else if (g_ascii_strcasecmp (scheme, "http") == 0 ||
+               g_ascii_strcasecmp (scheme, "https") == 0) {
 
-            /* If already have a port */
-            if (strchr (host, ':')) 
-                   ssrc = SEAHORSE_SERVER_SOURCE (seahorse_hkp_source_new (server, host));
+        /* If already have a port */
+        if (strchr (host, ':'))
+            ssrc = SEAHORSE_SERVER_SOURCE (seahorse_hkp_source_new (server, host));
 
-            /* No port make sure to use defaults */
-            else {
-                t = g_strdup_printf ("%s:%d", host, 
-                                     (g_ascii_strcasecmp (scheme, "http") == 0) ? 80 : 443);
-                ssrc = SEAHORSE_SERVER_SOURCE (seahorse_hkp_source_new (server, t));
-                g_free (t);
-            }
+        /* No port make sure to use defaults */
+        else {
+            t = g_strdup_printf ("%s:%d", host, (g_ascii_strcasecmp (scheme, "http") == 0) ? 80 : 443);
+            ssrc = SEAHORSE_SERVER_SOURCE (seahorse_hkp_source_new (server, t));
+            g_free (t);
+        }
 
-        } else
+    } else
 #endif /* WITH_HKP */
-        
-            g_message ("unsupported key server uri scheme: %s", scheme);
+        g_message ("unsupported key server uri scheme: %s", scheme);
     }
-    
+
     g_free (uri);
     return ssrc;
 }


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]