keytrans cleanup: getting rid of unimplemented/useless code.
[monkeysphere.git] / src / keytrans / openpgp2ssh.c
1 #include "gnutls-helpers.h"
2
3 #include <gnutls/openpgp.h>
4 #include <gnutls/x509.h>
5
6 /* for waitpid() */
7 #include <sys/types.h>
8 #include <sys/wait.h>
9
10 /* 
11    Author: Daniel Kahn Gillmor <dkg@fifthhorseman.net>
12    Date: 2008-06-12 13:47:41-0400
13    License: GPL v3 or later
14
15    monkeysphere key translator: execute this with an OpenPGP key on
16    stdin, (please indicate the specific keyid that you want as the
17    first argument if there are subkeys).  At the moment, only public
18    keys and passphraseless secret keys work.
19
20    For secret keys, it will spit out a PEM-encoded version of the key
21    on stdout, which can be fed into ssh-add like this:
22
23     gpg --export-secret-keys $KEYID | openpgp2ssh $KEYID | ssh-add -c /dev/stdin
24
25    For public keys, it will spit out a single line of text that can
26    (with some massaging) be used in an openssh known_hosts or
27    authorized_keys file.  For example:
28
29     echo server.example.org $(gpg --export $KEYID | openpgp2ssh $KEYID) >> ~/.ssh/known_hosts
30
31    Requirements: I've only built this so far with GnuTLS v2.3.x.
32    GnuTLS 2.2.x does not contain the appropriate functionality.
33
34  */
35
36
37 /* FIXME: keyid should be const as well */
38 int convert_private_pgp_to_x509(gnutls_x509_privkey_t* output, const gnutls_openpgp_privkey_t* pgp_privkey, gnutls_openpgp_keyid_t* keyid) {
39   gnutls_datum_t m, e, d, p, q, u, g, y, x;
40   gnutls_pk_algorithm_t pgp_algo;
41   unsigned int pgp_bits;
42   int ret;
43
44 /* FIXME: actually respect keyid argument.  At the moment, we just
45    emit the primary key.  */
46
47   init_datum(&m);
48   init_datum(&e);
49   init_datum(&d);
50   init_datum(&p);
51   init_datum(&q);
52   init_datum(&u);
53   init_datum(&g);
54   init_datum(&y);
55   init_datum(&x);
56
57   pgp_algo = gnutls_openpgp_privkey_get_pk_algorithm(*pgp_privkey, &pgp_bits);
58   if (pgp_algo < 0) {
59     err("failed to get OpenPGP key algorithm (error: %d)\n", pgp_algo);
60     return 1;
61   }
62   if (pgp_algo == GNUTLS_PK_RSA) {
63     err("OpenPGP RSA Key, with %d bits\n", pgp_bits);
64     ret = gnutls_openpgp_privkey_export_rsa_raw(*pgp_privkey, &m, &e, &d, &p, &q, &u);
65     if (GNUTLS_E_SUCCESS != ret) {
66       err ("failed to export RSA key parameters (error: %d)\n", ret);
67       return 1;
68     }
69
70     ret = gnutls_x509_privkey_import_rsa_raw (*output, &m, &e, &d, &p, &q, &u); 
71     if (GNUTLS_E_SUCCESS != ret) {
72       err ("failed to import RSA key parameters (error: %d)\n", ret);
73       return 1;
74     }
75   } else if (pgp_algo == GNUTLS_PK_DSA) {
76     err("OpenPGP DSA Key, with %d bits\n", pgp_bits);
77     ret = gnutls_openpgp_privkey_export_dsa_raw(*pgp_privkey, &p, &q, &g, &y, &x);
78     if (GNUTLS_E_SUCCESS != ret) {
79       err ("failed to export DSA key parameters (error: %d)\n", ret);
80       return 1;
81     }
82
83     ret = gnutls_x509_privkey_import_dsa_raw (*output, &p, &q, &g, &y, &x); 
84     if (GNUTLS_E_SUCCESS != ret) {
85       err ("failed to import DSA key parameters (error: %d)\n", ret);
86       return 1;
87     }
88   } else {
89     err("OpenPGP Key was not RSA or DSA -- can't deal! (actual algorithm was: %d)\n", pgp_algo);
90     return 1;
91   }
92   
93   ret = gnutls_x509_privkey_fix(*output);
94   if (ret != 0) {
95     err("failed to fix up the private key in X.509 format (error: %d)\n", ret);
96     return 1; 
97   }
98
99   return 0;
100 }
101
102 /* FIXME: keyid should be const also */
103 int emit_public_openssh_from_pgp(const gnutls_openpgp_crt_t* pgp_crt, gnutls_openpgp_keyid_t* keyid) {
104   gnutls_openpgp_keyid_t curkeyid;
105   int ret;
106   int subkeyidx;
107   int subkeycount;
108   int found = 0;
109   gnutls_datum_t m, e, p, q, g, y, algolabel;
110   unsigned int bits;
111   gnutls_pk_algorithm_t algo;
112   const gnutls_datum_t* all[5];
113   const char* algoname;
114   int mpicount;
115   /* output_data must be at least 2 chars longer than the maximum possible
116      algorithm name: */
117   char output_data[20];
118
119   /* variables for the output conversion: */
120   int pipestatus;
121   int pipefd, child_pid;
122   char* const b64args[] = {"/usr/bin/base64", "--wrap=0", NULL};
123
124   init_datum(&m);
125   init_datum(&e);
126   init_datum(&p);
127   init_datum(&q);
128   init_datum(&g);
129   init_datum(&algolabel);
130
131
132   /* figure out if we've got the right thing: */
133   subkeycount = gnutls_openpgp_crt_get_subkey_count(*pgp_crt);
134   if (subkeycount < 0) {
135     err("Could not determine subkey count (got value %d)\n", subkeycount);
136     return 1;
137   }
138
139   if ((keyid == NULL) && 
140       (subkeycount > 0)) {
141     err("No keyid passed in, but there were %d keys to choose from\n", subkeycount + 1);
142     return 1;
143   }
144
145   if (keyid != NULL) {
146     ret = gnutls_openpgp_crt_get_key_id(*pgp_crt, curkeyid);
147     if (ret) {
148       err("Could not get keyid (error: %d)\n", ret);
149       return 1;
150     }
151   }
152   if ((keyid == NULL) || (memcmp(*keyid, curkeyid, sizeof(gnutls_openpgp_keyid_t)) == 0)) {
153     /* we want to export the primary key: */
154     err("exporting primary key\n");
155
156     /* FIXME: this is almost identical to the block below for subkeys.
157        This clumsiness seems inherent in the gnutls OpenPGP API,
158        though.  ugh. */
159     algo = gnutls_openpgp_crt_get_pk_algorithm(*pgp_crt, &bits);
160     if (algo < 0) {
161       err("failed to get the algorithm of the OpenPGP public key (error: %d)\n", algo);
162       return algo;
163     } else if (algo == GNUTLS_PK_RSA) {
164       err("OpenPGP RSA certificate, with %d bits\n", bits);
165       ret = gnutls_openpgp_crt_get_pk_rsa_raw(*pgp_crt, &m, &e);
166       if (GNUTLS_E_SUCCESS != ret) {
167         err ("failed to export RSA key parameters (error: %d)\n", ret);
168         return 1;
169       }
170     } else if (algo == GNUTLS_PK_DSA) {
171       err("OpenPGP DSA Key, with %d bits\n", bits);
172       ret = gnutls_openpgp_crt_get_pk_dsa_raw(*pgp_crt, &p, &q, &g, &y);
173       if (GNUTLS_E_SUCCESS != ret) {
174         err ("failed to export DSA key parameters (error: %d)\n", ret);
175         return 1;
176       }
177     }
178     found = 1;
179
180   } else {
181     /* lets trawl through the subkeys until we find the one we want: */
182     for (subkeyidx = 0; (subkeyidx < subkeycount) && !found; subkeyidx++) {
183       ret = gnutls_openpgp_crt_get_subkey_id(*pgp_crt, subkeyidx, curkeyid);
184       if (ret) {
185         err("Could not get keyid of subkey with index %d (error: %d)\n", subkeyidx, ret);
186         return 1;
187       }
188       if (memcmp(*keyid, curkeyid, sizeof(gnutls_openpgp_keyid_t)) == 0) {
189         err("exporting subkey index %d\n", subkeyidx);
190
191         /* FIXME: this is almost identical to the block above for the
192            primary key. */
193         algo = gnutls_openpgp_crt_get_subkey_pk_algorithm(*pgp_crt, subkeyidx, &bits);
194         if (algo < 0) {
195           err("failed to get the algorithm of the OpenPGP public key (error: %d)\n", algo);
196           return algo;
197         } else if (algo == GNUTLS_PK_RSA) {
198           err("OpenPGP RSA certificate, with %d bits\n", bits);
199           ret = gnutls_openpgp_crt_get_subkey_pk_rsa_raw(*pgp_crt, subkeyidx, &m, &e);
200           if (GNUTLS_E_SUCCESS != ret) {
201             err ("failed to export RSA key parameters (error: %d)\n", ret);
202             return 1;
203           }
204         } else if (algo == GNUTLS_PK_DSA) {
205           err("OpenPGP DSA Key, with %d bits\n", bits);
206           ret = gnutls_openpgp_crt_get_subkey_pk_dsa_raw(*pgp_crt, subkeyidx, &p, &q, &g, &y);
207           if (GNUTLS_E_SUCCESS != ret) {
208             err ("failed to export DSA key parameters (error: %d)\n", ret);
209             return 1;
210           }
211         }
212         found = 1;
213         
214       }
215     }
216   }
217
218   if (!found) {
219     err("Could not find key in input\n");
220     return 1;
221   }
222
223   /* if we made it this far, we've got MPIs, and we've got the
224      algorithm, so we just need to emit the info */
225   if (algo == GNUTLS_PK_RSA) {
226     algoname = "ssh-rsa";
227     mpicount = 3;
228
229     all[0] = &algolabel;
230     all[1] = &e;
231     all[2] = &m;
232   } else if (algo == GNUTLS_PK_DSA) {
233     algoname = "ssh-dss";
234     mpicount = 5;
235
236     all[0] = &algolabel;
237     all[1] = &p;
238     all[2] = &q;
239     all[3] = &g;
240     all[4] = &y;
241   } else {
242     err("Key algorithm was neither DSA nor RSA (it was %d).  Can't deal.  Sorry!\n", algo);
243     return 1;
244   }
245
246   if (ret = datum_from_string(&algolabel, algoname), ret) {
247     err("couldn't label string (error: %d)\n", ret);
248     return ret;
249   }
250
251   snprintf(output_data, sizeof(output_data), "%s ", algoname);
252
253   pipefd = create_writing_pipe(&child_pid, b64args[0], b64args);
254   if (pipefd < 0) {
255     err("failed to create a writing pipe (returned %d)\n", pipefd);
256     return pipefd;
257   }
258     
259   write(1, output_data, strlen(output_data));
260
261   if (0 != write_data_fd_with_length(pipefd, all, mpicount)) {
262     err("was not able to write out RSA key data\n");
263     return 1;
264   }
265   close(pipefd);
266   if (child_pid != waitpid(child_pid, &pipestatus, 0)) {
267     err("could not wait for child process to return for some reason.\n");
268     return 1;
269   }
270   if (pipestatus != 0) {
271     err("base64 pipe died with return code %d\n", pipestatus);
272     return pipestatus;
273   }
274
275   write(1, "\n", 1);
276
277   
278   return 0;
279 }
280
281 int main(int argc, char* argv[]) {
282   gnutls_datum_t data;
283   int ret;
284   gnutls_x509_privkey_t x509_privkey;
285   gnutls_openpgp_privkey_t pgp_privkey;
286   gnutls_openpgp_crt_t pgp_crt;
287
288   char output_data[10240];
289   size_t ods = sizeof(output_data);
290   
291   gnutls_openpgp_keyid_t keyid;
292   gnutls_openpgp_keyid_t* use_keyid;
293
294   init_gnutls();
295
296   /* figure out what keyid we should be looking for: */
297   use_keyid = NULL;
298   if (argv[1] != NULL) {
299     ret = convert_string_to_keyid(keyid, argv[1]);
300     if (ret != 0)
301       return ret;
302     use_keyid = &keyid;
303   }
304
305   
306   init_datum(&data);
307
308   /* slurp in the key from stdin */
309   if (ret = set_datum_fd(&data, 0), ret) {
310     err("didn't read file descriptor 0\n");
311     return 1;
312   }
313
314
315   if (ret = gnutls_openpgp_privkey_init(&pgp_privkey), ret) {
316     err("Failed to initialized OpenPGP private key (error: %d)\n", ret);
317     return 1;
318   }
319   /* check whether it's a private key or a public key, by trying them: */
320   if ((gnutls_openpgp_privkey_import(pgp_privkey, &data, GNUTLS_OPENPGP_FMT_RAW, NULL, 0) == 0) || 
321       (gnutls_openpgp_privkey_import(pgp_privkey, &data, GNUTLS_OPENPGP_FMT_BASE64, NULL, 0) == 0)) {
322     /* we're dealing with a private key */
323     err("Translating private key\n");
324     if (ret = gnutls_x509_privkey_init(&x509_privkey), ret) {
325       err("Failed to initialize X.509 private key for output (error: %d)\n", ret);
326       return 1;
327     }
328     
329     ret = convert_private_pgp_to_x509(&x509_privkey, &pgp_privkey, use_keyid);
330
331     gnutls_openpgp_privkey_deinit(pgp_privkey);
332     if (ret)
333       return ret;
334
335     ret = gnutls_x509_privkey_export (x509_privkey,
336                                       GNUTLS_X509_FMT_PEM,
337                                       output_data,
338                                       &ods);
339     if (ret == 0) {
340       write(1, output_data, ods);
341     }
342     gnutls_x509_privkey_deinit(x509_privkey);
343   
344   } else {
345     if (ret = gnutls_openpgp_crt_init(&pgp_crt), ret) {
346       err("Failed to initialized OpenPGP certificate (error: %d)\n", ret);
347       return 1;
348     }
349     
350     if ((gnutls_openpgp_crt_import(pgp_crt, &data, GNUTLS_OPENPGP_FMT_RAW) == 0) || 
351         (gnutls_openpgp_crt_import(pgp_crt, &data, GNUTLS_OPENPGP_FMT_BASE64) == 0)) {
352       /* we're dealing with a public key */
353       err("Translating public key\n");
354
355       ret = emit_public_openssh_from_pgp(&pgp_crt, use_keyid);
356       
357     } else {
358       /* we have no idea what kind of key this is at all anyway! */
359       err("Input does contain any form of OpenPGP key I recognize.");
360       return 1;
361     }
362   }
363
364
365
366
367   gnutls_global_deinit();
368   return 0;
369 }