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