gpg: When comparing keyids, use the keyid, not the fingerprint's suffix.
[gnupg.git] / tools / gpgkey2ssh.c
1 /* gpgkey2ssh.c - Converter  (Debug helper)
2  *      Copyright (C) 2005 Free Software Foundation, Inc.
3  *
4  * This file is part of GnuPG.
5  *
6  * GnuPG is free software; you can redistribute it and/or modify it
7  * under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * GnuPG is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
13  * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
14  * License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, see <http://www.gnu.org/licenses/>.
18  */
19
20 /* 
21    FIXME:  This tool needs some cleanup:
22
23    - Do not use assert() for error output.
24    - Add proper option parsing and standard options.
25    - retrieve_key_material needs to take the ordinal at field 1 in account.
26    0 Write a man page.
27 */
28
29 #include <config.h>
30
31 #include <gcrypt.h>
32 #include <unistd.h>
33 #include <stdlib.h>
34 #include <assert.h>
35 #include <stdio.h>
36 #include <errno.h>
37
38 #include "util.h"
39 #include "sysutils.h"
40
41 \f
42
43 typedef struct pkdbuf
44 {
45   unsigned char *buffer;
46   size_t buffer_n;
47 } pkdbuf_t;
48
49 \f
50
51 /* Retrieve the public key material for the RSA key, whose fingerprint
52    is FPR, from gpg output, which can be read through the stream FP.
53    The RSA modulus will be stored at the address of M and MLEN, the
54    public exponent at E and ELEN.  Returns zero on success, an error
55    code on failure.  Caller must release the allocated buffers at M
56    and E if the function returns success.  */
57 static gpg_error_t
58 retrieve_key_material (FILE *fp, const char *hexkeyid, int *algorithm_id,
59                        pkdbuf_t **pkdbuf, size_t *pkdbuf_n)
60 {
61   pkdbuf_t *pkdbuf_new;
62   pkdbuf_t *pkdbuf_tmp;
63   size_t pkdbuf_new_n;
64   gcry_error_t err = 0;
65   char *line = NULL;    /* read_line() buffer. */
66   size_t line_size = 0; /* Helper for for read_line. */
67   int found_key = 0;    /* Helper to find a matching key. */
68   int id;
69   unsigned char *buffer;
70   size_t buffer_n;
71   int i;
72
73   pkdbuf_new = NULL;
74   pkdbuf_new_n = 0;
75   id = 0;
76
77   /* Loop over all records until we have found the subkey
78      corresponding to the fingerprint. In general the first record
79      should be the pub record, but we don't rely on that.  Given that
80      we only need to look at one key, it is sufficient to compare the
81      keyid so that we don't need to look at "fpr" records. */
82   for (;;)
83     {
84       char *p;
85       char *fields[6];
86       int nfields;
87       size_t max_length;
88       gcry_mpi_t mpi;
89
90       max_length = 4096;
91       i = read_line (fp, &line, &line_size, &max_length);
92       if (!i)
93         break; /* EOF. */
94       if (i < 0)
95         {
96           err = gpg_error_from_syserror ();
97           goto leave; /* Error. */
98         }
99       if (!max_length)
100         {
101           err = gpg_error (GPG_ERR_TRUNCATED);
102           goto leave;  /* Line truncated - we better stop processing.  */
103         }
104
105       /* Parse the line into fields. */
106       for (nfields=0, p=line; p && nfields < DIM (fields); nfields++)
107         {
108           fields[nfields] = p;
109           p = strchr (p, ':');
110           if (p)
111             *(p++) = 0;
112         }
113       if (!nfields)
114         continue; /* No fields at all - skip line.  */
115
116       if (!found_key)
117         {
118           if ( (!strcmp (fields[0], "sub") || !strcmp (fields[0], "pub") )
119                && nfields > 4 &&
120                (((strlen (hexkeyid) == 8)
121                  && (strlen (fields[4]) == 16)
122                  && (! strcmp (fields[4] + 8, hexkeyid)))
123                 || ((strlen (hexkeyid) == 16)
124                     && (! strcmp (fields[4], hexkeyid)))))
125             {
126               found_key = 1;
127               /* Save algorithm ID.  */
128               id = atoi (fields[3]);
129             }
130           continue;
131         }
132       
133       if ( !strcmp (fields[0], "sub") || !strcmp (fields[0], "pub") )
134         break; /* Next key - stop.  */
135
136       if ( strcmp (fields[0], "pkd") )
137         continue; /* Not a key data record.  */
138
139       /* FIXME, necessary?  */
140
141       i = atoi (fields[1]);
142       if ((nfields < 4) || (i < 0))
143         {
144           err = gpg_error (GPG_ERR_GENERAL);
145           goto leave;
146         }
147
148       err = gcry_mpi_scan (&mpi, GCRYMPI_FMT_HEX, fields[3], 0, NULL);
149       if (err)
150         mpi = NULL;
151
152       err = gcry_mpi_aprint (GCRYMPI_FMT_STD, &buffer, &buffer_n, mpi);
153       gcry_mpi_release (mpi);
154       if (err)
155         goto leave;
156
157       pkdbuf_tmp = xrealloc (pkdbuf_new, sizeof (*pkdbuf_new) * (pkdbuf_new_n + 1));
158       if (pkdbuf_new != pkdbuf_tmp)
159         pkdbuf_new = pkdbuf_tmp;
160       pkdbuf_new[pkdbuf_new_n].buffer = buffer;
161       pkdbuf_new[pkdbuf_new_n].buffer_n = buffer_n;
162       pkdbuf_new_n++;
163     }
164
165   *algorithm_id = id;
166   *pkdbuf = pkdbuf_new;
167   *pkdbuf_n = pkdbuf_new_n;
168
169  leave:
170
171   if (err)
172     if (pkdbuf_new)
173       {
174         for (i = 0; i < pkdbuf_new_n; i++)
175           xfree (pkdbuf_new[i].buffer);
176         xfree (pkdbuf_new);
177       }
178   xfree (line);
179
180   return err;
181 }
182
183 \f
184
185 int
186 key_to_blob (unsigned char **blob, size_t *blob_n, const char *identifier, ...)
187 {
188   unsigned char *blob_new;
189   size_t blob_new_n;
190   unsigned char uint32_buffer[4];
191   u32 identifier_n;
192   FILE *stream;
193   va_list ap;
194   int ret;
195   pkdbuf_t *pkd;
196
197   stream = gnupg_tmpfile ();
198   assert (stream);
199
200   identifier_n = strlen (identifier);
201   uint32_buffer[0] = identifier_n >> 24;
202   uint32_buffer[1] = identifier_n >> 16;
203   uint32_buffer[2] = identifier_n >>  8;
204   uint32_buffer[3] = identifier_n >>  0;
205   ret = fwrite (uint32_buffer, sizeof (uint32_buffer), 1, stream);
206   assert (ret == 1);
207   ret = fwrite (identifier, identifier_n, 1, stream);
208   assert (ret == 1);
209
210   va_start (ap, identifier);
211   while (1)
212     {
213       pkd = va_arg (ap, pkdbuf_t *);
214       if (! pkd)
215         break;
216
217       uint32_buffer[0] = pkd->buffer_n >> 24;
218       uint32_buffer[1] = pkd->buffer_n >> 16;
219       uint32_buffer[2] = pkd->buffer_n >>  8;
220       uint32_buffer[3] = pkd->buffer_n >>  0;
221       ret = fwrite (uint32_buffer, sizeof (uint32_buffer), 1, stream);
222       assert (ret == 1);
223       ret = fwrite (pkd->buffer, pkd->buffer_n, 1, stream);
224       assert (ret == 1);
225     }
226
227   va_end (ap);
228
229   blob_new_n = ftell (stream);
230   rewind (stream);
231
232   blob_new = xmalloc (blob_new_n);
233   ret = fread (blob_new, blob_new_n, 1, stream);
234   assert (ret == 1);
235
236   *blob = blob_new;
237   *blob_n = blob_new_n;
238
239   fclose (stream);
240
241   return 0;
242 }
243
244 int
245 main (int argc, char **argv)
246 {
247   const char *keyid;
248   int algorithm_id;
249   pkdbuf_t *pkdbuf;
250   size_t pkdbuf_n;
251   char *command = NULL;
252   FILE *fp;
253   int ret;
254   gcry_error_t err;
255   unsigned char *blob;
256   size_t blob_n;
257   struct b64state b64_state;
258   const char *identifier;
259
260   pkdbuf = NULL;
261   pkdbuf_n = 0;
262
263   algorithm_id = 0;  /* (avoid cc warning) */
264   identifier = NULL; /* (avoid cc warning) */
265
266   if (argc != 2)
267     {
268       fprintf (stderr, "Usage: %s KEYID\n", argv[0]);
269       exit (1);
270     }
271   if (strcmp (argv[1], "--help") == 0)
272     {
273       fprintf (stderr, "Usage: %s KEYID\n", argv[0]);
274       fprintf (stderr, "\n");
275       fprintf (stderr,
276                "Convert a gpg key to a format appropriate for inclusion in an\n"
277                "ssh authorized_keys file.\n");
278       exit (0);
279     }
280
281   keyid = argv[1];
282
283   asprintf (&command,
284             "gpg2 --list-keys --with-colons --with-key-data '%s'",
285             keyid);
286   if (! command)
287     {
288       fprintf (stderr, "Out of memory.\n");
289       exit (1);
290     }
291
292   fp = popen (command, "r");
293   if (! fp)
294     {
295       fprintf (stderr, "Failed to running: '%s'\n", command);
296       exit (1);
297     }
298
299   err = retrieve_key_material (fp, keyid, &algorithm_id, &pkdbuf, &pkdbuf_n);
300   if (err)
301     {
302       fprintf (stderr, "Error looking up key: %s\n", gpg_strerror (err));
303       exit (1);
304     }
305   if (! ((algorithm_id == 1) || (algorithm_id == 17)))
306     {
307       fprintf (stderr, "Unsupported algorithm: %d\n", algorithm_id);
308       exit (1);
309     }
310
311   if (algorithm_id == 1)
312     {
313       identifier = "ssh-rsa";
314       ret = key_to_blob (&blob, &blob_n, identifier,
315                          &pkdbuf[1], &pkdbuf[0], NULL);
316     }
317   else if (algorithm_id == 17)
318     {
319       identifier = "ssh-dss";
320       ret = key_to_blob (&blob, &blob_n, identifier,
321                          &pkdbuf[0], &pkdbuf[1], &pkdbuf[2], &pkdbuf[3], NULL);
322     }
323   assert (! ret);
324
325   printf ("%s ", identifier);
326
327   err = b64enc_start (&b64_state, stdout, "");
328   assert (! err);
329   err = b64enc_write (&b64_state, blob, blob_n);
330   assert (! err);
331   err = b64enc_finish (&b64_state);
332   assert (! err);
333
334   printf (" COMMENT\n");
335
336   return 0;
337 }