common: Correctly render SHA256-based ssh fingerprints.
authorJustus Winter <justus@g10code.com>
Wed, 24 May 2017 15:03:58 +0000 (17:03 +0200)
committerJustus Winter <justus@g10code.com>
Wed, 24 May 2017 15:07:13 +0000 (17:07 +0200)
* common/ssh-utils.c (dummy_realloc): New function.
(dummy_free): Likewise.
(get_fingerprint): Prepend the fingerprint with the name of the digest
algorithm.  Correctly render SHA256-based ssh fingerprints.
* common/t-ssh-utils.c (sample_keys): Add SHA256 hashes for the keys.
(main): Add an option to dump the keys to gather fingerprints, also
print the SHA256 fingerprint for keys given as arguments, and check
the SHA256 fingerprints of the test keys.

GnuPG-bug-id: 2106
Signed-off-by: Justus Winter <justus@g10code.com>
common/ssh-utils.c
common/t-ssh-utils.c

index 3925602..38d6e8a 100644 (file)
@@ -64,6 +64,9 @@ is_eddsa (gcry_sexp_t keyparms)
   return result;
 }
 
+/* Dummy functions for es_mopen.  */
+static void *dummy_realloc (void *mem, size_t size) { (void) size; return mem; }
+static void dummy_free (void *mem) { (void) mem; }
 
 /* Return the Secure Shell type fingerprint for KEY using digest ALGO.
    The length of the fingerprint is returned at R_LEN and the
@@ -232,10 +235,74 @@ get_fingerprint (gcry_sexp_t key, int algo,
 
   if (as_string)
     {
-      *r_fpr = (algo == GCRY_MD_MD5 ? bin2hexcolon : /* XXX we need base64 */ bin2hex)
-        (gcry_md_read (md, algo), gcry_md_get_algo_dlen (algo), NULL);
+      const char *algo_name;
+      char *fpr;
+
+      /* Prefix string with the algorithm name and a colon.  */
+      algo_name = gcry_md_algo_name (algo);
+      *r_fpr = xtrymalloc (strlen (algo_name) + 1 + 3 * gcry_md_get_algo_dlen (algo) + 1);
+      if (*r_fpr == NULL)
+        {
+          err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
+          goto leave;
+        }
+
+      strncpy (*r_fpr, algo_name, strlen (algo_name));
+      fpr = (char *) *r_fpr + strlen (algo_name);
+      *fpr++ = ':';
+
+      if (algo == GCRY_MD_MD5)
+        {
+          bin2hexcolon (gcry_md_read (md, algo), gcry_md_get_algo_dlen (algo), fpr);
+          strlwr (fpr);
+        }
+      else
+        {
+          struct b64state b64s;
+          estream_t stream;
+          char *p;
+          long int len;
+
+          /* Write the base64-encoded hash to fpr.  */
+          stream = es_mopen (fpr, 3 * gcry_md_get_algo_dlen (algo) + 1, 0,
+                             0, dummy_realloc, dummy_free, "w");
+          if (stream == NULL)
+            {
+              err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
+              goto leave;
+            }
+
+          err = b64enc_start_es (&b64s, stream, "");
+          if (err)
+            {
+              es_fclose (stream);
+              goto leave;
+            }
+
+          err = b64enc_write (&b64s,
+                              gcry_md_read (md, algo), gcry_md_get_algo_dlen (algo));
+          if (err)
+            {
+              es_fclose (stream);
+              goto leave;
+            }
+
+          /* Finish, get the length, and close the stream.  */
+          err = b64enc_finish (&b64s);
+          len = es_ftell (stream);
+          es_fclose (stream);
+          if (err)
+            goto leave;
+
+          /* Terminate.  */
+          fpr[len] = 0;
+
+          /* Strip the trailing padding characters.  */
+          for (p = fpr + len - 1; p > fpr && *p == '='; p--)
+            *p = 0;
+        }
+
       *r_len = strlen (*r_fpr) + 1;
