gpg-agent: SSH certificate support

NIIBE Yutaka gniibe at fsij.org
Wed Aug 10 05:09:48 CEST 2016


On 08/09/2016 05:39 PM, NIIBE Yutaka wrote:
> 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.

Here is a updated version.  I confirmed that it works for me
(somehow).

However, it's not the one I had expected; I found that OpenSSH and
ssh-agent interactions are not the one I had expected.

I had expected: if gpg-agent answers with certificate(s) for
REQUEST_IDENTITIES, it were enough.  No, the situation is different.
It is actually not needed, for current protocol between ssh and
ssh-agent.

To use SSH certificate authentication, I need to prepare a file:

	~/.ssh/id_rsa-cert.pub

which is the OpenSSH certificate of my authentication key.  When it is
available, the client program of OpenSSH (ssh) does certificate
authentication after normal public key authentication with this file.
Then, ssh asks signing to ssh-agent.

I think that we need to modify OpenSSH client program to use
certificates which are answered by ssh-agent.

After such a modification of OpenSSH, following patch will make sense.


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 2def342..4b02cde 100644
--- a/agent/command-ssh.c
+++ b/agent/command-ssh.c
@@ -2280,12 +2280,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;
@@ -2294,21 +2295,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);
@@ -2594,7 +2622,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);
@@ -2612,6 +2640,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.  */
@@ -2620,14 +2649,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..d3c7e3f 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)
+                    *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,10 @@ agent_public_key_from_file (ctrl_t ctrl,
   (void)ctrl;

   *result = NULL;
+  if (r_ssh_cert)
+    *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 +1211,11 @@ agent_public_key_from_file (ctrl_t ctrl,
                              array, DIM (array), &curve, &flags);
   if (err)
     {
+      if (r_ssh_cert)
+	{
+	  xfree (*r_ssh_cert);
+	  *r_ssh_cert = NULL;
+	}
       gcry_sexp_release (s_skey);
       return err;
     }
@@ -1221,6 +1247,11 @@ agent_public_key_from_file (ctrl_t ctrl,
   format = xtrymalloc (15+4+7*npkey+10+15+1+1);
   if (!format)
     {
+      if (r_ssh_cert)
+        {
+          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 +1306,14 @@ agent_public_key_from_file (ctrl_t ctrl,

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

@@ -1324,7 +1363,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 +1444,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