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