-      strlwr (*r_fpr);
     }
   else
     {
index a4e948f..1c9b87b 100644 (file)
 #include "ssh-utils.h"
 
 
-static struct { const char *key; const char *fpr; } sample_keys[] = {
+static struct
+{
+  const char *key;
+  const char *fpr_md5;
+  const char *fpr_sha256;
+} sample_keys[] = {
   { "(protected-private-key "
     "(rsa "
     "(n #"
@@ -70,7 +75,8 @@ static struct { const char *key; const char *fpr; } sample_keys[] = {
     ")"
     "(comment passphrase_is_abc)"
     ")",
-    "c7:c6:a7:ec:04:6c:87:59:54:f2:88:58:09:e0:f2:b1"
+    "MD5:c7:c6:a7:ec:04:6c:87:59:54:f2:88:58:09:e0:f2:b1",
+    "SHA256:ksKb4DKk2SFX56GRtpt0szBnyjiYARSb2FNlUb7snnE"
   },
   {
     "(protected-private-key "
@@ -99,7 +105,8 @@ static struct { const char *key; const char *fpr; } sample_keys[] = {
     ")"
     "(comment sample_dsa_passphrase_is_abc)"
     ")",
-    "2d:b1:70:1a:04:9e:41:a3:ce:27:a5:c7:22:fe:3a:a3"
+    "MD5:2d:b1:70:1a:04:9e:41:a3:ce:27:a5:c7:22:fe:3a:a3",
+    "SHA256:z8+8HEuD/5QpegGS4tSK02dJF+a6o2V67VM2gOPz9oQ"
   },
   { /* OpenSSH 6.7p1 generated key:  */
     "(protected-private-key "
@@ -118,7 +125,8 @@ static struct { const char *key; const char *fpr; } sample_keys[] = {
     ")"
     "(comment \"ecdsa w/o comment\")"
     ")", /* Passphrase="abc" */
-    "93:4f:08:02:7d:cb:16:9b:0c:39:21:4b:cf:28:5a:19"
+    "MD5:93:4f:08:02:7d:cb:16:9b:0c:39:21:4b:cf:28:5a:19",
+    "SHA256:zSj4uXfE1hlQnESD2LO723fMGXsNwzHrfqOfqep37is"
   },
   { /* OpenSSH 6.7p1 generated key:  */
     "(protected-private-key "
@@ -139,7 +147,8 @@ static struct { const char *key; const char *fpr; } sample_keys[] = {
     ")"
     "(comment \"ecdsa w/o comment\")"
     ")", /* Passphrase="abc" */
-    "a3:cb:44:c8:56:15:25:62:85:fd:e8:04:7a:26:dc:76"
+    "MD5:a3:cb:44:c8:56:15:25:62:85:fd:e8:04:7a:26:dc:76",
+    "SHA256:JuQh5fjduynuuTEwI9C6yAKK1NnLX9PPd7TP0qZfbGs"
   },
   { /* OpenSSH 6.7p1 generated key:  */
     "(protected-private-key "
@@ -161,7 +170,8 @@ static struct { const char *key; const char *fpr; } sample_keys[] = {
     ")"
     "(comment \"ecdsa w/o comment\")"
     ")", /* Passphrase="abc" */
-    "1e:a6:94:ab:bd:81:73:5f:22:bc:0e:c7:89:f6:68:df"
+    "MD5:1e:a6:94:ab:bd:81:73:5f:22:bc:0e:c7:89:f6:68:df",
+    "SHA256:+pbRyYa2UBwDki1k4Wziu2CKrdJIbZM/hOWOQ/sNe/0"
   },
   { /* OpenSSH 6.7p1 generated key:  */
     "(protected-private-key "
@@ -180,7 +190,8 @@ static struct { const char *key; const char *fpr; } sample_keys[] = {
     ")"
     "(comment \"eddsa w/o comment\")"
     ")", /* Passphrase="abc" */
-    "f1:fa:c8:a6:40:bb:b9:a1:65:d7:62:65:ac:26:78:0e"
+    "MD5:f1:fa:c8:a6:40:bb:b9:a1:65:d7:62:65:ac:26:78:0e",
+    "SHA256:yhwBfYnTOnSXcWf1EOPo+oIIpNJ6w/bG36udZ96MmsQ"
   },
   {
     NULL,
@@ -259,9 +270,43 @@ main (int argc, char **argv)
   char *string;
   int idx;
 
-  if (argc == 2)
+  /* --dump-keys dumps the keys as KEYGRIP.key.IDX.  Useful to compute
+       fingerprints to enhance the test vectors.  */
+  if (argc == 2 && strcmp (argv[1], "--dump-keys") == 0)
+    for (idx=0; sample_keys[idx].key; idx++)
+      {
+       FILE *s;
+       char *name;
+       char grip[20];
+       char *hexgrip;
+
+       err = keygrip_from_canon_sexp (sample_keys[idx].key,
+                                      strlen (sample_keys[idx].key),
+                                      grip);
+       if (err)
+         {
+           fprintf (stderr, "%s:%d: error computing keygrip: %s\n",
+                    __FILE__, __LINE__, gpg_strerror (err));
+           exit (1);
+         }
+       hexgrip = bin2hex (grip, 20, NULL);
+
+       name = xtryasprintf ("%s.key.%d", hexgrip, idx);
+       s = fopen (name, "w");
+       if (s == NULL)
+         {
+           fprintf (stderr, "%s:%d: error opening file: %s\n",
+                    __FILE__, __LINE__, gpg_strerror (gpg_error_from_syserror ()));
+           exit (1);
+         }
+       xfree (name);
+       fprintf (s, "%s", sample_keys[idx].key);
+       fclose (s);
+      }
+  else if (argc == 2)
     {
       key = read_key (argv[1]);
+
       err = ssh_get_fingerprint_string (key, GCRY_MD_MD5, &string);
       if (err)
         {
@@ -271,6 +316,17 @@ main (int argc, char **argv)
         }
       puts (string);
       xfree (string);
+
+      err = ssh_get_fingerprint_string (key, GCRY_MD_SHA256, &string);
+      if (err)
+        {
+          fprintf (stderr, "%s:%d: error getting fingerprint: %s\n",
+                   __FILE__, __LINE__, gpg_strerror (err));
+          exit (1);
+        }
+      puts (string);
+      xfree (string);
+
       gcry_sexp_release (key);
     }
   else
@@ -288,7 +344,6 @@ main (int argc, char **argv)
             }
 
           err = ssh_get_fingerprint_string (key, GCRY_MD_MD5, &string);
-          gcry_sexp_release (key);
           if (err)
             {
               fprintf (stderr, "%s:%d: error getting fingerprint for "
@@ -297,16 +352,38 @@ main (int argc, char **argv)
               exit (1);
             }
 
-          if (strcmp (string, sample_keys[idx].fpr))
+          if (strcmp (string, sample_keys[idx].fpr_md5))
+            {
+              fprintf (stderr, "%s:%d: fingerprint mismatch for "
+                       "sample key %d\n",
+                       __FILE__, __LINE__, idx);
+              fprintf (stderr, "want: %s\n got: %s\n",
+                       sample_keys[idx].fpr_md5, string);
+              exit (1);
+            }
+          xfree (string);
+
+          err = ssh_get_fingerprint_string (key, GCRY_MD_SHA256, &string);
+          if (err)
+            {
+              fprintf (stderr, "%s:%d: error getting fingerprint for "
+                       "sample key %d: %s\n",
+                       __FILE__, __LINE__, idx, gpg_strerror (err));
+              exit (1);
+            }
+
+          if (strcmp (string, sample_keys[idx].fpr_sha256))
             {
               fprintf (stderr, "%s:%d: fingerprint mismatch for "
                        "sample key %d\n",
                        __FILE__, __LINE__, idx);
               fprintf (stderr, "want: %s\n got: %s\n",
-                       sample_keys[idx].fpr, string);
+                       sample_keys[idx].fpr_sha256, string);
               exit (1);
             }
           xfree (string);
+
+          gcry_sexp_release (key);
         }
     }