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 }