added top-level Makefile for ease of building with debhelper.
[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   return 0;
278 }
279
280 int main(int argc, char* argv[]) {
281   gnutls_datum_t data;
282   int ret;
283   gnutls_x509_privkey_t x509_privkey;
284   gnutls_openpgp_privkey_t pgp_privkey;
285   gnutls_openpgp_crt_t pgp_crt;
286
287   char output_data[10240];
288   size_t ods = sizeof(output_data);
289   
290   gnutls_openpgp_keyid_t keyid;
291   gnutls_openpgp_keyid_t* use_keyid;
292
293   init_gnutls();
294
295   /* figure out what keyid we should be looking for: */
296   use_keyid = NULL;
297   if (argv[1] != NULL) {
298     ret = convert_string_to_keyid(keyid, argv[1]);
299     if (ret != 0)
300       return ret;
301     use_keyid = &keyid;
302   }
303
304   
305   init_datum(&data);
306
307   /* slurp in the key from stdin */
308   if (ret = set_datum_fd(&data, 0), ret) {
309     err("didn't read file descriptor 0\n");
310     return 1;
311   }
312
313
314   if (ret = gnutls_openpgp_privkey_init(&pgp_privkey), ret) {
315     err("Failed to initialized OpenPGP private key (error: %d)\n", ret);
316     return 1;
317   }
318   /* check whether it's a private key or a public key, by trying them: */
319   if ((gnutls_openpgp_privkey_import(pgp_privkey, &data, GNUTLS_OPENPGP_FMT_RAW, NULL, 0) == 0) || 
320       (gnutls_openpgp_privkey_import(pgp_privkey, &data, GNUTLS_OPENPGP_FMT_BASE64, NULL, 0) == 0)) {
321     /* we're dealing with a private key */
322     err("Translating private key\n");
323     if (ret = gnutls_x509_privkey_init(&x509_privkey), ret) {
324       err("Failed to initialize X.509 private key for output (error: %d)\n", ret);
325       return 1;
326     }
327     
328     ret = convert_private_pgp_to_x509(&x509_privkey, &pgp_privkey, use_keyid);
329
330     gnutls_openpgp_privkey_deinit(pgp_privkey);
331     if (ret)
332       return ret;
333
334     ret = gnutls_x509_privkey_export (x509_privkey,
335                                       GNUTLS_X509_FMT_PEM,
336                                       output_data,
337                                       &ods);
338     if (ret == 0) {
339       write(1, output_data, ods);
340     }
341     gnutls_x509_privkey_deinit(x509_privkey);
342   
343   } else {
344     if (ret = gnutls_openpgp_crt_init(&pgp_crt), ret) {
345       err("Failed to initialized OpenPGP certificate (error: %d)\n", ret);
346       return 1;
347     }
348     
349     if ((gnutls_openpgp_crt_import(pgp_crt, &data, GNUTLS_OPENPGP_FMT_RAW) == 0) || 
350         (gnutls_openpgp_crt_import(pgp_crt, &data, GNUTLS_OPENPGP_FMT_BASE64) == 0)) {
351       /* we're dealing with a public key */
352       err("Translating public key\n");
353
354       ret = emit_public_openssh_from_pgp(&pgp_crt, use_keyid);
355       
356     } else {
357       /* we have no idea what kind of key this is at all anyway! */
358       err("Input does contain any form of OpenPGP key I recognize.");
359       return 1;
360     }
361   }
362
363
364
365
366   gnutls_global_deinit();
367   return 0;
368 }