1*2c706440Stedu /* $OpenBSD: signify.c,v 1.82 2014/05/14 15:56:41 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 224215a5deStedu #include <stdint.h> 234215a5deStedu #include <fcntl.h> 244215a5deStedu #include <string.h> 254215a5deStedu #include <stdio.h> 26ebde6afdStedu #include <stdlib.h> 274215a5deStedu #include <err.h> 284215a5deStedu #include <unistd.h> 294215a5deStedu #include <readpassphrase.h> 304215a5deStedu #include <util.h> 314215a5deStedu #include <sha2.h> 324215a5deStedu 334215a5deStedu #include "crypto_api.h" 344215a5deStedu 354215a5deStedu #define SIGBYTES crypto_sign_ed25519_BYTES 364215a5deStedu #define SECRETBYTES crypto_sign_ed25519_SECRETKEYBYTES 374215a5deStedu #define PUBLICBYTES crypto_sign_ed25519_PUBLICKEYBYTES 384215a5deStedu 394215a5deStedu #define PKALG "Ed" 404215a5deStedu #define KDFALG "BK" 411c9c770cStedu #define FPLEN 8 421c9c770cStedu 431c9c770cStedu #define COMMENTHDR "untrusted comment: " 441453d2a0Stedu #define COMMENTHDRLEN 19 451453d2a0Stedu #define COMMENTMAXLEN 1024 460f212b48Stedu #define VERIFYWITH "verify with " 474215a5deStedu 484215a5deStedu struct enckey { 494215a5deStedu uint8_t pkalg[2]; 504215a5deStedu uint8_t kdfalg[2]; 514215a5deStedu uint32_t kdfrounds; 524215a5deStedu uint8_t salt[16]; 534215a5deStedu uint8_t checksum[8]; 541c9c770cStedu uint8_t fingerprint[FPLEN]; 554215a5deStedu uint8_t seckey[SECRETBYTES]; 564215a5deStedu }; 574215a5deStedu 584215a5deStedu struct pubkey { 594215a5deStedu uint8_t pkalg[2]; 601c9c770cStedu uint8_t fingerprint[FPLEN]; 614215a5deStedu uint8_t pubkey[PUBLICBYTES]; 624215a5deStedu }; 634215a5deStedu 644215a5deStedu struct sig { 654215a5deStedu uint8_t pkalg[2]; 661c9c770cStedu uint8_t fingerprint[FPLEN]; 674215a5deStedu uint8_t sig[SIGBYTES]; 684215a5deStedu }; 694215a5deStedu 704215a5deStedu extern char *__progname; 714215a5deStedu 724215a5deStedu static void 73f2adbe28Stedu usage(const char *error) 744215a5deStedu { 75f2adbe28Stedu if (error) 76f2adbe28Stedu fprintf(stderr, "%s\n", error); 7742efb9f2Sespie fprintf(stderr, "usage:" 7835e4c3d2Sespie #ifndef VERIFYONLY 79c374df80Snaddy "\t%1$s -C [-q] -p pubkey -x sigfile [file ...]\n" 80f2adbe28Stedu "\t%1$s -G [-n] [-c comment] -p pubkey -s seckey\n" 81f2adbe28Stedu "\t%1$s -I [-p pubkey] [-s seckey] [-x sigfile]\n" 82f2adbe28Stedu "\t%1$s -S [-e] [-x sigfile] -s seckey -m message\n" 8335e4c3d2Sespie #endif 8458559f60Stedu "\t%1$s -V [-eq] [-x sigfile] -p pubkey -m message\n", 8535e4c3d2Sespie __progname); 864215a5deStedu exit(1); 874215a5deStedu } 884215a5deStedu 894215a5deStedu static int 908e162516Sderaadt xopen(const char *fname, int oflags, mode_t mode) 914215a5deStedu { 92ead0b14bStedu struct stat sb; 934215a5deStedu int fd; 944215a5deStedu 95f2adbe28Stedu if (strcmp(fname, "-") == 0) { 968e162516Sderaadt if ((oflags & O_WRONLY)) 97f2adbe28Stedu fd = dup(STDOUT_FILENO); 98f2adbe28Stedu else 99f2adbe28Stedu fd = dup(STDIN_FILENO); 100f2adbe28Stedu if (fd == -1) 101f2adbe28Stedu err(1, "dup failed"); 102f2adbe28Stedu } else { 1038e162516Sderaadt fd = open(fname, oflags, mode); 1044215a5deStedu if (fd == -1) 105f2adbe28Stedu err(1, "can't open %s for %s", fname, 1068e162516Sderaadt (oflags & O_WRONLY) ? "writing" : "reading"); 107f2adbe28Stedu } 108ead0b14bStedu if (fstat(fd, &sb) == -1 || S_ISDIR(sb.st_mode)) 1095d586c2bStedu errx(1, "not a valid file: %s", fname); 1104215a5deStedu return fd; 1114215a5deStedu } 1124215a5deStedu 1134215a5deStedu static void * 1144215a5deStedu xmalloc(size_t len) 1154215a5deStedu { 1164215a5deStedu void *p; 1174215a5deStedu 1184215a5deStedu p = malloc(len); 1194215a5deStedu if (!p) 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 } 14127f66874Stedu b64end = strchr(commentend + 1, '\n'); 14227f66874Stedu if (!b64end) 143*2c706440Stedu errx(1, "missing new line after base64 in %s", filename); 144d502e9a0Stedu *b64end = '\0'; 145e67d6036Stedu if (b64_pton(commentend + 1, buf, buflen) != buflen) 146*2c706440Stedu errx(1, "invalid base64 encoding in %s", filename); 1477d7c2057Stedu if (memcmp(buf, PKALG, 2) != 0) 14827f66874Stedu errx(1, "unsupported file %s", filename); 14927f66874Stedu return b64end - b64 + 1; 15027f66874Stedu } 15127f66874Stedu 1524215a5deStedu static void 1530ce08f52Stedu readb64file(const char *filename, void *buf, size_t buflen, char *comment) 1544215a5deStedu { 1554215a5deStedu char b64[2048]; 156f030c3d3Stedu int rv, fd; 1574215a5deStedu 1584215a5deStedu fd = xopen(filename, O_RDONLY | O_NOFOLLOW, 0); 1594215a5deStedu rv = read(fd, b64, sizeof(b64) - 1); 1604215a5deStedu if (rv == -1) 161d59d433dSespie err(1, "read from %s", filename); 162d502e9a0Stedu b64[rv] = '\0'; 1630ce08f52Stedu parseb64file(filename, b64, buf, buflen, comment); 164c1ca80caStedu explicit_bzero(b64, sizeof(b64)); 1654215a5deStedu close(fd); 1664215a5deStedu } 1674215a5deStedu 168eee7f9deStedu static uint8_t * 1694215a5deStedu readmsg(const char *filename, unsigned long long *msglenp) 1704215a5deStedu { 171a30f80dbStedu unsigned long long msglen = 0; 172a30f80dbStedu uint8_t *msg = NULL; 1734215a5deStedu struct stat sb; 174a30f80dbStedu ssize_t x, space; 1754215a5deStedu int fd; 176ff9a6787Stedu const unsigned long long maxmsgsize = 1UL << 30; 1774215a5deStedu 1784215a5deStedu fd = xopen(filename, O_RDONLY | O_NOFOLLOW, 0); 179a30f80dbStedu if (fstat(fd, &sb) == 0 && S_ISREG(sb.st_mode)) { 180ff9a6787Stedu if (sb.st_size > maxmsgsize) 181a30f80dbStedu errx(1, "msg too large in %s", filename); 182a30f80dbStedu space = sb.st_size + 1; 183a30f80dbStedu } else { 1849083d0fcStedu space = 64 * 1024; 185a30f80dbStedu } 186a30f80dbStedu 1879083d0fcStedu msg = xmalloc(space + 1); 188a30f80dbStedu while (1) { 189a30f80dbStedu if (space == 0) { 190ff9a6787Stedu if (msglen * 2 > maxmsgsize) 1919083d0fcStedu errx(1, "msg too large in %s", filename); 1929083d0fcStedu space = msglen; 193a30f80dbStedu if (!(msg = realloc(msg, msglen + space + 1))) 194a30f80dbStedu errx(1, "realloc"); 195a30f80dbStedu } 196a30f80dbStedu if ((x = read(fd, msg + msglen, space)) == -1) 197a30f80dbStedu err(1, "read from %s", filename); 198a30f80dbStedu if (x == 0) 199a30f80dbStedu break; 200a30f80dbStedu space -= x; 201a30f80dbStedu msglen += x; 202a30f80dbStedu } 203a30f80dbStedu 204d502e9a0Stedu msg[msglen] = '\0'; 2054215a5deStedu close(fd); 2064215a5deStedu 2074215a5deStedu *msglenp = msglen; 2084215a5deStedu return msg; 2094215a5deStedu } 2104215a5deStedu 2114215a5deStedu static void 2120ce08f52Stedu writeall(int fd, const void *buf, size_t buflen, const char *filename) 2134215a5deStedu { 2147dec58f2Stedu ssize_t x; 2157dec58f2Stedu 2160ce08f52Stedu while (buflen != 0) { 2170ce08f52Stedu x = write(fd, buf, buflen); 218bcd4d29fSespie if (x == -1) 219d59d433dSespie err(1, "write to %s", filename); 2200ce08f52Stedu buflen -= x; 221bcd4d29fSespie buf = (char *)buf + x; 222bcd4d29fSespie } 223d59d433dSespie } 2244215a5deStedu 2256ffce13fSderaadt #ifndef VERIFYONLY 2264215a5deStedu static void 2274215a5deStedu writeb64file(const char *filename, const char *comment, const void *buf, 2288e162516Sderaadt size_t buflen, const void *msg, size_t msglen, int oflags, mode_t mode) 2294215a5deStedu { 2304215a5deStedu char header[1024]; 2314215a5deStedu char b64[1024]; 2324215a5deStedu int fd, rv; 2334215a5deStedu 2348e162516Sderaadt fd = xopen(filename, O_CREAT|oflags|O_NOFOLLOW|O_WRONLY, mode); 235ce3e40b1Sderaadt if (snprintf(header, sizeof(header), "%s%s\n", 236ce3e40b1Sderaadt COMMENTHDR, comment) >= sizeof(header)) 23741b393a4Stedu errx(1, "comment too long"); 238d59d433dSespie writeall(fd, header, strlen(header), filename); 2390ce08f52Stedu if ((rv = b64_ntop(buf, buflen, b64, sizeof(b64)-1)) == -1) 240*2c706440Stedu errx(1, "base64 encode failed"); 241e01c72c4Sespie b64[rv++] = '\n'; 242d59d433dSespie writeall(fd, b64, rv, filename); 243c1ca80caStedu explicit_bzero(b64, sizeof(b64)); 2440e8a2786Stedu if (msg) 2450e8a2786Stedu writeall(fd, msg, msglen, filename); 2464215a5deStedu close(fd); 2474215a5deStedu } 2484215a5deStedu 2494215a5deStedu static void 250fdf669dbStedu kdf(uint8_t *salt, size_t saltlen, int rounds, int allowstdin, int confirm, 2514333161aStedu uint8_t *key, size_t keylen) 2524215a5deStedu { 2534215a5deStedu char pass[1024]; 2542adf8afcStedu int rppflags = RPP_ECHO_OFF; 2554215a5deStedu 2564215a5deStedu if (rounds == 0) { 2574215a5deStedu memset(key, 0, keylen); 2584215a5deStedu return; 2594215a5deStedu } 2604215a5deStedu 2614333161aStedu if (allowstdin && !isatty(STDIN_FILENO)) 2622adf8afcStedu rppflags |= RPP_STDIN; 2632adf8afcStedu if (!readpassphrase("passphrase: ", pass, sizeof(pass), rppflags)) 26458ac87a3Stedu errx(1, "unable to read passphrase"); 2650e5a52c1Stedu if (strlen(pass) == 0) 2660e5a52c1Stedu errx(1, "please provide a password"); 267fdf669dbStedu if (confirm && !(rppflags & RPP_STDIN)) { 268fdf669dbStedu char pass2[1024]; 269fdf669dbStedu if (!readpassphrase("confirm passphrase: ", pass2, 270fdf669dbStedu sizeof(pass2), rppflags)) 271fdf669dbStedu errx(1, "unable to read passphrase"); 272fdf669dbStedu if (strcmp(pass, pass2) != 0) 273fdf669dbStedu errx(1, "passwords don't match"); 274fdf669dbStedu explicit_bzero(pass2, sizeof(pass2)); 275fdf669dbStedu } 2764215a5deStedu if (bcrypt_pbkdf(pass, strlen(pass), salt, saltlen, key, 2774215a5deStedu keylen, rounds) == -1) 2784215a5deStedu errx(1, "bcrypt pbkdf"); 279c1ca80caStedu explicit_bzero(pass, sizeof(pass)); 2804215a5deStedu } 2814215a5deStedu 2824215a5deStedu static void 2834215a5deStedu signmsg(uint8_t *seckey, uint8_t *msg, unsigned long long msglen, 2844215a5deStedu uint8_t *sig) 2854215a5deStedu { 2864215a5deStedu unsigned long long siglen; 2874215a5deStedu uint8_t *sigbuf; 2884215a5deStedu 2894215a5deStedu sigbuf = xmalloc(msglen + SIGBYTES); 2904215a5deStedu crypto_sign_ed25519(sigbuf, &siglen, msg, msglen, seckey); 2914215a5deStedu memcpy(sig, sigbuf, SIGBYTES); 2924215a5deStedu free(sigbuf); 2934215a5deStedu } 2944215a5deStedu 2954215a5deStedu static void 296bd7b638bStedu generate(const char *pubkeyfile, const char *seckeyfile, int rounds, 297bd7b638bStedu const char *comment) 2984215a5deStedu { 2994215a5deStedu uint8_t digest[SHA512_DIGEST_LENGTH]; 3004215a5deStedu struct pubkey pubkey; 3014215a5deStedu struct enckey enckey; 3024215a5deStedu uint8_t xorkey[sizeof(enckey.seckey)]; 3031c9c770cStedu uint8_t fingerprint[FPLEN]; 304bd7b638bStedu char commentbuf[COMMENTMAXLEN]; 3054215a5deStedu SHA2_CTX ctx; 3064215a5deStedu int i; 3074215a5deStedu 3084215a5deStedu crypto_sign_ed25519_keypair(pubkey.pubkey, enckey.seckey); 3091c9c770cStedu arc4random_buf(fingerprint, sizeof(fingerprint)); 3104215a5deStedu 3114215a5deStedu SHA512Init(&ctx); 3124215a5deStedu SHA512Update(&ctx, enckey.seckey, sizeof(enckey.seckey)); 3134215a5deStedu SHA512Final(digest, &ctx); 3144215a5deStedu 3154215a5deStedu memcpy(enckey.pkalg, PKALG, 2); 3164215a5deStedu memcpy(enckey.kdfalg, KDFALG, 2); 3174215a5deStedu enckey.kdfrounds = htonl(rounds); 3181c9c770cStedu memcpy(enckey.fingerprint, fingerprint, FPLEN); 3194215a5deStedu arc4random_buf(enckey.salt, sizeof(enckey.salt)); 320fdf669dbStedu kdf(enckey.salt, sizeof(enckey.salt), rounds, 1, 1, xorkey, sizeof(xorkey)); 3214215a5deStedu memcpy(enckey.checksum, digest, sizeof(enckey.checksum)); 3224215a5deStedu for (i = 0; i < sizeof(enckey.seckey); i++) 3234215a5deStedu enckey.seckey[i] ^= xorkey[i]; 324c1ca80caStedu explicit_bzero(digest, sizeof(digest)); 325c1ca80caStedu explicit_bzero(xorkey, sizeof(xorkey)); 3264215a5deStedu 327ce3e40b1Sderaadt if (snprintf(commentbuf, sizeof(commentbuf), "%s secret key", 328ce3e40b1Sderaadt comment) >= sizeof(commentbuf)) 32941b393a4Stedu errx(1, "comment too long"); 330bd7b638bStedu writeb64file(seckeyfile, commentbuf, &enckey, 3310e8a2786Stedu sizeof(enckey), NULL, 0, O_EXCL, 0600); 332c1ca80caStedu explicit_bzero(&enckey, sizeof(enckey)); 3334215a5deStedu 3344215a5deStedu memcpy(pubkey.pkalg, PKALG, 2); 3351c9c770cStedu memcpy(pubkey.fingerprint, fingerprint, FPLEN); 336ce3e40b1Sderaadt if (snprintf(commentbuf, sizeof(commentbuf), "%s public key", 337ce3e40b1Sderaadt comment) >= sizeof(commentbuf)) 33841b393a4Stedu errx(1, "comment too long"); 339bd7b638bStedu writeb64file(pubkeyfile, commentbuf, &pubkey, 3400e8a2786Stedu sizeof(pubkey), NULL, 0, O_EXCL, 0666); 3414215a5deStedu } 3424215a5deStedu 3434215a5deStedu static void 34427f66874Stedu sign(const char *seckeyfile, const char *msgfile, const char *sigfile, 34527f66874Stedu int embedded) 3464215a5deStedu { 3474215a5deStedu struct sig sig; 3484215a5deStedu uint8_t digest[SHA512_DIGEST_LENGTH]; 3494215a5deStedu struct enckey enckey; 3504215a5deStedu uint8_t xorkey[sizeof(enckey.seckey)]; 3514215a5deStedu uint8_t *msg; 3520f212b48Stedu char comment[COMMENTMAXLEN], sigcomment[COMMENTMAXLEN]; 3530f212b48Stedu char *secname; 3544215a5deStedu unsigned long long msglen; 3554215a5deStedu int i, rounds; 3564215a5deStedu SHA2_CTX ctx; 3574215a5deStedu 3581453d2a0Stedu readb64file(seckeyfile, &enckey, sizeof(enckey), comment); 3594215a5deStedu 3607d7c2057Stedu if (memcmp(enckey.kdfalg, KDFALG, 2) != 0) 3614215a5deStedu errx(1, "unsupported KDF"); 3624215a5deStedu rounds = ntohl(enckey.kdfrounds); 3634333161aStedu kdf(enckey.salt, sizeof(enckey.salt), rounds, strcmp(msgfile, "-") != 0, 364fdf669dbStedu 0, xorkey, sizeof(xorkey)); 3654215a5deStedu for (i = 0; i < sizeof(enckey.seckey); i++) 3664215a5deStedu enckey.seckey[i] ^= xorkey[i]; 367c1ca80caStedu explicit_bzero(xorkey, sizeof(xorkey)); 3684215a5deStedu SHA512Init(&ctx); 3694215a5deStedu SHA512Update(&ctx, enckey.seckey, sizeof(enckey.seckey)); 3704215a5deStedu SHA512Final(digest, &ctx); 3717d7c2057Stedu if (memcmp(enckey.checksum, digest, sizeof(enckey.checksum)) != 0) 3724215a5deStedu errx(1, "incorrect passphrase"); 373c1ca80caStedu explicit_bzero(digest, sizeof(digest)); 3744215a5deStedu 37527f66874Stedu msg = readmsg(msgfile, &msglen); 3764215a5deStedu 3774215a5deStedu signmsg(enckey.seckey, msg, msglen, sig.sig); 3781c9c770cStedu memcpy(sig.fingerprint, enckey.fingerprint, FPLEN); 379c1ca80caStedu explicit_bzero(&enckey, sizeof(enckey)); 3804215a5deStedu 3814215a5deStedu memcpy(sig.pkalg, PKALG, 2); 3820f212b48Stedu if ((secname = strstr(seckeyfile, ".sec")) && strlen(secname) == 4) { 3830f212b48Stedu if (snprintf(sigcomment, sizeof(sigcomment), VERIFYWITH "%.*s.pub", 384265537e5Stedu (int)strlen(seckeyfile) - 4, seckeyfile) >= sizeof(sigcomment)) 38541b393a4Stedu errx(1, "comment too long"); 3860f212b48Stedu } else { 387ce3e40b1Sderaadt if (snprintf(sigcomment, sizeof(sigcomment), "signature from %s", 388ce3e40b1Sderaadt comment) >= sizeof(sigcomment)) 38941b393a4Stedu errx(1, "comment too long"); 3900f212b48Stedu } 39127f66874Stedu if (embedded) 3920e8a2786Stedu writeb64file(sigfile, sigcomment, &sig, sizeof(sig), msg, 3930e8a2786Stedu msglen, O_TRUNC, 0666); 3940e8a2786Stedu else 3950e8a2786Stedu writeb64file(sigfile, sigcomment, &sig, sizeof(sig), NULL, 3960e8a2786Stedu 0, O_TRUNC, 0666); 3974215a5deStedu 3984215a5deStedu free(msg); 3994215a5deStedu } 40037f70c32Stedu 40137f70c32Stedu static void 40237f70c32Stedu inspect(const char *seckeyfile, const char *pubkeyfile, const char *sigfile) 40337f70c32Stedu { 40437f70c32Stedu struct sig sig; 40537f70c32Stedu struct enckey enckey; 40637f70c32Stedu struct pubkey pubkey; 40737f70c32Stedu char fp[(FPLEN + 2) / 3 * 4 + 1]; 40837f70c32Stedu 40937f70c32Stedu if (seckeyfile) { 41037f70c32Stedu readb64file(seckeyfile, &enckey, sizeof(enckey), NULL); 41137f70c32Stedu b64_ntop(enckey.fingerprint, FPLEN, fp, sizeof(fp)); 41237f70c32Stedu printf("sec fp: %s\n", fp); 41337f70c32Stedu } 41437f70c32Stedu if (pubkeyfile) { 41537f70c32Stedu readb64file(pubkeyfile, &pubkey, sizeof(pubkey), NULL); 41637f70c32Stedu b64_ntop(pubkey.fingerprint, FPLEN, fp, sizeof(fp)); 41737f70c32Stedu printf("pub fp: %s\n", fp); 41837f70c32Stedu } 41937f70c32Stedu if (sigfile) { 42037f70c32Stedu readb64file(sigfile, &sig, sizeof(sig), NULL); 42137f70c32Stedu b64_ntop(sig.fingerprint, FPLEN, fp, sizeof(fp)); 42237f70c32Stedu printf("sig fp: %s\n", fp); 42337f70c32Stedu } 42437f70c32Stedu } 425665ab7d9Stedu #endif 4264215a5deStedu 4274215a5deStedu static void 42848336e31Stedu verifymsg(struct pubkey *pubkey, uint8_t *msg, unsigned long long msglen, 42948336e31Stedu struct sig *sig, int quiet) 4301c9c770cStedu { 4311c9c770cStedu uint8_t *sigbuf, *dummybuf; 4321c9c770cStedu unsigned long long siglen, dummylen; 4331c9c770cStedu 4347d7c2057Stedu if (memcmp(pubkey->fingerprint, sig->fingerprint, FPLEN) != 0) 43548336e31Stedu errx(1, "verification failed: checked against wrong key"); 43648336e31Stedu 4371c9c770cStedu siglen = SIGBYTES + msglen; 4381c9c770cStedu sigbuf = xmalloc(siglen); 4391c9c770cStedu dummybuf = xmalloc(siglen); 44048336e31Stedu memcpy(sigbuf, sig->sig, SIGBYTES); 4411c9c770cStedu memcpy(sigbuf + SIGBYTES, msg, msglen); 4421c9c770cStedu if (crypto_sign_ed25519_open(dummybuf, &dummylen, sigbuf, siglen, 44348336e31Stedu pubkey->pubkey) == -1) 4441c9c770cStedu errx(1, "signature verification failed"); 44558559f60Stedu if (!quiet) 44658559f60Stedu printf("Signature Verified\n"); 4471c9c770cStedu free(sigbuf); 4481c9c770cStedu free(dummybuf); 4491c9c770cStedu } 4501c9c770cStedu 4511c9c770cStedu static void 45226d9395eStedu readpubkey(const char *pubkeyfile, struct pubkey *pubkey, 45326d9395eStedu const char *sigcomment) 4544215a5deStedu { 455f65d31e6Stedu const char *safepath = "/etc/signify/"; 45627f66874Stedu 4570f212b48Stedu if (!pubkeyfile) { 45826d9395eStedu if ((pubkeyfile = strstr(sigcomment, VERIFYWITH))) { 4590f212b48Stedu pubkeyfile += strlen(VERIFYWITH); 460f65d31e6Stedu if (strncmp(pubkeyfile, safepath, strlen(safepath)) != 0 || 461febc8181Stedu strstr(pubkeyfile, "/../") != NULL) 462b0b02d10Stedu errx(1, "untrusted path %s", pubkeyfile); 463b0b02d10Stedu } else 4645d586c2bStedu usage("must specify pubkey"); 4650f212b48Stedu } 46626d9395eStedu readb64file(pubkeyfile, pubkey, sizeof(*pubkey), NULL); 46726d9395eStedu } 46826d9395eStedu 46926d9395eStedu static void 47026d9395eStedu verifysimple(const char *pubkeyfile, const char *msgfile, const char *sigfile, 47126d9395eStedu int quiet) 47226d9395eStedu { 47326d9395eStedu char sigcomment[COMMENTMAXLEN]; 47426d9395eStedu struct sig sig; 47526d9395eStedu struct pubkey pubkey; 47626d9395eStedu unsigned long long msglen; 47726d9395eStedu uint8_t *msg; 47826d9395eStedu 47926d9395eStedu msg = readmsg(msgfile, &msglen); 48026d9395eStedu 48126d9395eStedu readb64file(sigfile, &sig, sizeof(sig), sigcomment); 48226d9395eStedu readpubkey(pubkeyfile, &pubkey, sigcomment); 4834215a5deStedu 48448336e31Stedu verifymsg(&pubkey, msg, msglen, &sig, quiet); 485ffebbc61Stedu 486ffebbc61Stedu free(msg); 487ffebbc61Stedu } 488ffebbc61Stedu 489ffebbc61Stedu static uint8_t * 490ffebbc61Stedu verifyembedded(const char *pubkeyfile, const char *sigfile, 491ffebbc61Stedu int quiet, unsigned long long *msglenp) 492ffebbc61Stedu { 49326d9395eStedu char sigcomment[COMMENTMAXLEN]; 494ffebbc61Stedu struct sig sig; 495ffebbc61Stedu struct pubkey pubkey; 496ffebbc61Stedu unsigned long long msglen, siglen; 497ffebbc61Stedu uint8_t *msg; 498ffebbc61Stedu 499ffebbc61Stedu msg = readmsg(sigfile, &msglen); 500ffebbc61Stedu 50126d9395eStedu siglen = parseb64file(sigfile, msg, &sig, sizeof(sig), sigcomment); 50226d9395eStedu readpubkey(pubkeyfile, &pubkey, sigcomment); 50326d9395eStedu 504ffebbc61Stedu msglen -= siglen; 505ffebbc61Stedu memmove(msg, msg + siglen, msglen); 506ffebbc61Stedu msg[msglen] = 0; 507ffebbc61Stedu 508ffebbc61Stedu verifymsg(&pubkey, msg, msglen, &sig, quiet); 509ffebbc61Stedu 510ffebbc61Stedu *msglenp = msglen; 511ffebbc61Stedu return msg; 512ffebbc61Stedu } 513ffebbc61Stedu 514ffebbc61Stedu static void 515ffebbc61Stedu verify(const char *pubkeyfile, const char *msgfile, const char *sigfile, 516ffebbc61Stedu int embedded, int quiet) 517ffebbc61Stedu { 518ffebbc61Stedu unsigned long long msglen; 519ffebbc61Stedu uint8_t *msg; 520ffebbc61Stedu int fd; 521ffebbc61Stedu 52227f66874Stedu if (embedded) { 523ffebbc61Stedu msg = verifyembedded(pubkeyfile, sigfile, quiet, &msglen); 5249831e76dStedu fd = xopen(msgfile, O_CREAT|O_TRUNC|O_NOFOLLOW|O_WRONLY, 0666); 52527f66874Stedu writeall(fd, msg, msglen, msgfile); 52687e034adSespie free(msg); 52727f66874Stedu close(fd); 528ffebbc61Stedu } else { 529ffebbc61Stedu verifysimple(pubkeyfile, msgfile, sigfile, quiet); 53027f66874Stedu } 5314215a5deStedu } 5324215a5deStedu 53358559f60Stedu #ifndef VERIFYONLY 53458559f60Stedu struct checksum { 53558559f60Stedu char file[1024]; 536032a32d9Stedu char hash[224]; 537032a32d9Stedu char algo[32]; 53858559f60Stedu }; 53958559f60Stedu 54058559f60Stedu static void 5410050332bStedu recodehash(char *hash) 5420050332bStedu { 543032a32d9Stedu uint8_t data[112]; 5440050332bStedu int i, rv; 5450050332bStedu 5460050332bStedu if (strlen(hash) == SHA256_DIGEST_STRING_LENGTH || 5470050332bStedu strlen(hash) == SHA512_DIGEST_STRING_LENGTH) 5480050332bStedu return; 5490050332bStedu if ((rv = b64_pton(hash, data, sizeof(data))) == -1) 5500050332bStedu errx(1, "invalid base64 encoding"); 5510050332bStedu for (i = 0; i < rv; i++) 5520050332bStedu snprintf(hash + i * 2, 1024 - i * 2, "%2.2x", data[i]); 5530050332bStedu } 5540050332bStedu 5550050332bStedu static void 556a40f4206Stedu verifychecksums(char *msg, int argc, char **argv, int quiet) 55758559f60Stedu { 55858559f60Stedu char buf[1024]; 559a40f4206Stedu char *line, *endline; 56058559f60Stedu struct checksum *checksums = NULL, *c = NULL; 561bfc490c6Stedu int nchecksums = 0, checksumspace = 0; 5622dd83db0Stedu int i, j, rv, uselist, count, hasfailed; 56358559f60Stedu int *failures; 56458559f60Stedu 565a40f4206Stedu line = msg; 56658559f60Stedu while (line && *line) { 567bfc490c6Stedu if (nchecksums == checksumspace) { 568bfc490c6Stedu checksumspace = 2 * (nchecksums + 1); 569f46a86e7Stedu if (!(checksums = reallocarray(checksums, 570bfc490c6Stedu checksumspace, sizeof(*checksums)))) 57158559f60Stedu err(1, "realloc"); 572bfc490c6Stedu } 57358559f60Stedu c = &checksums[nchecksums++]; 57458559f60Stedu if ((endline = strchr(line, '\n'))) 5752dd83db0Stedu *endline++ = '\0'; 576032a32d9Stedu rv = sscanf(line, "%31s %1023s = %223s", 5772dd83db0Stedu c->algo, buf, c->hash); 5782dd83db0Stedu if (rv != 3 || buf[0] != '(' || buf[strlen(buf) - 1] != ')') 57958559f60Stedu errx(1, "unable to parse checksum line %s", line); 5800050332bStedu recodehash(c->hash); 58158559f60Stedu buf[strlen(buf) - 1] = 0; 58258559f60Stedu strlcpy(c->file, buf + 1, sizeof(c->file)); 58358559f60Stedu line = endline; 58458559f60Stedu } 58558559f60Stedu 58658559f60Stedu if (argc) { 58758559f60Stedu uselist = 0; 58858559f60Stedu count = argc; 58958559f60Stedu } else { 59058559f60Stedu uselist = 1; 59158559f60Stedu count = nchecksums; 59258559f60Stedu } 593c8d42ad7Stedu if (!(failures = calloc(count, sizeof(*failures)))) 5948972cca3Stedu err(1, "calloc"); 59558559f60Stedu for (i = 0; i < count; i++) { 59658559f60Stedu if (uselist) { 59758559f60Stedu c = &checksums[i]; 59858559f60Stedu } else { 59958559f60Stedu for (j = 0; j < nchecksums; j++) { 60058559f60Stedu c = &checksums[j]; 60158559f60Stedu if (strcmp(c->file, argv[i]) == 0) 60258559f60Stedu break; 60358559f60Stedu } 60458559f60Stedu if (j == nchecksums) { 60558559f60Stedu failures[i] = 1; 60658559f60Stedu continue; 60758559f60Stedu } 60858559f60Stedu } 60958559f60Stedu 61058559f60Stedu if (strcmp(c->algo, "SHA256") == 0) { 61158559f60Stedu if (!SHA256File(c->file, buf)) { 61258559f60Stedu failures[i] = 1; 61358559f60Stedu continue; 61458559f60Stedu } 61558559f60Stedu } else if (strcmp(c->algo, "SHA512") == 0) { 61658559f60Stedu if (!SHA512File(c->file, buf)) { 61758559f60Stedu failures[i] = 1; 61858559f60Stedu continue; 61958559f60Stedu } 62058559f60Stedu } else { 62158559f60Stedu errx(1, "can't handle algorithm %s", c->algo); 62258559f60Stedu } 62358559f60Stedu if (strcmp(c->hash, buf) != 0) { 62458559f60Stedu failures[i] = 1; 62558559f60Stedu continue; 62658559f60Stedu } 62758559f60Stedu if (!quiet) 62858559f60Stedu printf("%s: OK\n", c->file); 62958559f60Stedu } 6308972cca3Stedu hasfailed = 0; 63158559f60Stedu for (i = 0; i < count; i++) { 63258559f60Stedu if (failures[i]) { 63358559f60Stedu fprintf(stderr, "%s: FAIL\n", 63458559f60Stedu uselist ? checksums[i].file : argv[i]); 6358972cca3Stedu hasfailed = 1; 63658559f60Stedu } 63758559f60Stedu } 6388972cca3Stedu if (hasfailed) 63958559f60Stedu exit(1); 64058559f60Stedu free(checksums); 6418972cca3Stedu free(failures); 64258559f60Stedu } 64358559f60Stedu 64458559f60Stedu static void 64558559f60Stedu check(const char *pubkeyfile, const char *sigfile, int quiet, int argc, 64658559f60Stedu char **argv) 64758559f60Stedu { 648ffebbc61Stedu unsigned long long msglen; 64958559f60Stedu uint8_t *msg; 65058559f60Stedu 651ffebbc61Stedu msg = verifyembedded(pubkeyfile, sigfile, quiet, &msglen); 652a40f4206Stedu verifychecksums((char *)msg, argc, argv, quiet); 65358559f60Stedu 654ffebbc61Stedu free(msg); 65558559f60Stedu } 65658559f60Stedu #endif 65758559f60Stedu 6584215a5deStedu int 6594215a5deStedu main(int argc, char **argv) 6604215a5deStedu { 66127f66874Stedu const char *pubkeyfile = NULL, *seckeyfile = NULL, *msgfile = NULL, 6624215a5deStedu *sigfile = NULL; 6634215a5deStedu char sigfilebuf[1024]; 664bd7b638bStedu const char *comment = "signify"; 6654215a5deStedu int ch, rounds; 66627f66874Stedu int embedded = 0; 66758559f60Stedu int quiet = 0; 668a6bade58Stedu enum { 669a6bade58Stedu NONE, 67058559f60Stedu CHECK, 671a6bade58Stedu GENERATE, 67237f70c32Stedu INSPECT, 673a6bade58Stedu SIGN, 674a6bade58Stedu VERIFY 675a6bade58Stedu } verb = NONE; 676a6bade58Stedu 6774215a5deStedu 6784215a5deStedu rounds = 42; 6794215a5deStedu 68058559f60Stedu while ((ch = getopt(argc, argv, "CGISVc:em:np:qs:x:")) != -1) { 6814215a5deStedu switch (ch) { 682665ab7d9Stedu #ifndef VERIFYONLY 68358559f60Stedu case 'C': 68458559f60Stedu if (verb) 68558559f60Stedu usage(NULL); 68658559f60Stedu verb = CHECK; 68758559f60Stedu break; 688a6bade58Stedu case 'G': 689a6bade58Stedu if (verb) 690f2adbe28Stedu usage(NULL); 691a6bade58Stedu verb = GENERATE; 6924215a5deStedu break; 69337f70c32Stedu case 'I': 69437f70c32Stedu if (verb) 695f2adbe28Stedu usage(NULL); 69637f70c32Stedu verb = INSPECT; 69737f70c32Stedu break; 6984215a5deStedu case 'S': 699a6bade58Stedu if (verb) 700f2adbe28Stedu usage(NULL); 701a6bade58Stedu verb = SIGN; 7024215a5deStedu break; 703665ab7d9Stedu #endif 7044215a5deStedu case 'V': 705a6bade58Stedu if (verb) 706f2adbe28Stedu usage(NULL); 707a6bade58Stedu verb = VERIFY; 708a6bade58Stedu break; 709bd7b638bStedu case 'c': 710bd7b638bStedu comment = optarg; 711bd7b638bStedu break; 71227f66874Stedu case 'e': 71327f66874Stedu embedded = 1; 71427f66874Stedu break; 715f2adbe28Stedu case 'm': 716f2adbe28Stedu msgfile = optarg; 717f2adbe28Stedu break; 718a6bade58Stedu case 'n': 719a6bade58Stedu rounds = 0; 720a6bade58Stedu break; 721a6bade58Stedu case 'p': 722a6bade58Stedu pubkeyfile = optarg; 723a6bade58Stedu break; 72458559f60Stedu case 'q': 72558559f60Stedu quiet = 1; 72658559f60Stedu break; 727a6bade58Stedu case 's': 728a6bade58Stedu seckeyfile = optarg; 7294215a5deStedu break; 730f2adbe28Stedu case 'x': 731f2adbe28Stedu sigfile = optarg; 732f2adbe28Stedu break; 7334215a5deStedu default: 734f2adbe28Stedu usage(NULL); 7354215a5deStedu break; 7364215a5deStedu } 7374215a5deStedu } 738bcc39c47Stedu argc -= optind; 73942efb9f2Sespie argv += optind; 74042efb9f2Sespie 74158559f60Stedu #ifndef VERIFYONLY 74258559f60Stedu if (verb == CHECK) { 74358559f60Stedu if (!pubkeyfile || !sigfile) 7445d586c2bStedu usage("must specify pubkey and sigfile"); 74558559f60Stedu check(pubkeyfile, sigfile, quiet, argc, argv); 74658559f60Stedu return 0; 74758559f60Stedu } 74858559f60Stedu #endif 74958559f60Stedu 750f2adbe28Stedu if (argc != 0) 751f2adbe28Stedu usage(NULL); 752f2adbe28Stedu 7539127dd95Stedu if (!sigfile && msgfile) { 754f2adbe28Stedu if (strcmp(msgfile, "-") == 0) 7555d586c2bStedu usage("must specify sigfile with - message"); 7564215a5deStedu if (snprintf(sigfilebuf, sizeof(sigfilebuf), "%s.sig", 75727f66874Stedu msgfile) >= sizeof(sigfilebuf)) 7584215a5deStedu errx(1, "path too long"); 7594215a5deStedu sigfile = sigfilebuf; 7604215a5deStedu } 7619127dd95Stedu 7629127dd95Stedu switch (verb) { 763665ab7d9Stedu #ifndef VERIFYONLY 7649127dd95Stedu case GENERATE: 7659127dd95Stedu if (!pubkeyfile || !seckeyfile) 7665d586c2bStedu usage("must specify pubkey and seckey"); 7679127dd95Stedu generate(pubkeyfile, seckeyfile, rounds, comment); 7689127dd95Stedu break; 7699127dd95Stedu case INSPECT: 7709127dd95Stedu inspect(seckeyfile, pubkeyfile, sigfile); 7719127dd95Stedu break; 7729127dd95Stedu case SIGN: 7739127dd95Stedu if (!msgfile || !seckeyfile) 7745d586c2bStedu usage("must specify message and seckey"); 77527f66874Stedu sign(seckeyfile, msgfile, sigfile, embedded); 7769127dd95Stedu break; 777665ab7d9Stedu #endif 7789127dd95Stedu case VERIFY: 7790f212b48Stedu if (!msgfile) 7805d586c2bStedu usage("must specify message"); 78158559f60Stedu verify(pubkeyfile, msgfile, sigfile, embedded, quiet); 7829127dd95Stedu break; 7839127dd95Stedu default: 7849127dd95Stedu usage(NULL); 7859127dd95Stedu break; 78642efb9f2Sespie } 78742efb9f2Sespie 7884215a5deStedu return 0; 7894215a5deStedu } 790