Major openpgp2ssh overhaul. It's an unforgiving and brittle tool, but
[monkeysphere.git] / src / keytrans / gnutls-helpers.c
1 /* Author: Daniel Kahn Gillmor <dkg@fifthhorseman.net> */
2 /* Date: Fri, 04 Apr 2008 19:31:16 -0400 */
3 /* License: GPL v3 or later */
4
5 #include "gnutls-helpers.h"
6 /* for htonl() */
7 #include <arpa/inet.h>
8
9 /* for setlocale() */
10 #include <locale.h>
11
12 /* for isalnum() */
13 #include <ctype.h>
14
15 /* for exit() */
16 #include <unistd.h>
17
18 int loglevel = 0;
19
20
21 void err(const char* fmt, ...) {
22   va_list ap;
23   va_start(ap, fmt);
24   vfprintf(stderr, fmt, ap);
25   va_end(ap);
26   fflush(stderr);
27 }
28
29 void logfunc(int level, const char* string) {
30   fprintf(stderr, "GnuTLS Logging (%d): %s\n", level, string);
31 }
32
33 void init_keyid(gnutls_openpgp_keyid_t keyid) {
34   memset(keyid, 'x', sizeof(gnutls_openpgp_keyid_t));
35 }
36
37
38
39 void make_keyid_printable(printable_keyid out, gnutls_openpgp_keyid_t keyid)
40 {
41   static const char hex[16] = "0123456789ABCDEF";
42   unsigned int kix = 0, outix = 0;
43   
44   while (kix < sizeof(gnutls_openpgp_keyid_t)) {
45     out[outix] = hex[(keyid[kix] >> 4) & 0x0f];
46     out[outix + 1] = hex[keyid[kix] & 0x0f];
47     kix++;
48     outix += 2;
49   }
50 }
51
52 unsigned char hex2bin(unsigned char x) {
53   if ((x >= '0') && (x <= '9')) 
54     return x - '0';
55   if ((x >= 'A') && (x <= 'F')) 
56     return 10 + x - 'A';
57   if ((x >= 'a') && (x <= 'f')) 
58     return 10 + x - 'a';
59   return 0xff;
60 }
61
62 void collapse_printable_keyid(gnutls_openpgp_keyid_t out, printable_keyid in) {
63   unsigned int pkix = 0, outkix = 0;
64   
65   while (pkix < sizeof(printable_keyid)) {
66     unsigned hi = hex2bin(in[pkix]);
67     unsigned lo = hex2bin(in[pkix + 1]);
68     if (hi == 0xff) {
69       err("character '%c' is not a hex char\n", in[pkix]);
70       exit(1);
71     }
72     if (lo == 0xff) {
73       err("character '%c' is not a hex char\n", in[pkix + 1]);
74       exit(1);
75     }
76     out[outkix] = lo | (hi << 4);
77
78     pkix += 2;
79     outkix++;
80   }
81 }
82
83 int convert_string_to_keyid(gnutls_openpgp_keyid_t out, const char* str) {
84   printable_keyid p;
85   int ret;
86
87   ret = convert_string_to_printable_keyid(p, str);
88   if (ret == 0) 
89     collapse_printable_keyid(out, p);
90   return ret;
91 }
92 int convert_string_to_printable_keyid(printable_keyid pkeyid, const char* str) {
93   int arglen, x;
94   arglen = 0;
95   x = 0;
96   while ((arglen <= sizeof(printable_keyid)) &&
97          (str[x] != '\0')) {
98     if (isxdigit(str[x])) {
99       if (arglen == sizeof(printable_keyid)) {
100         err("There are more than %d hex digits in the keyid '%s'\n", sizeof(printable_keyid), str);
101         return 1;
102       }
103       pkeyid[arglen] = str[x];
104       arglen++;
105     }
106     x++;
107   }
108   
109   if (arglen != sizeof(printable_keyid)) {
110     err("keyid '%s' is not %d hex digits in length\n", str, sizeof(printable_keyid));
111     return 1;
112   }
113   return 0;
114 }
115
116
117
118 int init_gnutls() {
119   const char* version = NULL;
120   const char* debug_string = NULL;
121   int ret;
122
123   if (ret = gnutls_global_init(), ret) {
124     err("Failed to do gnutls_global_init() (error: %d)\n", ret);
125     return 1;
126   }
127
128   version = gnutls_check_version(NULL);
129
130   if (version) 
131     err("gnutls version: %s\n", version);
132   else {
133     err("no version found!\n");
134     return 1;
135   }
136
137   if (debug_string = getenv("MONKEYSPHERE_DEBUG"), debug_string) {
138     loglevel = atoi(debug_string);
139     gnutls_global_set_log_function(logfunc);
140     
141     gnutls_global_set_log_level(loglevel);
142     err("set log level to %d\n", loglevel);
143   }
144   return 0;
145 }
146
147 void init_datum(gnutls_datum_t* d) {
148   d->data = NULL;
149   d->size = 0;
150 }
151 void copy_datum(gnutls_datum_t* dest, const gnutls_datum_t* src) {
152   dest->data = gnutls_realloc(dest->data, src->size);
153   dest->size = src->size;
154   memcpy(dest->data, src->data, src->size);
155 }
156 int compare_data(const gnutls_datum_t* a, const gnutls_datum_t* b) {
157   if (a->size > b->size) {
158     err("a is larger\n");
159     return 1;
160   }
161   if (a->size < b->size) {
162     err("b is larger\n");
163     return -1;
164   }
165   return memcmp(a->data, b->data, a->size);
166 }
167 void free_datum(gnutls_datum_t* d) {
168   gnutls_free(d->data);
169   d->data = NULL;
170   d->size = 0;
171 }
172
173 /* read the passed-in string, store in a single datum */
174 int set_datum_string(gnutls_datum_t* d, const char* s) {
175   unsigned int x = strlen(s)+1;
176   unsigned char* c = NULL;
177
178   c = gnutls_realloc(d->data, x);
179   if (NULL == c)
180     return -1;
181   d->data = c;
182   d->size = x;
183   memcpy(d->data, s, x);
184   return 0;
185 }
186
187 /* read the passed-in file descriptor until EOF, store in a single
188    datum */
189 int set_datum_fd(gnutls_datum_t* d, int fd) {
190   unsigned int bufsize = 1024;
191   unsigned int len = 0;
192
193   FILE* f = fdopen(fd, "r");
194   if (bufsize > d->size) {
195     bufsize = 1024;
196     d->data = gnutls_realloc(d->data, bufsize);
197     if (d->data == NULL) {
198       err("out of memory!\n");
199       return -1;
200     }
201     d->size = bufsize;
202   } else {
203     bufsize = d->size;
204   }
205   f = fdopen(fd, "r");
206   if (NULL == f) {
207     err("could not fdopen FD %d\n", fd);
208   }
209   clearerr(f);
210   while (!feof(f) && !ferror(f)) { 
211     if (len == bufsize) {
212       /* allocate more space by doubling: */
213       bufsize *= 2;
214       d->data = gnutls_realloc(d->data, bufsize);
215       if (d->data == NULL) {
216         err("out of memory!\n"); 
217         return -1;
218       };
219       d->size = bufsize;
220     }
221     len += fread(d->data + len, 1, bufsize - len, f);
222     /*     err("read %d bytes\n", len); */
223   }
224   if (ferror(f)) {
225     err("Error reading from fd %d (error: %d) (error: %d '%s')\n", fd, ferror(f), errno, strerror(errno));
226     return -1;
227   }
228     
229   /* touch up buffer size to match reality: */
230   d->data = gnutls_realloc(d->data, len);
231   d->size = len;
232   return 0;
233 }
234
235 /* read the file indicated (by name) in the fname parameter.  store
236    its entire contents in a single datum. */
237 int set_datum_file(gnutls_datum_t* d, const char* fname) {
238   struct stat sbuf;
239   unsigned char* c = NULL;
240   FILE* file = NULL;
241   size_t x = 0;
242
243   if (0 != stat(fname, &sbuf)) {
244     err("failed to stat '%s'\n", fname);
245     return -1;
246   }
247   
248   c = gnutls_realloc(d->data, sbuf.st_size);
249   if (NULL == c) {
250     err("failed to allocate %d bytes for '%s'\n", sbuf.st_size, fname);
251     return -1;
252   }
253
254   d->data = c;
255   d->size = sbuf.st_size;
256   file = fopen(fname, "r");
257   if (NULL == file) {
258     err("failed to open '%s' for reading\n",  fname);
259     return -1;
260   }
261
262   x = fread(d->data, d->size, 1, file);
263   if (x != 1) {
264     err("tried to read %d bytes, read %d instead from '%s'\n", d->size, x, fname);
265     fclose(file);
266     return -1;
267   }
268   fclose(file);
269   return 0;
270 }
271
272 int write_datum_fd(int fd, const gnutls_datum_t* d) {
273   if (d->size != write(fd, d->data, d->size)) {
274     err("failed to write body of datum.\n");
275     return -1;
276   }
277   return 0;
278 }
279
280
281 int write_datum_fd_with_length(int fd, const gnutls_datum_t* d) {
282   uint32_t len;
283   int looks_negative = (d->data[0] & 0x80);
284   unsigned char zero = 0;
285
286   /* if the first bit is 1, then the datum will appear negative in the
287      MPI encoding style used by OpenSSH.  In that case, we'll increase
288      the length by one, and dump out one more byte */
289
290   if (looks_negative) {
291     len = htonl(d->size + 1);
292   } else {
293     len = htonl(d->size);
294   }
295   if (write(fd, &len, sizeof(len)) != sizeof(len)) {
296     err("failed to write size of datum.\n");
297     return -2;
298   }
299   if (looks_negative) {
300     if (write(fd, &zero, 1) != 1) {
301       err("failed to write padding byte for MPI.\n");
302       return -2;
303     }
304   }
305   return write_datum_fd(fd, d);
306 }
307
308 int write_data_fd_with_length(int fd, const gnutls_datum_t** d, unsigned int num) {
309   unsigned int i;
310   int ret;
311
312   for (i = 0; i < num; i++)
313     if (ret = write_datum_fd_with_length(fd, d[i]), ret != 0)
314       return ret;
315
316   return 0;
317 }
318
319
320 int datum_from_string(gnutls_datum_t* d, const char* str) {
321   d->size = strlen(str);
322   d->data = gnutls_realloc(d->data, d->size);
323   if (d->data == 0)
324     return ENOMEM;
325   memcpy(d->data, str, d->size);
326   return 0;
327 }
328
329
330 int create_writing_pipe(pid_t* pid, const char* path, char* const argv[]) {
331   int p[2];
332   int ret;
333
334   if (pid == NULL) {
335     err("bad pointer passed to create_writing_pipe()\n");
336     return -1;
337   }
338
339   if (ret = pipe(p), ret == -1) {
340     err("failed to create a pipe (error: %d \"%s\")\n", errno, strerror(errno));
341     return -1;
342   }
343
344   *pid = fork();
345   if (*pid == -1) {
346     err("Failed to fork (error: %d \"%s\")\n", errno, strerror(errno));
347     return -1;
348   }
349   if (*pid == 0) { /* this is the child */
350     close(p[1]); /* close unused write end */
351     
352     if (0 != dup2(p[0], 0)) { /* map the reading end into stdin */
353       err("Failed to transfer reading file descriptor to stdin (error: %d \"%s\")\n", errno, strerror(errno));
354       exit(1);
355     }
356     execv(path, argv);
357     err("exec %s failed (error: %d \"%s\")\n", path, errno, strerror(errno));
358     /* close the open file descriptors */
359     close(p[0]);
360     close(0);
361
362     exit(1);
363   } else { /* this is the parent */
364     close(p[0]); /* close unused read end */
365     return p[1];
366   }
367 }
368
369 int validate_ssh_host_userid(const char* userid) {
370   char* oldlocale = setlocale(LC_ALL, "C");
371   
372   /* choke if userid does not match the expected format
373      ("ssh://fully.qualified.domain.name") */
374   if (strncmp("ssh://", userid, strlen("ssh://")) != 0) {
375     err("The user ID should start with ssh:// for a host key\n");
376     goto fail;
377   }
378   /* so that isalnum will work properly */
379   userid += strlen("ssh://");
380   while (0 != (*userid)) {
381     if (!isalnum(*userid)) {
382       err("label did not start with a letter or a digit! (%s)\n", userid);
383       goto fail;
384     }
385     userid++;
386     while (isalnum(*userid) || ('-' == (*userid)))
387       userid++;
388     if (('.' == (*userid)) || (0 == (*userid))) { /* clean end of label:
389                                                  check last char
390                                                  isalnum */
391       if (!isalnum(*(userid - 1))) {
392         err("label did not end with a letter or a digit!\n");
393         goto fail;
394       }
395       if ('.' == (*userid)) /* advance to the start of the next label */
396         userid++;
397     } else {
398       err("invalid character in domain name: %c\n", *userid);
399       goto fail;
400     }
401   }
402   /* ensure that the last character is valid: */
403   if (!isalnum(*(userid - 1))) {
404     err("hostname did not end with a letter or a digit!\n");
405     goto fail;
406   }
407   /* FIXME: fqdn's can be unicode now, thanks to RFC 3490 -- how do we
408      make sure that we've got an OK string? */
409
410   return 0;
411
412  fail:
413   setlocale(LC_ALL, oldlocale);
414   return 1;
415 }
416
417 /* http://tools.ietf.org/html/rfc4880#section-5.5.2 */
418 size_t get_openpgp_mpi_size(gnutls_datum_t* d) {
419   return 2 + d->size;
420 }
421
422 int write_openpgp_mpi_to_fd(int fd, gnutls_datum_t* d) {
423   uint16_t x;
424
425   x = d->size * 8;
426   x = htons(x);
427   
428   write(fd, &x, sizeof(x));
429   write(fd, d->data, d->size);
430   
431   return 0;
432 }