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