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