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