gpg-agent: SSH certificate support

NIIBE Yutaka gniibe at fsij.org
Tue Aug 9 10:39:48 CEST 2016


On 08/05/2016 08:28 PM, Werner Koch wrote:
> On Fri,  5 Aug 2016 10:48, gniibe at fsij.org said:
>>   read_key_file (const unsigned char *grip, gcry_sexp_t *result, int *ssh)
>>
>> When SSH is not NULL, it means allowing returning SSH certificate.
> 
> I would suggest to change to "char **ssh" and return a malloced buffer
> with the certificate (in some encoding).  The creation and parsing of
> the s-expressions is quite complicate when not using Lisp and we need to
> return that data anyway as a plain buffer.  This way we reduce the risk
> of introducing bugs in code paths not related to the ssh certificates.
> 
> To be future proof an strlist_t could also be used which would allow to
> return several certifciates or other info.

Thank you for the suggestions.  In the protocol of SSH (extended by
OpenSSH), only single certificate is supported.  So, I take simpler
approach, currently.

Now, in my working directory, I have following changes.

Since I don't have any SSH certificate, it is not tested yet.  I only
checked it builds with no problem and "make check" goes successfully.

I'll create SSH certificate to test this patch.

diff --git a/agent/agent.h b/agent/agent.h
index fe5ffba..b541037 100644
--- a/agent/agent.h
+++ b/agent/agent.h
@@ -396,7 +396,8 @@ gpg_error_t agent_raw_key_from_file (ctrl_t ctrl,
const unsigned char *grip,
                                      gcry_sexp_t *result);
 gpg_error_t agent_public_key_from_file (ctrl_t ctrl,
                                         const unsigned char *grip,
-                                        gcry_sexp_t *result);
+                                        gcry_sexp_t *result,
+                                        char **r_ssh_cert);
 int agent_is_dsa_key (gcry_sexp_t s_key);
 int agent_is_eddsa_key (gcry_sexp_t s_key);
 int agent_key_available (const unsigned char *grip);
diff --git a/agent/command-ssh.c b/agent/command-ssh.c
index b01cc06..8671154 100644
--- a/agent/command-ssh.c
+++ b/agent/command-ssh.c
@@ -2274,12 +2274,13 @@ ssh_receive_key (estream_t stream, gcry_sexp_t
*key_new, int secret,
 }


