fix typo
[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   gnutls_openpgp_keyid_t curkeyid;
44   int subkeyidx;
45   int subkeycount;
46   int found = 0;
47
48   init_datum(&m);
49   init_datum(&e);
50   init_datum(&d);
51   init_datum(&p);
52   init_datum(&q);
53   init_datum(&u);
54   init_datum(&g);
55   init_datum(&y);
56   init_datum(&x);
57
58   subkeycount = gnutls_openpgp_privkey_get_subkey_count(*pgp_privkey);
59   if (subkeycount < 0) {
60     err(0,"Could not determine subkey count (got value %d)\n", subkeycount);
61     return 1;
62   }
63
64   if ((keyid == NULL) && 
65       (subkeycount > 0)) {
66     err(0,"No keyid passed in, but there were %d keys to choose from\n", subkeycount + 1);
67     return 1;
68   }
69
70   if (keyid != NULL) {
71     ret = gnutls_openpgp_privkey_get_key_id(*pgp_privkey, curkeyid);
72     if (ret) {
73       err(0,"Could not get keyid (error: %d)\n", ret);
74       return 1;
75     }
76   }
77   if ((keyid == NULL) || (memcmp(*keyid, curkeyid, sizeof(gnutls_openpgp_keyid_t)) == 0)) {
78     /* we want to export the primary key: */
79     err(0,"exporting primary key\n");
80
81     /* FIXME: this is almost identical to the block below for subkeys.
82        This clumsiness seems inherent in the gnutls OpenPGP API,
83        though.  ugh. */
84     pgp_algo = gnutls_openpgp_privkey_get_pk_algorithm(*pgp_privkey, &pgp_bits);
85     if (pgp_algo < 0) {
86       err(0, "failed to get OpenPGP key algorithm (error: %d)\n", pgp_algo);
87       return 1;
88     }
89     if (pgp_algo == GNUTLS_PK_RSA) {
90       err(0,"OpenPGP RSA Key, with %d bits\n", pgp_bits);
91       ret = gnutls_openpgp_privkey_export_rsa_raw(*pgp_privkey, &m, &e, &d, &p, &q, &u);
92       if (GNUTLS_E_SUCCESS != ret) {
93         err(0, "failed to export RSA key parameters (error: %d)\n", ret);
94         return 1;
95       }
96       
97     } else if (pgp_algo == GNUTLS_PK_DSA) {
98       err(0,"OpenPGP DSA Key, with %d bits\n", pgp_bits);
99       ret = gnutls_openpgp_privkey_export_dsa_raw(*pgp_privkey, &p, &q, &g, &y, &x);
100       if (GNUTLS_E_SUCCESS != ret) {
101         err(0,"failed to export DSA key parameters (error: %d)\n", ret);
102         return 1;
103       }
104     }
105     found = 1;
106   } else {
107     /* lets trawl through the subkeys until we find the one we want: */
108     for (subkeyidx = 0; (subkeyidx < subkeycount) && !found; subkeyidx++) {
109       ret = gnutls_openpgp_privkey_get_subkey_id(*pgp_privkey, subkeyidx, curkeyid);
110       if (ret) {
111         err(0,"Could not get keyid of subkey with index %d (error: %d)\n", subkeyidx, ret);
112         return 1;
113       }
114       if (memcmp(*keyid, curkeyid, sizeof(gnutls_openpgp_keyid_t)) == 0) {
115         err(0,"exporting subkey index %d\n", subkeyidx);
116
117         /* FIXME: this is almost identical to the block above for the
118            primary key. */
119         pgp_algo = gnutls_openpgp_privkey_get_subkey_pk_algorithm(*pgp_privkey, subkeyidx, &pgp_bits);
120         if (pgp_algo < 0) {
121           err(0,"failed to get the algorithm of the OpenPGP public key (error: %d)\n", pgp_algo);
122           return pgp_algo;
123         } else if (pgp_algo == GNUTLS_PK_RSA) {
124           err(0,"OpenPGP RSA key, with %d bits\n", pgp_bits);
125           ret = gnutls_openpgp_privkey_export_subkey_rsa_raw(*pgp_privkey, subkeyidx, &m, &e, &d, &p, &q, &u);
126           if (GNUTLS_E_SUCCESS != ret) {
127             err(0,"failed to export RSA key parameters (error: %d)\n", ret);
128             return 1;
129           }
130         } else if (pgp_algo == GNUTLS_PK_DSA) {
131           err(0,"OpenPGP DSA Key, with %d bits\n", pgp_bits);
132           ret = gnutls_openpgp_privkey_export_subkey_dsa_raw(*pgp_privkey, subkeyidx, &p, &q, &g, &y, &x);
133           if (GNUTLS_E_SUCCESS != ret) {
134             err(0,"failed to export DSA key parameters (error: %d)\n", ret);
135             return 1;
136           }
137         }
138         found = 1;
139       }
140     }
141   }
142
143   if (!found) {
144     err(0,"Could not find key in input\n");
145     return 1;
146   }
147
148   if (pgp_algo == GNUTLS_PK_RSA) {
149     ret = gnutls_x509_privkey_import_rsa_raw (*output, &m, &e, &d, &p, &q, &u); 
150     if (GNUTLS_E_SUCCESS != ret) {
151       err(0, "failed to import RSA key parameters (error: %d)\n", ret);
152       return 1;
153     }
154   } else if (pgp_algo == GNUTLS_PK_DSA) {
155     ret = gnutls_x509_privkey_import_dsa_raw (*output, &p, &q, &g, &y, &x); 
156     if (GNUTLS_E_SUCCESS != ret) {
157       err(0,"failed to import DSA key parameters (error: %d)\n", ret);
158       return 1;
159     }
160   } else {
161     err(0,"OpenPGP Key was not RSA or DSA -- can't deal! (actual algorithm was: %d)\n", pgp_algo);
162     return 1;
163   }
164   
165   ret = gnutls_x509_privkey_fix(*output);
166   if (ret != 0) {
167     err(0,"failed to fix up the private key in X.509 format (error: %d)\n", ret);
168     return 1; 
169   }
170
171   return 0;
172 }
173
174 /* FIXME: keyid should be const also */
175 int emit_public_openssh_from_pgp(const gnutls_openpgp_crt_t* pgp_crt, gnutls_openpgp_keyid_t* keyid) {
176   gnutls_openpgp_keyid_t curkeyid;
177   int ret;
178   int subkeyidx;
179   int subkeycount;
180   int found = 0;
181   gnutls_datum_t m, e, p, q, g, y, algolabel;
182   unsigned int bits;
183   gnutls_pk_algorithm_t algo;
184   const gnutls_datum_t* all[5];
185   const char* algoname;
186   int mpicount;
187   /* output_data must be at least 2 chars longer than the maximum possible
188      algorithm name: */
189   char output_data[20];
190
191   /* variables for the output conversion: */
192   int pipestatus;
193   int pipefd, child_pid;
194   char* const b64args[] = {"/usr/bin/base64", "--wrap=0", NULL};
195
196   init_datum(&m);
197   init_datum(&e);
198   init_datum(&p);
199   init_datum(&q);
200   init_datum(&g);
201   init_datum(&algolabel);
202
203
204   /* figure out if we've got the right thing: */
205   subkeycount = gnutls_openpgp_crt_get_subkey_count(*pgp_crt);
206   if (subkeycount < 0) {
207     err(0,"Could not determine subkey count (got value %d)\n", subkeycount);
208     return 1;
209   }
210
211   if ((keyid == NULL) && 
212       (subkeycount > 0)) {
213     err(0,"No keyid passed in, but there were %d keys to choose from\n", subkeycount + 1);
214     return 1;
215   }
216
217   if (keyid != NULL) {
218     ret = gnutls_openpgp_crt_get_key_id(*pgp_crt, curkeyid);
219     if (ret) {
220       err(0,"Could not get keyid (error: %d)\n", ret);
221       return 1;
222     }
223   }
224   if ((keyid == NULL) || (memcmp(*keyid, curkeyid, sizeof(gnutls_openpgp_keyid_t)) == 0)) {
225     /* we want to export the primary key: */
226     err(0,"exporting primary key\n");
227
228     /* FIXME: this is almost identical to the block below for subkeys.
229        This clumsiness seems inherent in the gnutls OpenPGP API,
230        though.  ugh. */
231     algo = gnutls_openpgp_crt_get_pk_algorithm(*pgp_crt, &bits);
232     if (algo < 0) {
233       err(0,"failed to get the algorithm of the OpenPGP public key (error: %d)\n", algo);
234       return algo;
235     } else if (algo == GNUTLS_PK_RSA) {
236       err(0,"OpenPGP RSA certificate, with %d bits\n", bits);
237       ret = gnutls_openpgp_crt_get_pk_rsa_raw(*pgp_crt, &m, &e);
238       if (GNUTLS_E_SUCCESS != ret) {
239         err(0,"failed to export RSA certificate parameters (error: %d)\n", ret);
240         return 1;
241       }
242     } else if (algo == GNUTLS_PK_DSA) {
243       err(0,"OpenPGP DSA certificate, with %d bits\n", bits);
244       ret = gnutls_openpgp_crt_get_pk_dsa_raw(*pgp_crt, &p, &q, &g, &y);
245       if (GNUTLS_E_SUCCESS != ret) {
246         err(0,"failed to export DSA certificate parameters (error: %d)\n", ret);
247         return 1;
248       }
249     }
250     found = 1;
251
252   } else {
253     /* lets trawl through the subkeys until we find the one we want: */
254     for (subkeyidx = 0; (subkeyidx < subkeycount) && !found; subkeyidx++) {
255       ret = gnutls_openpgp_crt_get_subkey_id(*pgp_crt, subkeyidx, curkeyid);
256       if (ret) {
257         err(0,"Could not get keyid of subkey with index %d (error: %d)\n", subkeyidx, ret);
258         return 1;
259       }
260       if (memcmp(*keyid, curkeyid, sizeof(gnutls_openpgp_keyid_t)) == 0) {
261         err(0,"exporting subkey index %d\n", subkeyidx);
262
263         /* FIXME: this is almost identical to the block above for the
264            primary key. */
265         algo = gnutls_openpgp_crt_get_subkey_pk_algorithm(*pgp_crt, subkeyidx, &bits);
266         if (algo < 0) {
267           err(0,"failed to get the algorithm of the OpenPGP public key (error: %d)\n", algo);
268           return algo;
269         } else if (algo == GNUTLS_PK_RSA) {
270           err(0,"OpenPGP RSA certificate, with %d bits\n", bits);
271           ret = gnutls_openpgp_crt_get_subkey_pk_rsa_raw(*pgp_crt, subkeyidx, &m, &e);
272           if (GNUTLS_E_SUCCESS != ret) {
273             err(0,"failed to export RSA certificate parameters (error: %d)\n", ret);
274             return 1;
275           }
276         } else if (algo == GNUTLS_PK_DSA) {
277           err(0,"OpenPGP DSA certificate, with %d bits\n", bits);
278           ret = gnutls_openpgp_crt_get_subkey_pk_dsa_raw(*pgp_crt, subkeyidx, &p, &q, &g, &y);
279           if (GNUTLS_E_SUCCESS != ret) {
280             err(0,"failed to export DSA certificate parameters (error: %d)\n", ret);
281             return 1;
282           }
283         }
284         found = 1;
285         
286       }
287     }
288   }
289
290   if (!found) {
291     err(0,"Could not find key in input\n");
292     return 1;
293   }
294
295   /* if we made it this far, we've got MPIs, and we've got the
296      algorithm, so we just need to emit the info */
297   if (algo == GNUTLS_PK_RSA) {
298     algoname = "ssh-rsa";
299     mpicount = 3;
300
301     all[0] = &algolabel;
302     all[1] = &e;
303     all[2] = &m;
304   } else if (algo == GNUTLS_PK_DSA) {
305     algoname = "ssh-dss";
306     mpicount = 5;
307
308     all[0] = &algolabel;
309     all[1] = &p;
310     all[2] = &q;
311     all[3] = &g;
312     all[4] = &y;
313   } else {
314     err(0,"Key algorithm was neither DSA nor RSA (it was %d).  Can't deal.  Sorry!\n", algo);
315     return 1;
316   }
317
318   if (ret = datum_from_string(&algolabel, algoname), ret) {
319     err(0,"couldn't label string (error: %d)\n", ret);
320     return ret;
321   }
322
323   snprintf(output_data, sizeof(output_data), "%s ", algoname);
324
325   pipefd = create_writing_pipe(&child_pid, b64args[0], b64args);
326   if (pipefd < 0) {
327     err(0,"failed to create a writing pipe (returned %d)\n", pipefd);
328     return pipefd;
329   }
330     
331   write(1, output_data, strlen(output_data));
332
333   if (0 != write_data_fd_with_length(pipefd, all, mpicount)) {
334     err(0,"was not able to write out RSA key data\n");
335     return 1;
336   }
337   close(pipefd);
338   if (child_pid != waitpid(child_pid, &pipestatus, 0)) {
339     err(0,"could not wait for child process to return for some reason.\n");
340     return 1;
341   }
342   if (pipestatus != 0) {
343     err(0,"base64 pipe died with return code %d\n", pipestatus);
344     return pipestatus;
345   }
346
347   write(1, "\n", 1);
348   
349   return 0;
350 }
351
352 int main(int argc, char* argv[]) {
353   gnutls_datum_t data;
354   int ret;
355   gnutls_x509_privkey_t x509_privkey;
356   gnutls_openpgp_privkey_t pgp_privkey;
357   gnutls_openpgp_crt_t pgp_crt;
358
359   char output_data[10240];
360   size_t ods = sizeof(output_data);
361   
362   gnutls_openpgp_keyid_t keyid;
363   gnutls_openpgp_keyid_t* use_keyid;
364
365   init_gnutls();
366
367   /* figure out what keyid we should be looking for: */
368   use_keyid = NULL;
369   if (argv[1] != NULL) {
370     ret = convert_string_to_keyid(keyid, argv[1]);
371     if (ret != 0)
372       return ret;
373     use_keyid = &keyid;
374   }
375
376   
377   init_datum(&data);
378
379   /* slurp in the key from stdin */
380   if (ret = set_datum_fd(&data, 0), ret) {
381     err(0,"didn't read file descriptor 0\n");
382     return 1;
383   }
384
385
386   if (ret = gnutls_openpgp_privkey_init(&pgp_privkey), ret) {
387     err(0,"Failed to initialized OpenPGP private key (error: %d)\n", ret);
388     return 1;
389   }
390   /* check whether it's a private key or a public key, by trying them: */
391   if ((gnutls_openpgp_privkey_import(pgp_privkey, &data, GNUTLS_OPENPGP_FMT_RAW, NULL, 0) == 0) || 
392       (gnutls_openpgp_privkey_import(pgp_privkey, &data, GNUTLS_OPENPGP_FMT_BASE64, NULL, 0) == 0)) {
393     /* we're dealing with a private key */
394     err(0,"Translating private key\n");
395     if (ret = gnutls_x509_privkey_init(&x509_privkey), ret) {
396       err(0,"Failed to initialize X.509 private key for output (error: %d)\n", ret);
397       return 1;
398     }
399     
400     ret = convert_private_pgp_to_x509(&x509_privkey, &pgp_privkey, use_keyid);
401
402     gnutls_openpgp_privkey_deinit(pgp_privkey);
403     if (ret)
404       return ret;
405
406     ret = gnutls_x509_privkey_export (x509_privkey,
407                                       GNUTLS_X509_FMT_PEM,
408                                       output_data,
409                                       &ods);
410     if (ret == 0) {
411       write(1, output_data, ods);
412     }
413     gnutls_x509_privkey_deinit(x509_privkey);
414   
415   } else {
416     if (ret = gnutls_openpgp_crt_init(&pgp_crt), ret) {
417       err(0,"Failed to initialized OpenPGP certificate (error: %d)\n", ret);
418       return 1;
419     }
420     
421     if ((gnutls_openpgp_crt_import(pgp_crt, &data, GNUTLS_OPENPGP_FMT_RAW) == 0) || 
422         (gnutls_openpgp_crt_import(pgp_crt, &data, GNUTLS_OPENPGP_FMT_BASE64) == 0)) {
423       /* we're dealing with a public key */
424       err(0,"Translating public key\n");
425
426       ret = emit_public_openssh_from_pgp(&pgp_crt, use_keyid);
427       
428     } else {
429       /* we have no idea what kind of key this is at all anyway! */
430       err(0,"Input does not contain any form of OpenPGP key I recognize.\n");
431       return 1;
432     }
433   }
434
435   gnutls_global_deinit();
436   return 0;
437 }