tsick.c - sick - sign and check files using ed25519
(HTM) git clone git://z3bra.org/sick
(DIR) Log
(DIR) Files
(DIR) Refs
(DIR) README
(DIR) LICENSE
---
tsick.c (11437B)
---
1 /* See LICENSE file for copyright and license details. */
2 #include <dirent.h>
3 #include <limits.h>
4 #include <stdio.h>
5 #include <stdlib.h>
6 #include <string.h>
7 #include <sys/types.h>
8
9 #include "arg.h"
10 #include "base64.h"
11 #include "crypto_api.h"
12
13 #define SIGBEGIN "-----BEGIN ED25519 SIGNATURE-----\n"
14 #define SIGEND "-----END ED25519 SIGNATURE-----\n"
15
16 enum {
17 ACT_NONE,
18 ACT_SIGN,
19 ACT_CHCK,
20 ACT_TRIM
21 };
22
23 enum {
24 ERR_NOKEY = 1,
25 ERR_NOSIG = 2,
26 ERR_NOMSG = 3,
27 ERR_NORING = 4
28 };
29
30 static void usage();
31 static char *memstr(const void *h0, size_t k, const char *n0, size_t l);
32 static size_t bufferize(unsigned char **buf, FILE *fp);
33 static size_t extractmsg(unsigned char *msg[], unsigned char *buf, size_t len);
34 static size_t extractsig(unsigned char *sig[], unsigned char *buf, size_t len);
35 static int createkeypair(const char *);
36 static int readkey(FILE *, unsigned char **);
37 static int check_keyring(unsigned char *sig, unsigned char *msg, size_t len);
38 static int sign(FILE *fp, FILE *key);
39 static int check(FILE *fp, FILE *key);
40 static int trimsig(FILE *fp);
41
42 char *argv0;
43 static int verbose = 0;
44 static int armored = 0;
45
46 static void
47 usage()
48 {
49 fprintf(stderr, "usage: %s [-astv] [-g ALIAS] [-f KEY] [FILE]\n",
50 argv0);
51 exit(EXIT_FAILURE);
52 }
53
54 /*
55 * Find a string within a memory chunk, stupid style!
56 * Search is done backward, as the signature will be appended at the
57 * end of the stream.
58 */
59 char *
60 memstr(const void *h0, size_t k, const char *n0, size_t l)
61 {
62 ssize_t i;
63 const unsigned char *h = h0;
64
65 /* Return immediately on empty needle */
66 if (!l) return (char *)h;
67
68 /* Return immediately when needle is longer than haystack */
69 if (k<l) return 0;
70
71 for (i=k-l; i>0; i--) {
72 if (memcmp(h+i, n0, l) == 0)
73 return (char *)(h+i);
74 }
75 return NULL;
76 }
77
78 /*
79 * read chunks of data from a stream into a buffer, and return the size of the
80 * buffer
81 */
82 static size_t
83 bufferize(unsigned char **buf, FILE *fp)
84 {
85 size_t n, len = 0;
86 unsigned char chunk[_POSIX_MAX_INPUT], *tmp;
87
88 /*
89 * For each chunk read, reallocate the buffer size to fit the newly
90 * read data, and copy it over
91 */
92 while (!feof(fp) && (n = fread(chunk, 1, _POSIX_MAX_INPUT, fp)) > 0) {
93 if ((tmp = realloc(*buf, len + n)) == NULL) {
94 free(*buf);
95 *buf = NULL;
96 return 0;
97 }
98
99 *buf = tmp;
100 memcpy((*buf) + len, chunk, n);
101 len += n;
102 }
103
104 return len;
105 }
106
107 /*
108 * Copy the full content of the buffer, minus the signature to the given
109 * pointer
110 */
111 static size_t
112 extractmsg(unsigned char **msg, unsigned char *buf, size_t buflen)
113 {
114 size_t len = 0;
115 unsigned char *sig;
116
117 if (armored) {
118 /* signature start is identified by SIGBEGIN */
119 sig = (unsigned char *) memstr(buf, buflen, SIGBEGIN, strlen(SIGBEGIN));
120 } else {
121 /* signatures are 64 bytes long, appended to the stream */
122 sig = buf + buflen - 64;
123 }
124
125 /* if signature is not found, return the whole buffer */
126 if (sig == NULL) {
127 len = buflen;
128 } else {
129 len = sig - buf;
130 }
131
132 *msg = malloc(len);
133 memcpy(*msg, buf, len);
134
135 return len;
136 }
137
138 /*
139 * Copy the signature at the end of the buffer to the given pointer
140 */
141 static size_t
142 extractsig(unsigned char **sig, unsigned char *buf, size_t len)
143 {
144 off_t i;
145 size_t n, siglen = 0;
146 unsigned char *begin, *end, *tmp;
147 unsigned char base64[76];
148
149 if (armored) {
150 /* search start and end strings for the signatures */
151 begin = (unsigned char *) memstr(buf, len, SIGBEGIN, strlen(SIGBEGIN)) + strlen(SIGBEGIN);
152 end = (unsigned char *) memstr(buf, len, SIGEND, strlen(SIGEND));
153 if (!(begin && end) || end != (buf + len - strlen(SIGEND)))
154 return 0;
155 }
156
157 /* ed25519 signatures are 64 bytes longs */
158 *sig = malloc(64);
159 if (*sig == NULL)
160 return 0;
161
162 /* zero signature memory */
163 memset(*sig, 0, 64);
164
165 if (armored) {
166 /*
167 * base64 signature are wrapped at 76 chars.
168 * 76 being a multiple of 4, it means we can decode the signature in
169 * chunks of 76 bytes, and concatenate them together to get the
170 * original data.
171 */
172 for (i = 0; begin+i < end; i+=77) {
173 /*
174 * black magic pointer arithmetic there..
175 * if we reached the "end" pointer, it means we're at the end
176 * of the signature.
177 * The line length is either 76 bytes long, or less for the
178 * last line. In the latter case, the trailing \n must be
179 * trimmed before decoding.
180 */
181 n = begin+i+76 < end ? 76 : end - (begin + i) - 1;
182 memset(base64, 0, 76);
183 memcpy(base64, begin+i, n);
184
185 n = base64_decode((char **)&tmp, base64, n);
186 memcpy((*sig) + siglen, tmp, n);
187 siglen += n;
188 free(tmp);
189 }
190 } else {
191 /* assume the signature is the last 64 bytes of the stream */
192 if (len > 64 && memcpy((*sig), buf + len - 64, 64))
193 siglen = 64;
194 }
195
196 return siglen;
197 }
198
199 /*
200 * Creates a set of ed25519 key pairs on disk.
201 */
202 static int
203 createkeypair(const char *alias)
204 {
205 size_t klen = 0;
206 FILE *fp = NULL;
207 char fn[PATH_MAX];
208 unsigned char pub[32], priv[64], base64[96], *buf = NULL;
209
210 crypto_sign_ed25519_keypair(pub, priv);
211
212 memset(fn, 0, PATH_MAX);
213 memset(base64, 0, sizeof(base64));
214
215 /* write public key to "<alias>.pub" */
216 snprintf(fn, sizeof(fn) - 4, "%s.pub", alias);
217 if (verbose)
218 fprintf(stderr, "Creating public key %s\n", fn);
219 if ((fp = fopen(fn, "w")) == NULL) {
220 perror(fn);
221 return -1;
222 }
223 klen = base64_encode((char **)&buf, pub, sizeof(pub));
224 memcpy(base64, buf, klen);
225 base64[klen] = '\n';
226 if (fwrite(base64, 1, klen+1, fp) < klen) {
227 fclose(fp);
228 perror(fn);
229 return -1;
230 }
231 fclose(fp);
232 free(buf);
233
234 /* write private key to "<alias>.key" */
235 snprintf(fn, sizeof(fn) - 4, "%s.key", alias);
236 if (verbose)
237 fprintf(stderr, "Creating private key %s\n", fn);
238 if ((fp = fopen(fn, "w")) == NULL) {
239 perror(fn);
240 return -1;
241 }
242 klen = base64_encode((char **)&buf, priv, sizeof(priv));
243 memcpy(base64, buf, klen);
244 base64[klen] = '\n';
245 if (fwrite(base64, 1, klen + 1, fp) < klen) {
246 fclose(fp);
247 perror(fn);
248 return -1;
249 }
250 fclose(fp);
251 free(buf);
252
253 return 0;
254 }
255
256 int
257 readkey(FILE *fp, unsigned char **k)
258 {
259 size_t len;
260 char b64[96];
261 len = fread(b64, 1, sizeof(b64), fp);
262 return base64_decode((char **)k, (unsigned char *)b64, len);
263 }
264
265 /*
266 * Buffer a data stream, sign it, and write the buffer + base64 encoded
267 * signature to stdout
268 */
269 int
270 sign(FILE *fp, FILE *key)
271 {
272 unsigned long long mlen, slen;
273 char *base64;
274 unsigned char sig[64], *priv = NULL, *msg = NULL, *buf = NULL;
275
276 if (key == NULL)
277 return ERR_NOKEY;
278
279 if (readkey(key, &priv) < 64)
280 return ERR_NOKEY;
281
282 mlen = bufferize(&msg, fp);
283 if (mlen == 0) {
284 free(priv);
285 return ERR_NOMSG;
286 }
287
288 if (verbose)
289 fprintf(stderr, "Signing stream (%llu bytes)\n", mlen);
290
291 buf = malloc(mlen + 64);
292
293 crypto_sign_ed25519(buf, &slen, msg, mlen, priv);
294 free(priv);
295
296 memcpy(sig, buf, 64);
297 free(buf);
298
299 /* write buffer to stdout .. */
300 fwrite(msg, 1, mlen, stdout);
301 free(msg);
302
303 if (armored) {
304 /* .. followed by the signature delimiter .. */
305 fwrite(SIGBEGIN, 1, strlen(SIGBEGIN), stdout);
306
307 /* .. then the base64 encoded signature .. */
308 mlen = base64_encode(&base64, sig, 64);
309 base64_fold(stdout, base64, mlen, 0);
310 free(base64);
311
312 /* .. and the final signature delimiter! */
313 fwrite(SIGEND, 1, strlen(SIGEND), stdout);
314 } else {
315 /* simply write the signature as-is */
316 fwrite(sig, 1, 64, stdout);
317 }
318
319 return 0;
320 }
321
322 /*
323 * Check a buffer against all files in the $KEYRING directory set in the
324 * environment.
325 */
326 static int
327 check_keyring(unsigned char *sig, unsigned char *msg, size_t len)
328 {
329 int ret = 0;
330 unsigned long long n = 0, buflen;
331 DIR *dirp = NULL;
332 FILE *key = NULL;
333 struct dirent *dt = NULL;
334 char *keyring = NULL, path[PATH_MAX];
335 unsigned char *pub;
336
337 /* get the keyring from the environment */
338 keyring = getenv("KEYRING");
339 if (keyring == NULL) {
340 if (verbose)
341 fprintf(stderr, "KEYRING not set\n");
342 return ERR_NORING;
343 }
344
345 dirp = opendir(keyring);
346 if (dirp == NULL) {
347 perror(keyring);
348 return ERR_NORING;
349 }
350
351 /* loop through all entries in the $KEYRING directory */
352 while ((dt = readdir(dirp)) != NULL) {
353 /* ignore entries that are not regular files */
354 if (dt->d_type != DT_REG)
355 continue;
356
357 /* ignore all entries that are not 32 bytes long */
358 if (dt->d_reclen != 32)
359 continue;
360
361 /* set public key file path and store its content */
362 n = strnlen(keyring, PATH_MAX);
363 memset(path, 0, PATH_MAX);
364 memcpy(path, keyring, n);
365 path[n] = '/';
366 memcpy(path+n+1, dt->d_name, dt->d_reclen);
367 if ((key = fopen(path, "r")) == NULL) {
368 perror(path);
369 continue;
370 }
371 if (readkey(key, &pub) < 32) {
372 perror(path);
373 fclose(key);
374 free(pub);
375 continue;
376 }
377
378 /* check message for the given public key */
379 ret += crypto_sign_ed25519_open(sig, &buflen, msg, len, pub) == -1 ? 0 : 1;
380 free(pub);
381 if (ret) {
382 if (verbose)
383 fprintf(stderr, "Key match: %s\n", path);
384 break;
385 }
386 }
387
388 closedir(dirp);
389 return !ret;
390 }
391
392 /*
393 * Check the given stream against the key provided. If the stream pointer
394 * supposed to hold the key is NULL, check the stream against all public keys
395 * located in the $KEYRING directory.
396 */
397 static int
398 check(FILE *fp, FILE *key)
399 {
400 int ret = 0;
401 unsigned long long len, dummylen;
402 unsigned char *pub, *sig, *msg, *buf = NULL, *dummybuf = NULL;
403
404 if ((len = bufferize(&buf, fp)) == 0)
405 return ERR_NOMSG;
406
407 if (verbose)
408 fprintf(stderr, "Extracting signature from input\n");
409
410 int slen = extractsig(&sig, buf, len);
411 if (slen != 64) {
412 if (verbose)
413 fprintf(stderr, "ERROR: No valid signature found (%d bytes)\n", slen);
414
415 free(buf);
416 return ERR_NOSIG;
417 }
418
419 if ((len = extractmsg(&msg, buf, len)) == 0) {
420 free(buf);
421 free(sig);
422 }
423
424 free(buf);
425
426 if (verbose)
427 fprintf(stderr, "Verifying stream (%llu bytes)\n", len);
428
429 if (key) {
430 if (readkey(key, &pub) != 32)
431 return ERR_NOKEY;
432
433 buf = malloc(len + 64);
434 memcpy(buf, sig, 64);
435 memcpy(buf + 64, msg, len);
436 dummybuf = malloc(len + 64);
437
438 ret = crypto_sign_ed25519_open(dummybuf, &dummylen, buf, len + 64, pub);
439 free(buf);
440 free(dummybuf);
441 } else {
442 ret = check_keyring(sig, msg, len);
443 }
444
445 /*
446 * if we're able to verify the signature, dump buffer's content to
447 * stdout
448 */
449 if (!ret)
450 fwrite(msg, 1, len, stdout);
451
452 if (verbose)
453 fprintf(stderr, "Stream check %s\n", ret ? "FAILED" : "OK");
454
455 free(msg);
456 free(sig);
457
458 return ret;
459 }
460
461 /*
462 * Remove a signature from a stream, and dump it to stdout
463 */
464 static int
465 trimsig(FILE *fp)
466 {
467 size_t len = 0;
468 unsigned char *msg = NULL, *buf = NULL;
469
470 len = bufferize(&buf, fp);
471 if (!buf)
472 return -1;
473
474 len = extractmsg(&msg, buf, len);
475 if (!msg) {
476 free(buf);
477 return ERR_NOMSG;
478 }
479
480 fwrite(msg, 1, len, stdout);
481
482 free(buf);
483 free(msg);
484
485 return 0;
486 }
487
488 int
489 main(int argc, char *argv[])
490 {
491 int ret = 0, action = ACT_CHCK;
492 FILE *key = NULL, *fp = NULL;
493
494 ARGBEGIN{
495 case 'a':
496 armored = 1;
497 break;
498 case 'f':
499 key = fopen(EARGF(usage()), "r");
500 break;
501 case 'g':
502 return createkeypair(EARGF(usage()));
503 break; /* NOTREACHED */
504 case 's':
505 action = ACT_SIGN;
506 break;
507 case 't':
508 action = ACT_TRIM;
509 break;
510 case 'v':
511 verbose = 1;
512 break;
513 default:
514 usage();
515 }ARGEND;
516
517 /* if no argument is provided, read stdin */
518 fp = argc ? fopen(*argv, "r") : stdin;
519
520 switch (action) {
521 case ACT_SIGN:
522 ret |= sign(fp, key);
523 break;
524 case ACT_CHCK:
525 ret |= check(fp, key);
526 break;
527 case ACT_TRIM:
528 ret |= trimsig(fp);
529 break;
530 }
531
532 fclose(fp);
533 if (key)
534 fclose(key);
535
536 return ret;
537 }