-/* Write the public key from KEY to STREAM in SSH key format.  If
-   OVERRIDE_COMMENT is not NULL, it will be used instead of the
-   comment stored in the key.  */
+/* Write the public key from KEY to STREAM in SSH key format.  Or
+   write the certificate of CERT to STREAM in SSH key format.  CERT
+   has precedence over KEY.  If OVERRIDE_COMMENT is not NULL, it will
+   be used instead of the comment stored in the key.  */
 static gpg_error_t
 ssh_send_key_public (estream_t stream, gcry_sexp_t key,
-                     const char *override_comment)
+                     char *cert, const char *override_comment)
 {
   ssh_key_type_spec_t spec;
   int algo;
@@ -2288,21 +2289,48 @@ ssh_send_key_public (estream_t stream,
gcry_sexp_t key,
   size_t bloblen;
   gpg_error_t err = 0;

-  algo = get_pk_algo_from_key (key);
-  if (algo == 0)
-    goto out;
+  if (cert)
+    {
+      struct b64state state;
+      size_t nbytes;

-  err = ssh_key_type_lookup (NULL, algo, &spec);
-  if (err)
-    goto out;
+      err = b64dec_start (&state, NULL);
+      if (err)
+        goto out;

-  err = ssh_key_to_blob (key, 0, spec, &blob, &bloblen);
-  if (err)
-    goto out;
+      err = b64dec_proc (&state, cert, strlen (cert), &nbytes);
+      if (err)
+        goto out;

-  err = stream_write_string (stream, blob, bloblen);
-  if (err)
-    goto out;
+      err = b64dec_finish (&state);
+      if (err)
+        goto out;
+
+      err = stream_write_string (stream, cert, nbytes);
+      if (err)
+        goto out;
+    }
+  else
+    {
+      algo = get_pk_algo_from_key (key);
+      if (algo == 0)
+        {
+          err = gpg_error (GPG_ERR_BAD_SECKEY);
+          goto out;
+        }
+
+      err = ssh_key_type_lookup (NULL, algo, &spec);
+      if (err)
+        goto out;
+
+      err = ssh_key_to_blob (key, 0, spec, &blob, &bloblen);
+      if (err)
+        goto out;
+
+      err = stream_write_string (stream, blob, bloblen);
+      if (err)
+        goto out;
+    }

   if (override_comment)
     err = stream_write_cstring (stream, override_comment);
@@ -2588,7 +2616,7 @@ ssh_handler_request_identities (ctrl_t ctrl,
   if (!opt.disable_scdaemon
       && !card_key_available (ctrl, &key_public, &cardsn))
     {
-      err = ssh_send_key_public (key_blobs, key_public, cardsn);
+      err = ssh_send_key_public (key_blobs, key_public, NULL, cardsn);
       gcry_sexp_release (key_public);
       key_public = NULL;
       xfree (cardsn);
@@ -2606,6 +2634,7 @@ ssh_handler_request_identities (ctrl_t ctrl,
   while (!read_control_file_item (cf))
     {
       unsigned char grip[20];
+      char *cert;

       if (!cf->item.valid)
         continue; /* Should not happen.  */
@@ -2614,14 +2643,17 @@ ssh_handler_request_identities (ctrl_t ctrl,
       assert (strlen (cf->item.hexgrip) == 40);
       hex2bin (cf->item.hexgrip, grip, sizeof (grip));

-      err = agent_public_key_from_file (ctrl, grip, &key_public);
+      err = agent_public_key_from_file (ctrl, grip, &key_public, &cert);
       if (err)
         {
-          log_error ("failed to read the public key\n");
+          log_error ("%s:%d: key '%s' skipped: %s\n",
+                     cf->fname, cf->lnr, cf->item.hexgrip,
+                     gpg_strerror (err));
           continue;
         }

-      err = ssh_send_key_public (key_blobs, key_public, NULL);
+      err = ssh_send_key_public (key_blobs, key_public, cert, NULL);
+      xfree (cert);
       if (err)
         goto out;
       gcry_sexp_release (key_public);
diff --git a/agent/command.c b/agent/command.c
index 7fc28ad..f72c0a7 100644
--- a/agent/command.c
+++ b/agent/command.c
@@ -998,7 +998,7 @@ cmd_readkey (assuan_context_t ctx, char *line)
   if (rc)
     return rc; /* Return immediately as this is already an Assuan error
code.*/

-  rc = agent_public_key_from_file (ctrl, grip, &s_pkey);
+  rc = agent_public_key_from_file (ctrl, grip, &s_pkey, NULL);
   if (!rc)
     {
       size_t len;
diff --git a/agent/findkey.c b/agent/findkey.c
index c5ab0e9..894243c 100644
--- a/agent/findkey.c
+++ b/agent/findkey.c
@@ -634,9 +634,13 @@ unprotect (ctrl_t ctrl, const char *cache_nonce,
const char *desc_text,

 /* Read the key identified by GRIP from the private key directory and
    return it as an gcrypt S-expression object in RESULT.  On failure
-   returns an error code and stores NULL at RESULT. */
+   returns an error code and stores NULL at RESULT.  If R_SSH_CERT is
+   not NULL and certificate information is available in the Extended
+   Private Key Format, certificate encoded in base64 is stored there.
+*/
 static gpg_error_t
-read_key_file (const unsigned char *grip, gcry_sexp_t *result)
+read_key_file (const unsigned char *grip, gcry_sexp_t *result,
+               char **r_ssh_cert)
 {
   int rc;
   char *fname;
@@ -699,10 +703,22 @@ read_key_file (const unsigned char *grip,
gcry_sexp_t *result)
       else
         {
           rc = nvc_get_private_key (pk, result);
-          nvc_release (pk);
           if (rc)
             log_error ("error getting private key from '%s': %s\n",
                        fname, gpg_strerror (rc));
+          else
+            {
+              nve_t e = nvc_lookup (pk, "OpenSSH-cert");
+              if (e)
+                {
+                  char *cert_str;
+
+                  cert_str = nve_value (e);
+                  if (cert_str)
+                    *r_ssh_cert = xtrystrdup (cert_str);
+                }
+            }
+          nvc_release (pk);
         }

       xfree (fname);
@@ -811,7 +827,7 @@ agent_key_from_file (ctrl_t ctrl, const char
*cache_nonce,
   if (r_passphrase)
     *r_passphrase = NULL;

-  rc = read_key_file (grip, &s_skey);
+  rc = read_key_file (grip, &s_skey, NULL);
   if (rc)
     {
       if (gpg_err_code (rc) == GPG_ERR_ENOENT)
@@ -1141,7 +1157,7 @@ agent_raw_key_from_file (ctrl_t ctrl, const
unsigned char *grip,

   *result = NULL;

-  err = read_key_file (grip, &s_skey);
+  err = read_key_file (grip, &s_skey, NULL);
   if (!err)
     *result = s_skey;
   return err;
@@ -1151,11 +1167,14 @@ agent_raw_key_from_file (ctrl_t ctrl, const
unsigned char *grip,
 /* Return the public key for the keygrip GRIP.  The result is stored
    at RESULT.  This function extracts the public key from the private
    key database.  On failure an error code is returned and NULL stored
-   at RESULT. */
+   at RESULT.  When OpenSSH cert information is available and
+   R_SSH_CERT is not NULL, base64 encoded string is stored there.  The
+   caller needs to free the returned certificate in base64.
+*/
 gpg_error_t
 agent_public_key_from_file (ctrl_t ctrl,
                             const unsigned char *grip,
-                            gcry_sexp_t *result)
+                            gcry_sexp_t *result, char **r_ssh_cert)
 {
   gpg_error_t err;
   int i, idx;
@@ -1178,8 +1197,9 @@ agent_public_key_from_file (ctrl_t ctrl,
   (void)ctrl;

   *result = NULL;
+  *r_ssh_cert = NULL;

-  err = read_key_file (grip, &s_skey);
+  err = read_key_file (grip, &s_skey, r_ssh_cert);
   if (err)
     return err;

@@ -1190,6 +1210,8 @@ agent_public_key_from_file (ctrl_t ctrl,
                              array, DIM (array), &curve, &flags);
   if (err)
     {
+      xfree (*r_ssh_cert);
+      *r_ssh_cert = NULL;
       gcry_sexp_release (s_skey);
       return err;
     }
@@ -1221,6 +1243,8 @@ agent_public_key_from_file (ctrl_t ctrl,
   format = xtrymalloc (15+4+7*npkey+10+15+1+1);
   if (!format)
     {
+      xfree (*r_ssh_cert);
+      *r_ssh_cert = NULL;
       err = gpg_error_from_syserror ();
       for (i=0; array[i]; i++)
         gcry_mpi_release (array[i]);
@@ -1275,6 +1299,11 @@ agent_public_key_from_file (ctrl_t ctrl,

   if (!err)
     *result = list;
+  else
+    {
+      xfree (*r_ssh_cert);
+      *r_ssh_cert = NULL;
+    }
   return err;
 }

@@ -1324,7 +1353,7 @@ agent_key_info_from_file (ctrl_t ctrl, const
unsigned char *grip,
   {
     gcry_sexp_t sexp;

-    err = read_key_file (grip, &sexp);
+    err = read_key_file (grip, &sexp, NULL);
     if (err)
       {
         if (gpg_err_code (err) == GPG_ERR_ENOENT)
@@ -1405,7 +1434,7 @@ agent_delete_key (ctrl_t ctrl, const char *desc_text,
   char hexgrip[40+4+1];
   char *default_desc = NULL;

-  err = read_key_file (grip, &s_skey);
+  err = read_key_file (grip, &s_skey, NULL);
   if (gpg_err_code (err) == GPG_ERR_ENOENT)
     err = gpg_error (GPG_ERR_NO_SECKEY);
   if (err)
diff --git a/agent/pksign.c b/agent/pksign.c
index 9011be2..2b16a30 100644
--- a/agent/pksign.c
+++ b/agent/pksign.c
@@ -332,7 +332,7 @@ agent_pksign_do (ctrl_t ctrl, const char *cache_nonce,
       int is_ECDSA = 0;
       int is_EdDSA = 0;

-      rc = agent_public_key_from_file (ctrl, ctrl->keygrip, &s_pkey);
+      rc = agent_public_key_from_file (ctrl, ctrl->keygrip, &s_pkey, NULL);
       if (rc)
         {
           log_error ("failed to read the public key\n");
-- 



More information about the Gnupg-devel mailing list