1*de861b93Stedu /* $OpenBSD: signify.c,v 1.114 2016/09/02 21:18:50 tedu Exp $ */ 24215a5deStedu /* 34215a5deStedu * Copyright (c) 2013 Ted Unangst <tedu@openbsd.org> 44215a5deStedu * 54215a5deStedu * Permission to use, copy, modify, and distribute this software for any 64215a5deStedu * purpose with or without fee is hereby granted, provided that the above 74215a5deStedu * copyright notice and this permission notice appear in all copies. 84215a5deStedu * 94215a5deStedu * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 104215a5deStedu * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 114215a5deStedu * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 124215a5deStedu * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 134215a5deStedu * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 144215a5deStedu * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 154215a5deStedu * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 164215a5deStedu */ 174215a5deStedu #include <sys/stat.h> 184215a5deStedu 194215a5deStedu #include <netinet/in.h> 204215a5deStedu #include <resolv.h> 214215a5deStedu 22f30a54aaStedu #include <limits.h> 234215a5deStedu #include <stdint.h> 244215a5deStedu #include <fcntl.h> 254215a5deStedu #include <string.h> 264215a5deStedu #include <stdio.h> 27ebde6afdStedu #include <stdlib.h> 285e4b5029Stedu #include <stddef.h> 295e4b5029Stedu #include <ohash.h> 304215a5deStedu #include <err.h> 314215a5deStedu #include <unistd.h> 324215a5deStedu #include <readpassphrase.h> 334215a5deStedu #include <util.h> 344215a5deStedu #include <sha2.h> 354215a5deStedu 364215a5deStedu #include "crypto_api.h" 373ccd7401Sespie #include "signify.h" 384215a5deStedu 394215a5deStedu #define SIGBYTES crypto_sign_ed25519_BYTES 404215a5deStedu #define SECRETBYTES crypto_sign_ed25519_SECRETKEYBYTES 414215a5deStedu #define PUBLICBYTES crypto_sign_ed25519_PUBLICKEYBYTES 424215a5deStedu 434215a5deStedu #define PKALG "Ed" 444215a5deStedu #define KDFALG "BK" 45534dbeadStedu #define KEYNUMLEN 8 461c9c770cStedu 471c9c770cStedu #define COMMENTHDR "untrusted comment: " 481453d2a0Stedu #define COMMENTHDRLEN 19 491453d2a0Stedu #define COMMENTMAXLEN 1024 500f212b48Stedu #define VERIFYWITH "verify with " 514215a5deStedu 524215a5deStedu struct enckey { 534215a5deStedu uint8_t pkalg[2]; 544215a5deStedu uint8_t kdfalg[2]; 554215a5deStedu uint32_t kdfrounds; 564215a5deStedu uint8_t salt[16]; 574215a5deStedu uint8_t checksum[8]; 58534dbeadStedu uint8_t keynum[KEYNUMLEN]; 594215a5deStedu uint8_t seckey[SECRETBYTES]; 604215a5deStedu }; 614215a5deStedu 624215a5deStedu struct pubkey { 634215a5deStedu uint8_t pkalg[2]; 64534dbeadStedu uint8_t keynum[KEYNUMLEN]; 654215a5deStedu uint8_t pubkey[PUBLICBYTES]; 664215a5deStedu }; 674215a5deStedu 684215a5deStedu struct sig { 694215a5deStedu uint8_t pkalg[2]; 70534dbeadStedu uint8_t keynum[KEYNUMLEN]; 714215a5deStedu uint8_t sig[SIGBYTES]; 724215a5deStedu }; 734215a5deStedu 748155533aStedu static void __dead 75f2adbe28Stedu usage(const char *error) 764215a5deStedu { 77f2adbe28Stedu if (error) 78f2adbe28Stedu fprintf(stderr, "%s\n", error); 7942efb9f2Sespie fprintf(stderr, "usage:" 8035e4c3d2Sespie #ifndef VERIFYONLY 81c374df80Snaddy "\t%1$s -C [-q] -p pubkey -x sigfile [file ...]\n" 82f2adbe28Stedu "\t%1$s -G [-n] [-c comment] -p pubkey -s seckey\n" 833ccd7401Sespie "\t%1$s -S [-ez] [-x sigfile] -s seckey -m message\n" 8435e4c3d2Sespie #endif 85915581b0Sespie "\t%1$s -V [-eqz] [-p pubkey] [-t keytype] [-x sigfile] -m message\n", 863301e1c6Stedu getprogname()); 874215a5deStedu exit(1); 884215a5deStedu } 894215a5deStedu 903ccd7401Sespie int 918e162516Sderaadt xopen(const char *fname, int oflags, mode_t mode) 924215a5deStedu { 93ead0b14bStedu struct stat sb; 944215a5deStedu int fd; 954215a5deStedu 96f2adbe28Stedu if (strcmp(fname, "-") == 0) { 978e162516Sderaadt if ((oflags & O_WRONLY)) 98f2adbe28Stedu fd = dup(STDOUT_FILENO); 99f2adbe28Stedu else 100f2adbe28Stedu fd = dup(STDIN_FILENO); 101f2adbe28Stedu if (fd == -1) 102f2adbe28Stedu err(1, "dup failed"); 103f2adbe28Stedu } else { 1048e162516Sderaadt fd = open(fname, oflags, mode); 1054215a5deStedu if (fd == -1) 106f2adbe28Stedu err(1, "can't open %s for %s", fname, 1078e162516Sderaadt (oflags & O_WRONLY) ? "writing" : "reading"); 108f2adbe28Stedu } 109ead0b14bStedu if (fstat(fd, &sb) == -1 || S_ISDIR(sb.st_mode)) 1105d586c2bStedu errx(1, "not a valid file: %s", fname); 1114215a5deStedu return fd; 1124215a5deStedu } 1134215a5deStedu 1143ccd7401Sespie void * 1154215a5deStedu xmalloc(size_t len) 1164215a5deStedu { 1174215a5deStedu void *p; 1184215a5deStedu 119dba2cf70Stedu if (!(p = malloc(len))) 1204215a5deStedu err(1, "malloc %zu", len); 1214215a5deStedu return p; 1224215a5deStedu } 1234215a5deStedu 12427f66874Stedu static size_t 1250ce08f52Stedu parseb64file(const char *filename, char *b64, void *buf, size_t buflen, 1261453d2a0Stedu char *comment) 12727f66874Stedu { 12827f66874Stedu char *commentend, *b64end; 12927f66874Stedu 13027f66874Stedu commentend = strchr(b64, '\n'); 13127f66874Stedu if (!commentend || commentend - b64 <= COMMENTHDRLEN || 1327d7c2057Stedu memcmp(b64, COMMENTHDR, COMMENTHDRLEN) != 0) 13327f66874Stedu errx(1, "invalid comment in %s; must start with '%s'", 13427f66874Stedu filename, COMMENTHDR); 135d502e9a0Stedu *commentend = '\0'; 136ce3e40b1Sderaadt if (comment) { 137ce3e40b1Sderaadt if (strlcpy(comment, b64 + COMMENTHDRLEN, 138ce3e40b1Sderaadt COMMENTMAXLEN) >= COMMENTMAXLEN) 13941b393a4Stedu errx(1, "comment too long"); 140ce3e40b1Sderaadt } 141dba2cf70Stedu if (!(b64end = strchr(commentend + 1, '\n'))) 1422c706440Stedu errx(1, "missing new line after base64 in %s", filename); 143d502e9a0Stedu *b64end = '\0'; 144e67d6036Stedu if (b64_pton(commentend + 1, buf, buflen) != buflen) 1452c706440Stedu errx(1, "invalid base64 encoding in %s", filename); 1467d7c2057Stedu if (memcmp(buf, PKALG, 2) != 0) 14727f66874Stedu errx(1, "unsupported file %s", filename); 14827f66874Stedu return b64end - b64 + 1; 14927f66874Stedu } 15027f66874Stedu 1514215a5deStedu static void 1520ce08f52Stedu readb64file(const char *filename, void *buf, size_t buflen, char *comment) 1534215a5deStedu { 1544215a5deStedu char b64[2048]; 155f030c3d3Stedu int rv, fd; 1564215a5deStedu 1574215a5deStedu fd = xopen(filename, O_RDONLY | O_NOFOLLOW, 0); 158dba2cf70Stedu if ((rv = read(fd, b64, sizeof(b64) - 1)) == -1) 159d59d433dSespie err(1, "read from %s", filename); 160d502e9a0Stedu b64[rv] = '\0'; 1610ce08f52Stedu parseb64file(filename, b64, buf, buflen, comment); 162c1ca80caStedu explicit_bzero(b64, sizeof(b64)); 1634215a5deStedu close(fd); 1644215a5deStedu } 1654215a5deStedu 166eee7f9deStedu static uint8_t * 1674215a5deStedu readmsg(const char *filename, unsigned long long *msglenp) 1684215a5deStedu { 169a30f80dbStedu unsigned long long msglen = 0; 170a30f80dbStedu uint8_t *msg = NULL; 1714215a5deStedu struct stat sb; 172a30f80dbStedu ssize_t x, space; 1734215a5deStedu int fd; 174ff9a6787Stedu const unsigned long long maxmsgsize = 1UL << 30; 1754215a5deStedu 1764215a5deStedu fd = xopen(filename, O_RDONLY | O_NOFOLLOW, 0); 177a30f80dbStedu if (fstat(fd, &sb) == 0 && S_ISREG(sb.st_mode)) { 178ff9a6787Stedu if (sb.st_size > maxmsgsize) 179a30f80dbStedu errx(1, "msg too large in %s", filename); 180a30f80dbStedu space = sb.st_size + 1; 181a30f80dbStedu } else { 182d6c685c1Stedu space = 64 * 1024 - 1; 183a30f80dbStedu } 184a30f80dbStedu 1859083d0fcStedu msg = xmalloc(space + 1); 186a30f80dbStedu while (1) { 187a30f80dbStedu if (space == 0) { 188ff9a6787Stedu if (msglen * 2 > maxmsgsize) 1899083d0fcStedu errx(1, "msg too large in %s", filename); 1909083d0fcStedu space = msglen; 191a30f80dbStedu if (!(msg = realloc(msg, msglen + space + 1))) 192a30f80dbStedu errx(1, "realloc"); 193a30f80dbStedu } 194a30f80dbStedu if ((x = read(fd, msg + msglen, space)) == -1) 195a30f80dbStedu err(1, "read from %s", filename); 196a30f80dbStedu if (x == 0) 197a30f80dbStedu break; 198a30f80dbStedu space -= x; 199a30f80dbStedu msglen += x; 200a30f80dbStedu } 201a30f80dbStedu 202d502e9a0Stedu msg[msglen] = '\0'; 2034215a5deStedu close(fd); 2044215a5deStedu 2054215a5deStedu *msglenp = msglen; 2064215a5deStedu return msg; 2074215a5deStedu } 2084215a5deStedu 2093ccd7401Sespie void 2100ce08f52Stedu writeall(int fd, const void *buf, size_t buflen, const char *filename) 2114215a5deStedu { 2127dec58f2Stedu ssize_t x; 2137dec58f2Stedu 2140ce08f52Stedu while (buflen != 0) { 215dba2cf70Stedu if ((x = write(fd, buf, buflen)) == -1) 216d59d433dSespie err(1, "write to %s", filename); 2170ce08f52Stedu buflen -= x; 218bcd4d29fSespie buf = (char *)buf + x; 219bcd4d29fSespie } 220d59d433dSespie } 2214215a5deStedu 2226ffce13fSderaadt #ifndef VERIFYONLY 2239b252276Stedu static char * 2249b252276Stedu createheader(const char *comment, const void *buf, size_t buflen) 2254215a5deStedu { 2269b252276Stedu char *header; 2274215a5deStedu char b64[1024]; 2289b252276Stedu 2299b252276Stedu if (b64_ntop(buf, buflen, b64, sizeof(b64)) == -1) 2309b252276Stedu errx(1, "base64 encode failed"); 2319b252276Stedu if (asprintf(&header, "%s%s\n%s\n", COMMENTHDR, comment, b64) == -1) 2329b252276Stedu err(1, "asprintf failed"); 2339b252276Stedu explicit_bzero(b64, sizeof(b64)); 2349b252276Stedu return header; 2359b252276Stedu } 2369b252276Stedu 2379b252276Stedu static void 2389b252276Stedu writekeyfile(const char *filename, const char *comment, const void *buf, 2399b252276Stedu size_t buflen, int oflags, mode_t mode) 2409b252276Stedu { 2419b252276Stedu char *header; 2429b252276Stedu int fd; 2434215a5deStedu 2448e162516Sderaadt fd = xopen(filename, O_CREAT|oflags|O_NOFOLLOW|O_WRONLY, mode); 2459b252276Stedu header = createheader(comment, buf, buflen); 246d59d433dSespie writeall(fd, header, strlen(header), filename); 2479b252276Stedu explicit_bzero(header, strlen(header)); 2489b252276Stedu free(header); 2494215a5deStedu close(fd); 2504215a5deStedu } 2514215a5deStedu 2524215a5deStedu static void 253fdf669dbStedu kdf(uint8_t *salt, size_t saltlen, int rounds, int allowstdin, int confirm, 2544333161aStedu uint8_t *key, size_t keylen) 2554215a5deStedu { 2564215a5deStedu char pass[1024]; 2572adf8afcStedu int rppflags = RPP_ECHO_OFF; 2584215a5deStedu 2594215a5deStedu if (rounds == 0) { 2604215a5deStedu memset(key, 0, keylen); 2614215a5deStedu return; 2624215a5deStedu } 2634215a5deStedu 2644333161aStedu if (allowstdin && !isatty(STDIN_FILENO)) 2652adf8afcStedu rppflags |= RPP_STDIN; 2662adf8afcStedu if (!readpassphrase("passphrase: ", pass, sizeof(pass), rppflags)) 26758ac87a3Stedu errx(1, "unable to read passphrase"); 2680e5a52c1Stedu if (strlen(pass) == 0) 2690e5a52c1Stedu errx(1, "please provide a password"); 270fdf669dbStedu if (confirm && !(rppflags & RPP_STDIN)) { 271fdf669dbStedu char pass2[1024]; 272fdf669dbStedu if (!readpassphrase("confirm passphrase: ", pass2, 273fdf669dbStedu sizeof(pass2), rppflags)) 274fdf669dbStedu errx(1, "unable to read passphrase"); 275fdf669dbStedu if (strcmp(pass, pass2) != 0) 276fdf669dbStedu errx(1, "passwords don't match"); 277fdf669dbStedu explicit_bzero(pass2, sizeof(pass2)); 278fdf669dbStedu } 2794215a5deStedu if (bcrypt_pbkdf(pass, strlen(pass), salt, saltlen, key, 2804215a5deStedu keylen, rounds) == -1) 2814215a5deStedu errx(1, "bcrypt pbkdf"); 282c1ca80caStedu explicit_bzero(pass, sizeof(pass)); 2834215a5deStedu } 2844215a5deStedu 2854215a5deStedu static void 2864215a5deStedu signmsg(uint8_t *seckey, uint8_t *msg, unsigned long long msglen, 2874215a5deStedu uint8_t *sig) 2884215a5deStedu { 2894215a5deStedu unsigned long long siglen; 2904215a5deStedu uint8_t *sigbuf; 2914215a5deStedu 2924215a5deStedu sigbuf = xmalloc(msglen + SIGBYTES); 2934215a5deStedu crypto_sign_ed25519(sigbuf, &siglen, msg, msglen, seckey); 2944215a5deStedu memcpy(sig, sigbuf, SIGBYTES); 2954215a5deStedu free(sigbuf); 2964215a5deStedu } 2974215a5deStedu 2984215a5deStedu static void 299bd7b638bStedu generate(const char *pubkeyfile, const char *seckeyfile, int rounds, 300bd7b638bStedu const char *comment) 3014215a5deStedu { 3024215a5deStedu uint8_t digest[SHA512_DIGEST_LENGTH]; 3034215a5deStedu struct pubkey pubkey; 3044215a5deStedu struct enckey enckey; 3054215a5deStedu uint8_t xorkey[sizeof(enckey.seckey)]; 306534dbeadStedu uint8_t keynum[KEYNUMLEN]; 307bd7b638bStedu char commentbuf[COMMENTMAXLEN]; 3084215a5deStedu SHA2_CTX ctx; 309b3fe1a3aStedu int i, nr; 3104215a5deStedu 3114215a5deStedu crypto_sign_ed25519_keypair(pubkey.pubkey, enckey.seckey); 312534dbeadStedu arc4random_buf(keynum, sizeof(keynum)); 3134215a5deStedu 3144215a5deStedu SHA512Init(&ctx); 3154215a5deStedu SHA512Update(&ctx, enckey.seckey, sizeof(enckey.seckey)); 3164215a5deStedu SHA512Final(digest, &ctx); 3174215a5deStedu 3184215a5deStedu memcpy(enckey.pkalg, PKALG, 2); 3194215a5deStedu memcpy(enckey.kdfalg, KDFALG, 2); 3204215a5deStedu enckey.kdfrounds = htonl(rounds); 321534dbeadStedu memcpy(enckey.keynum, keynum, KEYNUMLEN); 3224215a5deStedu arc4random_buf(enckey.salt, sizeof(enckey.salt)); 323fdf669dbStedu kdf(enckey.salt, sizeof(enckey.salt), rounds, 1, 1, xorkey, sizeof(xorkey)); 3244215a5deStedu memcpy(enckey.checksum, digest, sizeof(enckey.checksum)); 3254215a5deStedu for (i = 0; i < sizeof(enckey.seckey); i++) 3264215a5deStedu enckey.seckey[i] ^= xorkey[i]; 327c1ca80caStedu explicit_bzero(digest, sizeof(digest)); 328c1ca80caStedu explicit_bzero(xorkey, sizeof(xorkey)); 3294215a5deStedu 330b3fe1a3aStedu if ((nr = snprintf(commentbuf, sizeof(commentbuf), "%s secret key", 331b3fe1a3aStedu comment)) == -1 || nr >= sizeof(commentbuf)) 33241b393a4Stedu errx(1, "comment too long"); 3339b252276Stedu writekeyfile(seckeyfile, commentbuf, &enckey, 3349b252276Stedu sizeof(enckey), O_EXCL, 0600); 335c1ca80caStedu explicit_bzero(&enckey, sizeof(enckey)); 3364215a5deStedu 3374215a5deStedu memcpy(pubkey.pkalg, PKALG, 2); 338534dbeadStedu memcpy(pubkey.keynum, keynum, KEYNUMLEN); 339b3fe1a3aStedu if ((nr = snprintf(commentbuf, sizeof(commentbuf), "%s public key", 340b3fe1a3aStedu comment)) == -1 || nr >= sizeof(commentbuf)) 34141b393a4Stedu errx(1, "comment too long"); 3429b252276Stedu writekeyfile(pubkeyfile, commentbuf, &pubkey, 3439b252276Stedu sizeof(pubkey), O_EXCL, 0666); 3449b252276Stedu } 3459b252276Stedu 3463ccd7401Sespie uint8_t * 347ff9e4f8aStedu createsig(const char *seckeyfile, const char *msgfile, uint8_t *msg, 348ff9e4f8aStedu unsigned long long msglen) 3494215a5deStedu { 3504215a5deStedu struct enckey enckey; 351ff9e4f8aStedu uint8_t xorkey[sizeof(enckey.seckey)]; 352ff9e4f8aStedu struct sig sig; 3539b252276Stedu char *sighdr; 3540f212b48Stedu char *secname; 355ff9e4f8aStedu uint8_t digest[SHA512_DIGEST_LENGTH]; 356ff9e4f8aStedu int i, nr, rounds; 357ff9e4f8aStedu SHA2_CTX ctx; 3589b252276Stedu char comment[COMMENTMAXLEN], sigcomment[COMMENTMAXLEN]; 3594215a5deStedu 3601453d2a0Stedu readb64file(seckeyfile, &enckey, sizeof(enckey), comment); 3614215a5deStedu 3621118246cStedu secname = strstr(seckeyfile, ".sec"); 3631118246cStedu if (secname && strlen(secname) == 4) { 364b3fe1a3aStedu if ((nr = snprintf(sigcomment, sizeof(sigcomment), VERIFYWITH "%.*s.pub", 365b3fe1a3aStedu (int)strlen(seckeyfile) - 4, seckeyfile)) == -1 || nr >= sizeof(sigcomment)) 36641b393a4Stedu errx(1, "comment too long"); 3670f212b48Stedu } else { 368b3fe1a3aStedu if ((nr = snprintf(sigcomment, sizeof(sigcomment), "signature from %s", 369b3fe1a3aStedu comment)) == -1 || nr >= sizeof(sigcomment)) 37041b393a4Stedu errx(1, "comment too long"); 3710f212b48Stedu } 3729b252276Stedu 373ff9e4f8aStedu if (memcmp(enckey.kdfalg, KDFALG, 2) != 0) 374ff9e4f8aStedu errx(1, "unsupported KDF"); 375ff9e4f8aStedu rounds = ntohl(enckey.kdfrounds); 376ff9e4f8aStedu kdf(enckey.salt, sizeof(enckey.salt), rounds, strcmp(msgfile, "-") != 0, 377ff9e4f8aStedu 0, xorkey, sizeof(xorkey)); 378ff9e4f8aStedu for (i = 0; i < sizeof(enckey.seckey); i++) 379ff9e4f8aStedu enckey.seckey[i] ^= xorkey[i]; 380ff9e4f8aStedu explicit_bzero(xorkey, sizeof(xorkey)); 381ff9e4f8aStedu SHA512Init(&ctx); 382ff9e4f8aStedu SHA512Update(&ctx, enckey.seckey, sizeof(enckey.seckey)); 383ff9e4f8aStedu SHA512Final(digest, &ctx); 384ff9e4f8aStedu if (memcmp(enckey.checksum, digest, sizeof(enckey.checksum)) != 0) 385ff9e4f8aStedu errx(1, "incorrect passphrase"); 386ff9e4f8aStedu explicit_bzero(digest, sizeof(digest)); 387ff9e4f8aStedu 388ff9e4f8aStedu signmsg(enckey.seckey, msg, msglen, sig.sig); 389ff9e4f8aStedu memcpy(sig.keynum, enckey.keynum, KEYNUMLEN); 390ff9e4f8aStedu explicit_bzero(&enckey, sizeof(enckey)); 391ff9e4f8aStedu 392ff9e4f8aStedu memcpy(sig.pkalg, PKALG, 2); 393ff9e4f8aStedu 394ff9e4f8aStedu sighdr = createheader(sigcomment, &sig, sizeof(sig)); 395ff9e4f8aStedu return sighdr; 396ff9e4f8aStedu } 397ff9e4f8aStedu 398ff9e4f8aStedu static void 399ff9e4f8aStedu sign(const char *seckeyfile, const char *msgfile, const char *sigfile, 400ff9e4f8aStedu int embedded) 401ff9e4f8aStedu { 402ff9e4f8aStedu uint8_t *msg; 403ff9e4f8aStedu char *sighdr; 404ff9e4f8aStedu int fd; 405ff9e4f8aStedu unsigned long long msglen; 406ff9e4f8aStedu 4079b252276Stedu msg = readmsg(msgfile, &msglen); 4089b252276Stedu 409ff9e4f8aStedu sighdr = createsig(seckeyfile, msgfile, msg, msglen); 4109b252276Stedu 4119b252276Stedu fd = xopen(sigfile, O_CREAT|O_TRUNC|O_NOFOLLOW|O_WRONLY, 0666); 4129b252276Stedu writeall(fd, sighdr, strlen(sighdr), sigfile); 4139b252276Stedu free(sighdr); 41427f66874Stedu if (embedded) 4159b252276Stedu writeall(fd, msg, msglen, sigfile); 4169b252276Stedu close(fd); 4174215a5deStedu 4184215a5deStedu free(msg); 4194215a5deStedu } 420665ab7d9Stedu #endif 4214215a5deStedu 4224215a5deStedu static void 42348336e31Stedu verifymsg(struct pubkey *pubkey, uint8_t *msg, unsigned long long msglen, 42448336e31Stedu struct sig *sig, int quiet) 4251c9c770cStedu { 4261c9c770cStedu uint8_t *sigbuf, *dummybuf; 4271c9c770cStedu unsigned long long siglen, dummylen; 4281c9c770cStedu 429534dbeadStedu if (memcmp(pubkey->keynum, sig->keynum, KEYNUMLEN) != 0) 43048336e31Stedu errx(1, "verification failed: checked against wrong key"); 43148336e31Stedu 4321c9c770cStedu siglen = SIGBYTES + msglen; 4331c9c770cStedu sigbuf = xmalloc(siglen); 4341c9c770cStedu dummybuf = xmalloc(siglen); 43548336e31Stedu memcpy(sigbuf, sig->sig, SIGBYTES); 4361c9c770cStedu memcpy(sigbuf + SIGBYTES, msg, msglen); 4371c9c770cStedu if (crypto_sign_ed25519_open(dummybuf, &dummylen, sigbuf, siglen, 43848336e31Stedu pubkey->pubkey) == -1) 4391c9c770cStedu errx(1, "signature verification failed"); 44058559f60Stedu if (!quiet) 44158559f60Stedu printf("Signature Verified\n"); 4421c9c770cStedu free(sigbuf); 4431c9c770cStedu free(dummybuf); 4441c9c770cStedu } 4451c9c770cStedu 44618d71116Sespie #ifndef VERIFYONLY 44718d71116Sespie static void 44818d71116Sespie check_keytype(const char *pubkeyfile, const char *keytype) 44918d71116Sespie { 45047dc9001Stedu size_t len; 45118d71116Sespie char *cmp; 45247dc9001Stedu int slen; 45347dc9001Stedu 45447dc9001Stedu len = strlen(pubkeyfile); 45547dc9001Stedu slen = asprintf(&cmp, "-%s.pub", keytype); 45618d71116Sespie if (slen < 0) 45718d71116Sespie errx(1, "asprintf error"); 45818d71116Sespie if (len < slen) 45918d71116Sespie errx(1, "too short"); 46018d71116Sespie 46118d71116Sespie if (strcmp(pubkeyfile + len - slen, cmp) != 0) 46218d71116Sespie errx(1, "wrong keytype"); 46318d71116Sespie free(cmp); 46418d71116Sespie } 46518d71116Sespie #endif 46618d71116Sespie 4671c9c770cStedu static void 46826d9395eStedu readpubkey(const char *pubkeyfile, struct pubkey *pubkey, 46918d71116Sespie const char *sigcomment, const char *keytype) 4704215a5deStedu { 471f65d31e6Stedu const char *safepath = "/etc/signify/"; 47227f66874Stedu 4730f212b48Stedu if (!pubkeyfile) { 4741118246cStedu pubkeyfile = strstr(sigcomment, VERIFYWITH); 4751118246cStedu if (pubkeyfile) { 4760f212b48Stedu pubkeyfile += strlen(VERIFYWITH); 477f65d31e6Stedu if (strncmp(pubkeyfile, safepath, strlen(safepath)) != 0 || 478febc8181Stedu strstr(pubkeyfile, "/../") != NULL) 479b0b02d10Stedu errx(1, "untrusted path %s", pubkeyfile); 48018d71116Sespie #ifndef VERIFYONLY 48118d71116Sespie if (keytype) 48218d71116Sespie check_keytype(pubkeyfile, keytype); 48318d71116Sespie #endif 484b0b02d10Stedu } else 4855d586c2bStedu usage("must specify pubkey"); 4860f212b48Stedu } 48726d9395eStedu readb64file(pubkeyfile, pubkey, sizeof(*pubkey), NULL); 48826d9395eStedu } 48926d9395eStedu 49026d9395eStedu static void 49126d9395eStedu verifysimple(const char *pubkeyfile, const char *msgfile, const char *sigfile, 49218d71116Sespie int quiet, const char *keytype) 49326d9395eStedu { 49426d9395eStedu char sigcomment[COMMENTMAXLEN]; 49526d9395eStedu struct sig sig; 49626d9395eStedu struct pubkey pubkey; 49726d9395eStedu unsigned long long msglen; 49826d9395eStedu uint8_t *msg; 49926d9395eStedu 50026d9395eStedu msg = readmsg(msgfile, &msglen); 50126d9395eStedu 50226d9395eStedu readb64file(sigfile, &sig, sizeof(sig), sigcomment); 50318d71116Sespie readpubkey(pubkeyfile, &pubkey, sigcomment, keytype); 5044215a5deStedu 50548336e31Stedu verifymsg(&pubkey, msg, msglen, &sig, quiet); 506ffebbc61Stedu 507ffebbc61Stedu free(msg); 508ffebbc61Stedu } 509ffebbc61Stedu 510ffebbc61Stedu static uint8_t * 511ffebbc61Stedu verifyembedded(const char *pubkeyfile, const char *sigfile, 51218d71116Sespie int quiet, unsigned long long *msglenp, const char *keytype) 513ffebbc61Stedu { 51426d9395eStedu char sigcomment[COMMENTMAXLEN]; 515ffebbc61Stedu struct sig sig; 516ffebbc61Stedu struct pubkey pubkey; 517ffebbc61Stedu unsigned long long msglen, siglen; 518ffebbc61Stedu uint8_t *msg; 519ffebbc61Stedu 520ffebbc61Stedu msg = readmsg(sigfile, &msglen); 521ffebbc61Stedu 52226d9395eStedu siglen = parseb64file(sigfile, msg, &sig, sizeof(sig), sigcomment); 52318d71116Sespie readpubkey(pubkeyfile, &pubkey, sigcomment, keytype); 52426d9395eStedu 525ffebbc61Stedu msglen -= siglen; 526ffebbc61Stedu memmove(msg, msg + siglen, msglen); 527ffebbc61Stedu msg[msglen] = 0; 528ffebbc61Stedu 529ffebbc61Stedu verifymsg(&pubkey, msg, msglen, &sig, quiet); 530ffebbc61Stedu 531ffebbc61Stedu *msglenp = msglen; 532ffebbc61Stedu return msg; 533ffebbc61Stedu } 534ffebbc61Stedu 535ffebbc61Stedu static void 536ffebbc61Stedu verify(const char *pubkeyfile, const char *msgfile, const char *sigfile, 53718d71116Sespie int embedded, int quiet, const char *keytype) 538ffebbc61Stedu { 539ffebbc61Stedu unsigned long long msglen; 540ffebbc61Stedu uint8_t *msg; 541ffebbc61Stedu int fd; 542ffebbc61Stedu 54327f66874Stedu if (embedded) { 54418d71116Sespie msg = verifyembedded(pubkeyfile, sigfile, quiet, &msglen, 54518d71116Sespie keytype); 5469831e76dStedu fd = xopen(msgfile, O_CREAT|O_TRUNC|O_NOFOLLOW|O_WRONLY, 0666); 54727f66874Stedu writeall(fd, msg, msglen, msgfile); 54887e034adSespie free(msg); 54927f66874Stedu close(fd); 550ffebbc61Stedu } else { 55118d71116Sespie verifysimple(pubkeyfile, msgfile, sigfile, quiet, keytype); 55227f66874Stedu } 5534215a5deStedu } 5544215a5deStedu 55558559f60Stedu #ifndef VERIFYONLY 556ce955b95Stedu #define HASHBUFSIZE 224 55758559f60Stedu struct checksum { 558f30a54aaStedu char file[PATH_MAX]; 559ce955b95Stedu char hash[HASHBUFSIZE]; 560032a32d9Stedu char algo[32]; 56158559f60Stedu }; 56258559f60Stedu 563e96025a1Sespie static void * 564e96025a1Sespie ecalloc(size_t s1, size_t s2, void *data) 565e96025a1Sespie { 566db6a8127Stedu void *p; 567db6a8127Stedu 568dba2cf70Stedu if (!(p = calloc(s1, s2))) 569e96025a1Sespie err(1, "calloc"); 570e96025a1Sespie return p; 571e96025a1Sespie } 572e96025a1Sespie 57358559f60Stedu static void 574e96025a1Sespie efree(void *p, void *data) 575e96025a1Sespie { 576e96025a1Sespie free(p); 577e96025a1Sespie } 578e96025a1Sespie 579e96025a1Sespie static void 580e96025a1Sespie recodehash(char *hash, size_t len) 5810050332bStedu { 582ce955b95Stedu uint8_t data[HASHBUFSIZE / 2]; 5830050332bStedu int i, rv; 5840050332bStedu 585e96025a1Sespie if (strlen(hash) == len) 5860050332bStedu return; 5870050332bStedu if ((rv = b64_pton(hash, data, sizeof(data))) == -1) 5880050332bStedu errx(1, "invalid base64 encoding"); 5890050332bStedu for (i = 0; i < rv; i++) 590ce955b95Stedu snprintf(hash + i * 2, HASHBUFSIZE - i * 2, "%2.2x", data[i]); 5910050332bStedu } 5920050332bStedu 593e96025a1Sespie static int 594e96025a1Sespie verifychecksum(struct checksum *c, int quiet) 59558559f60Stedu { 596e96025a1Sespie char buf[HASHBUFSIZE]; 597db6a8127Stedu 59858559f60Stedu if (strcmp(c->algo, "SHA256") == 0) { 599e96025a1Sespie recodehash(c->hash, SHA256_DIGEST_STRING_LENGTH-1); 600e96025a1Sespie if (!SHA256File(c->file, buf)) 601e96025a1Sespie return 0; 60258559f60Stedu } else if (strcmp(c->algo, "SHA512") == 0) { 603e96025a1Sespie recodehash(c->hash, SHA512_DIGEST_STRING_LENGTH-1); 604e96025a1Sespie if (!SHA512File(c->file, buf)) 605e96025a1Sespie return 0; 60658559f60Stedu } else { 60758559f60Stedu errx(1, "can't handle algorithm %s", c->algo); 60858559f60Stedu } 609b39b450dStedu if (strcmp(c->hash, buf) != 0) 610e96025a1Sespie return 0; 61158559f60Stedu if (!quiet) 61258559f60Stedu printf("%s: OK\n", c->file); 613e96025a1Sespie return 1; 61458559f60Stedu } 615e96025a1Sespie 616e96025a1Sespie static void 617e96025a1Sespie verifychecksums(char *msg, int argc, char **argv, int quiet) 618e96025a1Sespie { 619e96025a1Sespie struct ohash_info info = { 0, NULL, ecalloc, efree, NULL }; 620e96025a1Sespie struct ohash myh; 621e96025a1Sespie struct checksum c; 622a5b6fcd7Stedu char *e, *line, *endline; 623db6a8127Stedu int hasfailed = 0; 624db6a8127Stedu int i, rv; 625e96025a1Sespie unsigned int slot; 626e96025a1Sespie 627e96025a1Sespie ohash_init(&myh, 6, &info); 628a5b6fcd7Stedu if (argc) { 629e96025a1Sespie for (i = 0; i < argc; i++) { 630e96025a1Sespie slot = ohash_qlookup(&myh, argv[i]); 631e96025a1Sespie e = ohash_find(&myh, slot); 632e96025a1Sespie if (e == NULL) 633e96025a1Sespie ohash_insert(&myh, slot, argv[i]); 634e96025a1Sespie } 635e96025a1Sespie } 636e96025a1Sespie 637e96025a1Sespie line = msg; 638e96025a1Sespie while (line && *line) { 639e96025a1Sespie if ((endline = strchr(line, '\n'))) 640e96025a1Sespie *endline++ = '\0'; 641a8e2d00fStedu #if PATH_MAX < 1024 || HASHBUFSIZE < 224 642a8e2d00fStedu #error sizes are wrong 643a8e2d00fStedu #endif 6443111eefdStedu rv = sscanf(line, "%31s (%1023[^)]) = %223s", 645e96025a1Sespie c.algo, c.file, c.hash); 6463111eefdStedu if (rv != 3) 647e96025a1Sespie errx(1, "unable to parse checksum line %s", line); 648e96025a1Sespie line = endline; 649e96025a1Sespie if (argc) { 650e96025a1Sespie slot = ohash_qlookup(&myh, c.file); 651e96025a1Sespie e = ohash_find(&myh, slot); 652e96025a1Sespie if (e != NULL) { 653db6a8127Stedu if (verifychecksum(&c, quiet) != 0) 654e96025a1Sespie ohash_remove(&myh, slot); 655e96025a1Sespie } 656e96025a1Sespie } else { 657db6a8127Stedu if (verifychecksum(&c, quiet) == 0) { 658a5b6fcd7Stedu slot = ohash_qlookup(&myh, c.file); 659a5b6fcd7Stedu e = ohash_find(&myh, slot); 660a5b6fcd7Stedu if (e == NULL) { 661a5b6fcd7Stedu if (!(e = strdup(c.file))) 662a5b6fcd7Stedu err(1, "strdup"); 663a5b6fcd7Stedu ohash_insert(&myh, slot, e); 664a5b6fcd7Stedu } 66558559f60Stedu } 66658559f60Stedu } 667e96025a1Sespie } 668e96025a1Sespie 669a5b6fcd7Stedu for (e = ohash_first(&myh, &slot); e != NULL; e = ohash_next(&myh, &slot)) { 670a5b6fcd7Stedu fprintf(stderr, "%s: FAIL\n", e); 671e96025a1Sespie hasfailed = 1; 672a5b6fcd7Stedu if (argc == 0) 673a5b6fcd7Stedu free(e); 674e96025a1Sespie } 675e96025a1Sespie ohash_delete(&myh); 6768972cca3Stedu if (hasfailed) 67758559f60Stedu exit(1); 67858559f60Stedu } 67958559f60Stedu 68058559f60Stedu static void 68158559f60Stedu check(const char *pubkeyfile, const char *sigfile, int quiet, int argc, 68258559f60Stedu char **argv) 68358559f60Stedu { 684ffebbc61Stedu unsigned long long msglen; 68558559f60Stedu uint8_t *msg; 68658559f60Stedu 68718d71116Sespie msg = verifyembedded(pubkeyfile, sigfile, quiet, &msglen, NULL); 688a40f4206Stedu verifychecksums((char *)msg, argc, argv, quiet); 68958559f60Stedu 690ffebbc61Stedu free(msg); 69158559f60Stedu } 6923ccd7401Sespie 6933ccd7401Sespie void * 6943ccd7401Sespie verifyzdata(uint8_t *zdata, unsigned long long zdatalen, 6953ccd7401Sespie const char *filename, const char *pubkeyfile, const char *keytype) 6963ccd7401Sespie { 6973ccd7401Sespie struct sig sig; 6983ccd7401Sespie char sigcomment[COMMENTMAXLEN]; 6993ccd7401Sespie unsigned long long siglen; 7003ccd7401Sespie struct pubkey pubkey; 7013ccd7401Sespie 7023ccd7401Sespie if (zdatalen < sizeof(sig)) 7033ccd7401Sespie errx(1, "signature too short in %s", filename); 7043ccd7401Sespie siglen = parseb64file(filename, zdata, &sig, sizeof(sig), 7053ccd7401Sespie sigcomment); 7063ccd7401Sespie readpubkey(pubkeyfile, &pubkey, sigcomment, keytype); 7073ccd7401Sespie zdata += siglen; 7083ccd7401Sespie zdatalen -= siglen; 7093ccd7401Sespie verifymsg(&pubkey, zdata, zdatalen, &sig, 1); 7103ccd7401Sespie return zdata; 7113ccd7401Sespie } 71258559f60Stedu #endif 71358559f60Stedu 7144215a5deStedu int 7154215a5deStedu main(int argc, char **argv) 7164215a5deStedu { 71727f66874Stedu const char *pubkeyfile = NULL, *seckeyfile = NULL, *msgfile = NULL, 7184215a5deStedu *sigfile = NULL; 719f30a54aaStedu char sigfilebuf[PATH_MAX]; 720bd7b638bStedu const char *comment = "signify"; 72118d71116Sespie char *keytype = NULL; 7224215a5deStedu int ch, rounds; 72327f66874Stedu int embedded = 0; 72458559f60Stedu int quiet = 0; 7253ccd7401Sespie int gzip = 0; 726a6bade58Stedu enum { 727a6bade58Stedu NONE, 72858559f60Stedu CHECK, 729a6bade58Stedu GENERATE, 730a6bade58Stedu SIGN, 731a6bade58Stedu VERIFY 732a6bade58Stedu } verb = NONE; 733a6bade58Stedu 7340bd1216cSderaadt if (pledge("stdio rpath wpath cpath tty", NULL) == -1) 7350bd1216cSderaadt err(1, "pledge"); 7364215a5deStedu 7374215a5deStedu rounds = 42; 7384215a5deStedu 7393ccd7401Sespie while ((ch = getopt(argc, argv, "CGSVzc:em:np:qs:t:x:")) != -1) { 7404215a5deStedu switch (ch) { 741665ab7d9Stedu #ifndef VERIFYONLY 74258559f60Stedu case 'C': 74358559f60Stedu if (verb) 74458559f60Stedu usage(NULL); 74558559f60Stedu verb = CHECK; 74658559f60Stedu break; 747a6bade58Stedu case 'G': 748a6bade58Stedu if (verb) 749f2adbe28Stedu usage(NULL); 750a6bade58Stedu verb = GENERATE; 7514215a5deStedu break; 7524215a5deStedu case 'S': 753a6bade58Stedu if (verb) 754f2adbe28Stedu usage(NULL); 755a6bade58Stedu verb = SIGN; 7564215a5deStedu break; 7573ccd7401Sespie case 'z': 7583ccd7401Sespie gzip = 1; 7593ccd7401Sespie break; 760665ab7d9Stedu #endif 7614215a5deStedu case 'V': 762a6bade58Stedu if (verb) 763f2adbe28Stedu usage(NULL); 764a6bade58Stedu verb = VERIFY; 765a6bade58Stedu break; 766bd7b638bStedu case 'c': 767bd7b638bStedu comment = optarg; 768bd7b638bStedu break; 76927f66874Stedu case 'e': 77027f66874Stedu embedded = 1; 77127f66874Stedu break; 772f2adbe28Stedu case 'm': 773f2adbe28Stedu msgfile = optarg; 774f2adbe28Stedu break; 775a6bade58Stedu case 'n': 776a6bade58Stedu rounds = 0; 777a6bade58Stedu break; 778a6bade58Stedu case 'p': 779a6bade58Stedu pubkeyfile = optarg; 780a6bade58Stedu break; 78158559f60Stedu case 'q': 78258559f60Stedu quiet = 1; 78358559f60Stedu break; 784a6bade58Stedu case 's': 785a6bade58Stedu seckeyfile = optarg; 7864215a5deStedu break; 78718d71116Sespie case 't': 78818d71116Sespie keytype = optarg; 78918d71116Sespie break; 790f2adbe28Stedu case 'x': 791f2adbe28Stedu sigfile = optarg; 792f2adbe28Stedu break; 7934215a5deStedu default: 794f2adbe28Stedu usage(NULL); 7954215a5deStedu break; 7964215a5deStedu } 7974215a5deStedu } 798bcc39c47Stedu argc -= optind; 79942efb9f2Sespie argv += optind; 80042efb9f2Sespie 801da1d8f10Stedu if (embedded && gzip) 802da1d8f10Stedu errx(1, "can't combine -e and -z options"); 803da1d8f10Stedu 80475f1004aSbluhm if (setvbuf(stdout, NULL, _IOLBF, 0) != 0) 80575f1004aSbluhm err(1, "setvbuf"); 80675f1004aSbluhm 80758559f60Stedu #ifndef VERIFYONLY 80858559f60Stedu if (verb == CHECK) { 809*de861b93Stedu if (pledge("stdio rpath", NULL) == -1) 810*de861b93Stedu err(1, "pledge"); 8119fbe12ffStedu if (!sigfile) 8129fbe12ffStedu usage("must specify sigfile"); 81358559f60Stedu check(pubkeyfile, sigfile, quiet, argc, argv); 81458559f60Stedu return 0; 81558559f60Stedu } 81658559f60Stedu #endif 81758559f60Stedu 818f2adbe28Stedu if (argc != 0) 819f2adbe28Stedu usage(NULL); 820f2adbe28Stedu 8219127dd95Stedu if (!sigfile && msgfile) { 822b3fe1a3aStedu int nr; 823f2adbe28Stedu if (strcmp(msgfile, "-") == 0) 8245d586c2bStedu usage("must specify sigfile with - message"); 825b3fe1a3aStedu if ((nr = snprintf(sigfilebuf, sizeof(sigfilebuf), "%s.sig", 826b3fe1a3aStedu msgfile)) == -1 || nr >= sizeof(sigfilebuf)) 8274215a5deStedu errx(1, "path too long"); 8284215a5deStedu sigfile = sigfilebuf; 8294215a5deStedu } 8309127dd95Stedu 8319127dd95Stedu switch (verb) { 832665ab7d9Stedu #ifndef VERIFYONLY 8339127dd95Stedu case GENERATE: 834*de861b93Stedu /* no pledge */ 8359127dd95Stedu if (!pubkeyfile || !seckeyfile) 8365d586c2bStedu usage("must specify pubkey and seckey"); 8379127dd95Stedu generate(pubkeyfile, seckeyfile, rounds, comment); 8389127dd95Stedu break; 8399127dd95Stedu case SIGN: 840*de861b93Stedu /* no pledge */ 8413ccd7401Sespie if (gzip) 8423ccd7401Sespie zsign(seckeyfile, msgfile, sigfile); 8433ccd7401Sespie else { 8449127dd95Stedu if (!msgfile || !seckeyfile) 8455d586c2bStedu usage("must specify message and seckey"); 84627f66874Stedu sign(seckeyfile, msgfile, sigfile, embedded); 8473ccd7401Sespie } 8489127dd95Stedu break; 849665ab7d9Stedu #endif 8509127dd95Stedu case VERIFY: 851*de861b93Stedu if ((embedded || gzip) 852*de861b93Stedu && (!msgfile || strcmp(msgfile, "-") != 0)) { 853*de861b93Stedu if (pledge("stdio rpath wpath cpath", NULL) == -1) 854*de861b93Stedu err(1, "pledge"); 855*de861b93Stedu } else { 856*de861b93Stedu if (pledge("stdio rpath", NULL) == -1) 857*de861b93Stedu err(1, "pledge"); 858*de861b93Stedu } 8593ccd7401Sespie if (gzip) 8603ccd7401Sespie zverify(pubkeyfile, msgfile, sigfile, keytype); 8613ccd7401Sespie else { 8620f212b48Stedu if (!msgfile) 8635d586c2bStedu usage("must specify message"); 8643ccd7401Sespie verify(pubkeyfile, msgfile, sigfile, embedded, 8653ccd7401Sespie quiet, keytype); 8663ccd7401Sespie } 8679127dd95Stedu break; 8689127dd95Stedu default: 869*de861b93Stedu if (pledge("stdio", NULL) == -1) 870*de861b93Stedu err(1, "pledge"); 8719127dd95Stedu usage(NULL); 8729127dd95Stedu break; 87342efb9f2Sespie } 87442efb9f2Sespie 8754215a5deStedu return 0; 8764215a5deStedu } 877