1*dba2cf70Stedu /* $OpenBSD: signify.c,v 1.89 2014/05/30 21:17:42 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" 34e96025a1Sespie #ifndef VERIFY_ONLY 35e96025a1Sespie #include <stdint.h> 36e96025a1Sespie #include <stddef.h> 37e96025a1Sespie #include <ohash.h> 38e96025a1Sespie #endif 394215a5deStedu 404215a5deStedu #define SIGBYTES crypto_sign_ed25519_BYTES 414215a5deStedu #define SECRETBYTES crypto_sign_ed25519_SECRETKEYBYTES 424215a5deStedu #define PUBLICBYTES crypto_sign_ed25519_PUBLICKEYBYTES 434215a5deStedu 444215a5deStedu #define PKALG "Ed" 454215a5deStedu #define KDFALG "BK" 461c9c770cStedu #define FPLEN 8 471c9c770cStedu 481c9c770cStedu #define COMMENTHDR "untrusted comment: " 491453d2a0Stedu #define COMMENTHDRLEN 19 501453d2a0Stedu #define COMMENTMAXLEN 1024 510f212b48Stedu #define VERIFYWITH "verify with " 524215a5deStedu 534215a5deStedu struct enckey { 544215a5deStedu uint8_t pkalg[2]; 554215a5deStedu uint8_t kdfalg[2]; 564215a5deStedu uint32_t kdfrounds; 574215a5deStedu uint8_t salt[16]; 584215a5deStedu uint8_t checksum[8]; 591c9c770cStedu uint8_t fingerprint[FPLEN]; 604215a5deStedu uint8_t seckey[SECRETBYTES]; 614215a5deStedu }; 624215a5deStedu 634215a5deStedu struct pubkey { 644215a5deStedu uint8_t pkalg[2]; 651c9c770cStedu uint8_t fingerprint[FPLEN]; 664215a5deStedu uint8_t pubkey[PUBLICBYTES]; 674215a5deStedu }; 684215a5deStedu 694215a5deStedu struct sig { 704215a5deStedu uint8_t pkalg[2]; 711c9c770cStedu uint8_t fingerprint[FPLEN]; 724215a5deStedu uint8_t sig[SIGBYTES]; 734215a5deStedu }; 744215a5deStedu 754215a5deStedu extern char *__progname; 764215a5deStedu 774215a5deStedu static void 78f2adbe28Stedu usage(const char *error) 794215a5deStedu { 80f2adbe28Stedu if (error) 81f2adbe28Stedu fprintf(stderr, "%s\n", error); 8242efb9f2Sespie fprintf(stderr, "usage:" 8335e4c3d2Sespie #ifndef VERIFYONLY 84c374df80Snaddy "\t%1$s -C [-q] -p pubkey -x sigfile [file ...]\n" 85f2adbe28Stedu "\t%1$s -G [-n] [-c comment] -p pubkey -s seckey\n" 86f2adbe28Stedu "\t%1$s -I [-p pubkey] [-s seckey] [-x sigfile]\n" 87f2adbe28Stedu "\t%1$s -S [-e] [-x sigfile] -s seckey -m message\n" 8835e4c3d2Sespie #endif 8958559f60Stedu "\t%1$s -V [-eq] [-x sigfile] -p pubkey -m message\n", 9035e4c3d2Sespie __progname); 914215a5deStedu exit(1); 924215a5deStedu } 934215a5deStedu 944215a5deStedu static int 958e162516Sderaadt xopen(const char *fname, int oflags, mode_t mode) 964215a5deStedu { 97ead0b14bStedu struct stat sb; 984215a5deStedu int fd; 994215a5deStedu 100f2adbe28Stedu if (strcmp(fname, "-") == 0) { 1018e162516Sderaadt if ((oflags & O_WRONLY)) 102f2adbe28Stedu fd = dup(STDOUT_FILENO); 103f2adbe28Stedu else 104f2adbe28Stedu fd = dup(STDIN_FILENO); 105f2adbe28Stedu if (fd == -1) 106f2adbe28Stedu err(1, "dup failed"); 107f2adbe28Stedu } else { 1088e162516Sderaadt fd = open(fname, oflags, mode); 1094215a5deStedu if (fd == -1) 110f2adbe28Stedu err(1, "can't open %s for %s", fname, 1118e162516Sderaadt (oflags & O_WRONLY) ? "writing" : "reading"); 112f2adbe28Stedu } 113ead0b14bStedu if (fstat(fd, &sb) == -1 || S_ISDIR(sb.st_mode)) 1145d586c2bStedu errx(1, "not a valid file: %s", fname); 1154215a5deStedu return fd; 1164215a5deStedu } 1174215a5deStedu 1184215a5deStedu static void * 1194215a5deStedu xmalloc(size_t len) 1204215a5deStedu { 1214215a5deStedu void *p; 1224215a5deStedu 123*dba2cf70Stedu if (!(p = malloc(len))) 1244215a5deStedu err(1, "malloc %zu", len); 1254215a5deStedu return p; 1264215a5deStedu } 1274215a5deStedu 12827f66874Stedu static size_t 1290ce08f52Stedu parseb64file(const char *filename, char *b64, void *buf, size_t buflen, 1301453d2a0Stedu char *comment) 13127f66874Stedu { 13227f66874Stedu char *commentend, *b64end; 13327f66874Stedu 13427f66874Stedu commentend = strchr(b64, '\n'); 13527f66874Stedu if (!commentend || commentend - b64 <= COMMENTHDRLEN || 1367d7c2057Stedu memcmp(b64, COMMENTHDR, COMMENTHDRLEN) != 0) 13727f66874Stedu errx(1, "invalid comment in %s; must start with '%s'", 13827f66874Stedu filename, COMMENTHDR); 139d502e9a0Stedu *commentend = '\0'; 140ce3e40b1Sderaadt if (comment) { 141ce3e40b1Sderaadt if (strlcpy(comment, b64 + COMMENTHDRLEN, 142ce3e40b1Sderaadt COMMENTMAXLEN) >= COMMENTMAXLEN) 14341b393a4Stedu errx(1, "comment too long"); 144ce3e40b1Sderaadt } 145*dba2cf70Stedu if (!(b64end = strchr(commentend + 1, '\n'))) 1462c706440Stedu errx(1, "missing new line after base64 in %s", filename); 147d502e9a0Stedu *b64end = '\0'; 148e67d6036Stedu if (b64_pton(commentend + 1, buf, buflen) != buflen) 1492c706440Stedu errx(1, "invalid base64 encoding in %s", filename); 1507d7c2057Stedu if (memcmp(buf, PKALG, 2) != 0) 15127f66874Stedu errx(1, "unsupported file %s", filename); 15227f66874Stedu return b64end - b64 + 1; 15327f66874Stedu } 15427f66874Stedu 1554215a5deStedu static void 1560ce08f52Stedu readb64file(const char *filename, void *buf, size_t buflen, char *comment) 1574215a5deStedu { 1584215a5deStedu char b64[2048]; 159f030c3d3Stedu int rv, fd; 1604215a5deStedu 1614215a5deStedu fd = xopen(filename, O_RDONLY | O_NOFOLLOW, 0); 162*dba2cf70Stedu if ((rv = read(fd, b64, sizeof(b64) - 1)) == -1) 163d59d433dSespie err(1, "read from %s", filename); 164d502e9a0Stedu b64[rv] = '\0'; 1650ce08f52Stedu parseb64file(filename, b64, buf, buflen, comment); 166c1ca80caStedu explicit_bzero(b64, sizeof(b64)); 1674215a5deStedu close(fd); 1684215a5deStedu } 1694215a5deStedu 170eee7f9deStedu static uint8_t * 1714215a5deStedu readmsg(const char *filename, unsigned long long *msglenp) 1724215a5deStedu { 173a30f80dbStedu unsigned long long msglen = 0; 174a30f80dbStedu uint8_t *msg = NULL; 1754215a5deStedu struct stat sb; 176a30f80dbStedu ssize_t x, space; 1774215a5deStedu int fd; 178ff9a6787Stedu const unsigned long long maxmsgsize = 1UL << 30; 1794215a5deStedu 1804215a5deStedu fd = xopen(filename, O_RDONLY | O_NOFOLLOW, 0); 181a30f80dbStedu if (fstat(fd, &sb) == 0 && S_ISREG(sb.st_mode)) { 182ff9a6787Stedu if (sb.st_size > maxmsgsize) 183a30f80dbStedu errx(1, "msg too large in %s", filename); 184a30f80dbStedu space = sb.st_size + 1; 185a30f80dbStedu } else { 1869083d0fcStedu space = 64 * 1024; 187a30f80dbStedu } 188a30f80dbStedu 1899083d0fcStedu msg = xmalloc(space + 1); 190a30f80dbStedu while (1) { 191a30f80dbStedu if (space == 0) { 192ff9a6787Stedu if (msglen * 2 > maxmsgsize) 1939083d0fcStedu errx(1, "msg too large in %s", filename); 1949083d0fcStedu space = msglen; 195a30f80dbStedu if (!(msg = realloc(msg, msglen + space + 1))) 196a30f80dbStedu errx(1, "realloc"); 197a30f80dbStedu } 198a30f80dbStedu if ((x = read(fd, msg + msglen, space)) == -1) 199a30f80dbStedu err(1, "read from %s", filename); 200a30f80dbStedu if (x == 0) 201a30f80dbStedu break; 202a30f80dbStedu space -= x; 203a30f80dbStedu msglen += x; 204a30f80dbStedu } 205a30f80dbStedu 206d502e9a0Stedu msg[msglen] = '\0'; 2074215a5deStedu close(fd); 2084215a5deStedu 2094215a5deStedu *msglenp = msglen; 2104215a5deStedu return msg; 2114215a5deStedu } 2124215a5deStedu 2134215a5deStedu static void 2140ce08f52Stedu writeall(int fd, const void *buf, size_t buflen, const char *filename) 2154215a5deStedu { 2167dec58f2Stedu ssize_t x; 2177dec58f2Stedu 2180ce08f52Stedu while (buflen != 0) { 219*dba2cf70Stedu if ((x = write(fd, buf, buflen)) == -1) 220d59d433dSespie err(1, "write to %s", filename); 2210ce08f52Stedu buflen -= x; 222bcd4d29fSespie buf = (char *)buf + x; 223bcd4d29fSespie } 224d59d433dSespie } 2254215a5deStedu 2266ffce13fSderaadt #ifndef VERIFYONLY 2274215a5deStedu static void 2284215a5deStedu writeb64file(const char *filename, const char *comment, const void *buf, 2298e162516Sderaadt size_t buflen, const void *msg, size_t msglen, int oflags, mode_t mode) 2304215a5deStedu { 2314215a5deStedu char header[1024]; 2324215a5deStedu char b64[1024]; 2334215a5deStedu int fd, rv; 2344215a5deStedu 2358e162516Sderaadt fd = xopen(filename, O_CREAT|oflags|O_NOFOLLOW|O_WRONLY, mode); 236ce3e40b1Sderaadt if (snprintf(header, sizeof(header), "%s%s\n", 237ce3e40b1Sderaadt COMMENTHDR, comment) >= sizeof(header)) 23841b393a4Stedu errx(1, "comment too long"); 239d59d433dSespie writeall(fd, header, strlen(header), filename); 2400ce08f52Stedu if ((rv = b64_ntop(buf, buflen, b64, sizeof(b64)-1)) == -1) 2412c706440Stedu errx(1, "base64 encode failed"); 242e01c72c4Sespie b64[rv++] = '\n'; 243d59d433dSespie writeall(fd, b64, rv, filename); 244c1ca80caStedu explicit_bzero(b64, sizeof(b64)); 2450e8a2786Stedu if (msg) 2460e8a2786Stedu writeall(fd, msg, msglen, filename); 2474215a5deStedu close(fd); 2484215a5deStedu } 2494215a5deStedu 2504215a5deStedu static void 251fdf669dbStedu kdf(uint8_t *salt, size_t saltlen, int rounds, int allowstdin, int confirm, 2524333161aStedu uint8_t *key, size_t keylen) 2534215a5deStedu { 2544215a5deStedu char pass[1024]; 2552adf8afcStedu int rppflags = RPP_ECHO_OFF; 2564215a5deStedu 2574215a5deStedu if (rounds == 0) { 2584215a5deStedu memset(key, 0, keylen); 2594215a5deStedu return; 2604215a5deStedu } 2614215a5deStedu 2624333161aStedu if (allowstdin && !isatty(STDIN_FILENO)) 2632adf8afcStedu rppflags |= RPP_STDIN; 2642adf8afcStedu if (!readpassphrase("passphrase: ", pass, sizeof(pass), rppflags)) 26558ac87a3Stedu errx(1, "unable to read passphrase"); 2660e5a52c1Stedu if (strlen(pass) == 0) 2670e5a52c1Stedu errx(1, "please provide a password"); 268fdf669dbStedu if (confirm && !(rppflags & RPP_STDIN)) { 269fdf669dbStedu char pass2[1024]; 270fdf669dbStedu if (!readpassphrase("confirm passphrase: ", pass2, 271fdf669dbStedu sizeof(pass2), rppflags)) 272fdf669dbStedu errx(1, "unable to read passphrase"); 273fdf669dbStedu if (strcmp(pass, pass2) != 0) 274fdf669dbStedu errx(1, "passwords don't match"); 275fdf669dbStedu explicit_bzero(pass2, sizeof(pass2)); 276fdf669dbStedu } 2774215a5deStedu if (bcrypt_pbkdf(pass, strlen(pass), salt, saltlen, key, 2784215a5deStedu keylen, rounds) == -1) 2794215a5deStedu errx(1, "bcrypt pbkdf"); 280c1ca80caStedu explicit_bzero(pass, sizeof(pass)); 2814215a5deStedu } 2824215a5deStedu 2834215a5deStedu static void 2844215a5deStedu signmsg(uint8_t *seckey, uint8_t *msg, unsigned long long msglen, 2854215a5deStedu uint8_t *sig) 2864215a5deStedu { 2874215a5deStedu unsigned long long siglen; 2884215a5deStedu uint8_t *sigbuf; 2894215a5deStedu 2904215a5deStedu sigbuf = xmalloc(msglen + SIGBYTES); 2914215a5deStedu crypto_sign_ed25519(sigbuf, &siglen, msg, msglen, seckey); 2924215a5deStedu memcpy(sig, sigbuf, SIGBYTES); 2934215a5deStedu free(sigbuf); 2944215a5deStedu } 2954215a5deStedu 2964215a5deStedu static void 297bd7b638bStedu generate(const char *pubkeyfile, const char *seckeyfile, int rounds, 298bd7b638bStedu const char *comment) 2994215a5deStedu { 3004215a5deStedu uint8_t digest[SHA512_DIGEST_LENGTH]; 3014215a5deStedu struct pubkey pubkey; 3024215a5deStedu struct enckey enckey; 3034215a5deStedu uint8_t xorkey[sizeof(enckey.seckey)]; 3041c9c770cStedu uint8_t fingerprint[FPLEN]; 305bd7b638bStedu char commentbuf[COMMENTMAXLEN]; 3064215a5deStedu SHA2_CTX ctx; 3074215a5deStedu int i; 3084215a5deStedu 3094215a5deStedu crypto_sign_ed25519_keypair(pubkey.pubkey, enckey.seckey); 3101c9c770cStedu arc4random_buf(fingerprint, sizeof(fingerprint)); 3114215a5deStedu 3124215a5deStedu SHA512Init(&ctx); 3134215a5deStedu SHA512Update(&ctx, enckey.seckey, sizeof(enckey.seckey)); 3144215a5deStedu SHA512Final(digest, &ctx); 3154215a5deStedu 3164215a5deStedu memcpy(enckey.pkalg, PKALG, 2); 3174215a5deStedu memcpy(enckey.kdfalg, KDFALG, 2); 3184215a5deStedu enckey.kdfrounds = htonl(rounds); 3191c9c770cStedu memcpy(enckey.fingerprint, fingerprint, FPLEN); 3204215a5deStedu arc4random_buf(enckey.salt, sizeof(enckey.salt)); 321fdf669dbStedu kdf(enckey.salt, sizeof(enckey.salt), rounds, 1, 1, xorkey, sizeof(xorkey)); 3224215a5deStedu memcpy(enckey.checksum, digest, sizeof(enckey.checksum)); 3234215a5deStedu for (i = 0; i < sizeof(enckey.seckey); i++) 3244215a5deStedu enckey.seckey[i] ^= xorkey[i]; 325c1ca80caStedu explicit_bzero(digest, sizeof(digest)); 326c1ca80caStedu explicit_bzero(xorkey, sizeof(xorkey)); 3274215a5deStedu 328ce3e40b1Sderaadt if (snprintf(commentbuf, sizeof(commentbuf), "%s secret key", 329ce3e40b1Sderaadt comment) >= sizeof(commentbuf)) 33041b393a4Stedu errx(1, "comment too long"); 331bd7b638bStedu writeb64file(seckeyfile, commentbuf, &enckey, 3320e8a2786Stedu sizeof(enckey), NULL, 0, O_EXCL, 0600); 333c1ca80caStedu explicit_bzero(&enckey, sizeof(enckey)); 3344215a5deStedu 3354215a5deStedu memcpy(pubkey.pkalg, PKALG, 2); 3361c9c770cStedu memcpy(pubkey.fingerprint, fingerprint, FPLEN); 337ce3e40b1Sderaadt if (snprintf(commentbuf, sizeof(commentbuf), "%s public key", 338ce3e40b1Sderaadt comment) >= sizeof(commentbuf)) 33941b393a4Stedu errx(1, "comment too long"); 340bd7b638bStedu writeb64file(pubkeyfile, commentbuf, &pubkey, 3410e8a2786Stedu sizeof(pubkey), NULL, 0, O_EXCL, 0666); 3424215a5deStedu } 3434215a5deStedu 3444215a5deStedu static void 34527f66874Stedu sign(const char *seckeyfile, const char *msgfile, const char *sigfile, 34627f66874Stedu int embedded) 3474215a5deStedu { 3484215a5deStedu struct sig sig; 3494215a5deStedu uint8_t digest[SHA512_DIGEST_LENGTH]; 3504215a5deStedu struct enckey enckey; 3514215a5deStedu uint8_t xorkey[sizeof(enckey.seckey)]; 3524215a5deStedu uint8_t *msg; 3530f212b48Stedu char comment[COMMENTMAXLEN], sigcomment[COMMENTMAXLEN]; 3540f212b48Stedu char *secname; 3554215a5deStedu unsigned long long msglen; 3564215a5deStedu int i, rounds; 3574215a5deStedu SHA2_CTX ctx; 3584215a5deStedu 3591453d2a0Stedu readb64file(seckeyfile, &enckey, sizeof(enckey), comment); 3604215a5deStedu 3617d7c2057Stedu if (memcmp(enckey.kdfalg, KDFALG, 2) != 0) 3624215a5deStedu errx(1, "unsupported KDF"); 3634215a5deStedu rounds = ntohl(enckey.kdfrounds); 3644333161aStedu kdf(enckey.salt, sizeof(enckey.salt), rounds, strcmp(msgfile, "-") != 0, 365fdf669dbStedu 0, xorkey, sizeof(xorkey)); 3664215a5deStedu for (i = 0; i < sizeof(enckey.seckey); i++) 3674215a5deStedu enckey.seckey[i] ^= xorkey[i]; 368c1ca80caStedu explicit_bzero(xorkey, sizeof(xorkey)); 3694215a5deStedu SHA512Init(&ctx); 3704215a5deStedu SHA512Update(&ctx, enckey.seckey, sizeof(enckey.seckey)); 3714215a5deStedu SHA512Final(digest, &ctx); 3727d7c2057Stedu if (memcmp(enckey.checksum, digest, sizeof(enckey.checksum)) != 0) 3734215a5deStedu errx(1, "incorrect passphrase"); 374c1ca80caStedu explicit_bzero(digest, sizeof(digest)); 3754215a5deStedu 37627f66874Stedu msg = readmsg(msgfile, &msglen); 3774215a5deStedu 3784215a5deStedu signmsg(enckey.seckey, msg, msglen, sig.sig); 3791c9c770cStedu memcpy(sig.fingerprint, enckey.fingerprint, FPLEN); 380c1ca80caStedu explicit_bzero(&enckey, sizeof(enckey)); 3814215a5deStedu 3824215a5deStedu memcpy(sig.pkalg, PKALG, 2); 3830f212b48Stedu if ((secname = strstr(seckeyfile, ".sec")) && strlen(secname) == 4) { 3840f212b48Stedu if (snprintf(sigcomment, sizeof(sigcomment), VERIFYWITH "%.*s.pub", 385265537e5Stedu (int)strlen(seckeyfile) - 4, seckeyfile) >= sizeof(sigcomment)) 38641b393a4Stedu errx(1, "comment too long"); 3870f212b48Stedu } else { 388ce3e40b1Sderaadt if (snprintf(sigcomment, sizeof(sigcomment), "signature from %s", 389ce3e40b1Sderaadt comment) >= sizeof(sigcomment)) 39041b393a4Stedu errx(1, "comment too long"); 3910f212b48Stedu } 39227f66874Stedu if (embedded) 3930e8a2786Stedu writeb64file(sigfile, sigcomment, &sig, sizeof(sig), msg, 3940e8a2786Stedu msglen, O_TRUNC, 0666); 3950e8a2786Stedu else 3960e8a2786Stedu writeb64file(sigfile, sigcomment, &sig, sizeof(sig), NULL, 3970e8a2786Stedu 0, O_TRUNC, 0666); 3984215a5deStedu 3994215a5deStedu free(msg); 4004215a5deStedu } 40137f70c32Stedu 40237f70c32Stedu static void 40337f70c32Stedu inspect(const char *seckeyfile, const char *pubkeyfile, const char *sigfile) 40437f70c32Stedu { 40537f70c32Stedu struct sig sig; 40637f70c32Stedu struct enckey enckey; 40737f70c32Stedu struct pubkey pubkey; 40837f70c32Stedu char fp[(FPLEN + 2) / 3 * 4 + 1]; 40937f70c32Stedu 41037f70c32Stedu if (seckeyfile) { 41137f70c32Stedu readb64file(seckeyfile, &enckey, sizeof(enckey), NULL); 41237f70c32Stedu b64_ntop(enckey.fingerprint, FPLEN, fp, sizeof(fp)); 41337f70c32Stedu printf("sec fp: %s\n", fp); 41437f70c32Stedu } 41537f70c32Stedu if (pubkeyfile) { 41637f70c32Stedu readb64file(pubkeyfile, &pubkey, sizeof(pubkey), NULL); 41737f70c32Stedu b64_ntop(pubkey.fingerprint, FPLEN, fp, sizeof(fp)); 41837f70c32Stedu printf("pub fp: %s\n", fp); 41937f70c32Stedu } 42037f70c32Stedu if (sigfile) { 42137f70c32Stedu readb64file(sigfile, &sig, sizeof(sig), NULL); 42237f70c32Stedu b64_ntop(sig.fingerprint, FPLEN, fp, sizeof(fp)); 42337f70c32Stedu printf("sig fp: %s\n", fp); 42437f70c32Stedu } 42537f70c32Stedu } 426665ab7d9Stedu #endif 4274215a5deStedu 4284215a5deStedu static void 42948336e31Stedu verifymsg(struct pubkey *pubkey, uint8_t *msg, unsigned long long msglen, 43048336e31Stedu struct sig *sig, int quiet) 4311c9c770cStedu { 4321c9c770cStedu uint8_t *sigbuf, *dummybuf; 4331c9c770cStedu unsigned long long siglen, dummylen; 4341c9c770cStedu 4357d7c2057Stedu if (memcmp(pubkey->fingerprint, sig->fingerprint, FPLEN) != 0) 43648336e31Stedu errx(1, "verification failed: checked against wrong key"); 43748336e31Stedu 4381c9c770cStedu siglen = SIGBYTES + msglen; 4391c9c770cStedu sigbuf = xmalloc(siglen); 4401c9c770cStedu dummybuf = xmalloc(siglen); 44148336e31Stedu memcpy(sigbuf, sig->sig, SIGBYTES); 4421c9c770cStedu memcpy(sigbuf + SIGBYTES, msg, msglen); 4431c9c770cStedu if (crypto_sign_ed25519_open(dummybuf, &dummylen, sigbuf, siglen, 44448336e31Stedu pubkey->pubkey) == -1) 4451c9c770cStedu errx(1, "signature verification failed"); 44658559f60Stedu if (!quiet) 44758559f60Stedu printf("Signature Verified\n"); 4481c9c770cStedu free(sigbuf); 4491c9c770cStedu free(dummybuf); 4501c9c770cStedu } 4511c9c770cStedu 4521c9c770cStedu static void 45326d9395eStedu readpubkey(const char *pubkeyfile, struct pubkey *pubkey, 45426d9395eStedu const char *sigcomment) 4554215a5deStedu { 456f65d31e6Stedu const char *safepath = "/etc/signify/"; 45727f66874Stedu 4580f212b48Stedu if (!pubkeyfile) { 45926d9395eStedu if ((pubkeyfile = strstr(sigcomment, VERIFYWITH))) { 4600f212b48Stedu pubkeyfile += strlen(VERIFYWITH); 461f65d31e6Stedu if (strncmp(pubkeyfile, safepath, strlen(safepath)) != 0 || 462febc8181Stedu strstr(pubkeyfile, "/../") != NULL) 463b0b02d10Stedu errx(1, "untrusted path %s", pubkeyfile); 464b0b02d10Stedu } else 4655d586c2bStedu usage("must specify pubkey"); 4660f212b48Stedu } 46726d9395eStedu readb64file(pubkeyfile, pubkey, sizeof(*pubkey), NULL); 46826d9395eStedu } 46926d9395eStedu 47026d9395eStedu static void 47126d9395eStedu verifysimple(const char *pubkeyfile, const char *msgfile, const char *sigfile, 47226d9395eStedu int quiet) 47326d9395eStedu { 47426d9395eStedu char sigcomment[COMMENTMAXLEN]; 47526d9395eStedu struct sig sig; 47626d9395eStedu struct pubkey pubkey; 47726d9395eStedu unsigned long long msglen; 47826d9395eStedu uint8_t *msg; 47926d9395eStedu 48026d9395eStedu msg = readmsg(msgfile, &msglen); 48126d9395eStedu 48226d9395eStedu readb64file(sigfile, &sig, sizeof(sig), sigcomment); 48326d9395eStedu readpubkey(pubkeyfile, &pubkey, sigcomment); 4844215a5deStedu 48548336e31Stedu verifymsg(&pubkey, msg, msglen, &sig, quiet); 486ffebbc61Stedu 487ffebbc61Stedu free(msg); 488ffebbc61Stedu } 489ffebbc61Stedu 490ffebbc61Stedu static uint8_t * 491ffebbc61Stedu verifyembedded(const char *pubkeyfile, const char *sigfile, 492ffebbc61Stedu int quiet, unsigned long long *msglenp) 493ffebbc61Stedu { 49426d9395eStedu char sigcomment[COMMENTMAXLEN]; 495ffebbc61Stedu struct sig sig; 496ffebbc61Stedu struct pubkey pubkey; 497ffebbc61Stedu unsigned long long msglen, siglen; 498ffebbc61Stedu uint8_t *msg; 499ffebbc61Stedu 500ffebbc61Stedu msg = readmsg(sigfile, &msglen); 501ffebbc61Stedu 50226d9395eStedu siglen = parseb64file(sigfile, msg, &sig, sizeof(sig), sigcomment); 50326d9395eStedu readpubkey(pubkeyfile, &pubkey, sigcomment); 50426d9395eStedu 505ffebbc61Stedu msglen -= siglen; 506ffebbc61Stedu memmove(msg, msg + siglen, msglen); 507ffebbc61Stedu msg[msglen] = 0; 508ffebbc61Stedu 509ffebbc61Stedu verifymsg(&pubkey, msg, msglen, &sig, quiet); 510ffebbc61Stedu 511ffebbc61Stedu *msglenp = msglen; 512ffebbc61Stedu return msg; 513ffebbc61Stedu } 514ffebbc61Stedu 515ffebbc61Stedu static void 516ffebbc61Stedu verify(const char *pubkeyfile, const char *msgfile, const char *sigfile, 517ffebbc61Stedu int embedded, int quiet) 518ffebbc61Stedu { 519ffebbc61Stedu unsigned long long msglen; 520ffebbc61Stedu uint8_t *msg; 521ffebbc61Stedu int fd; 522ffebbc61Stedu 52327f66874Stedu if (embedded) { 524ffebbc61Stedu msg = verifyembedded(pubkeyfile, sigfile, quiet, &msglen); 5259831e76dStedu fd = xopen(msgfile, O_CREAT|O_TRUNC|O_NOFOLLOW|O_WRONLY, 0666); 52627f66874Stedu writeall(fd, msg, msglen, msgfile); 52787e034adSespie free(msg); 52827f66874Stedu close(fd); 529ffebbc61Stedu } else { 530ffebbc61Stedu verifysimple(pubkeyfile, msgfile, sigfile, quiet); 53127f66874Stedu } 5324215a5deStedu } 5334215a5deStedu 53458559f60Stedu #ifndef VERIFYONLY 535ce955b95Stedu #define HASHBUFSIZE 224 53658559f60Stedu struct checksum { 53758559f60Stedu char file[1024]; 538ce955b95Stedu char hash[HASHBUFSIZE]; 539032a32d9Stedu char algo[32]; 54058559f60Stedu }; 54158559f60Stedu 542e96025a1Sespie static void * 543e96025a1Sespie ecalloc(size_t s1, size_t s2, void *data) 544e96025a1Sespie { 545db6a8127Stedu void *p; 546db6a8127Stedu 547*dba2cf70Stedu if (!(p = calloc(s1, s2))) 548e96025a1Sespie err(1, "calloc"); 549e96025a1Sespie return p; 550e96025a1Sespie } 551e96025a1Sespie 55258559f60Stedu static void 553e96025a1Sespie efree(void *p, void *data) 554e96025a1Sespie { 555e96025a1Sespie free(p); 556e96025a1Sespie } 557e96025a1Sespie 558e96025a1Sespie static void 559e96025a1Sespie recodehash(char *hash, size_t len) 5600050332bStedu { 561ce955b95Stedu uint8_t data[HASHBUFSIZE / 2]; 5620050332bStedu int i, rv; 5630050332bStedu 564e96025a1Sespie if (strlen(hash) == len) 5650050332bStedu return; 5660050332bStedu if ((rv = b64_pton(hash, data, sizeof(data))) == -1) 5670050332bStedu errx(1, "invalid base64 encoding"); 5680050332bStedu for (i = 0; i < rv; i++) 569ce955b95Stedu snprintf(hash + i * 2, HASHBUFSIZE - i * 2, "%2.2x", data[i]); 5700050332bStedu } 5710050332bStedu 572e96025a1Sespie static int 573e96025a1Sespie verifychecksum(struct checksum *c, int quiet) 57458559f60Stedu { 575e96025a1Sespie char buf[HASHBUFSIZE]; 576db6a8127Stedu 57758559f60Stedu if (strcmp(c->algo, "SHA256") == 0) { 578e96025a1Sespie recodehash(c->hash, SHA256_DIGEST_STRING_LENGTH-1); 579e96025a1Sespie if (!SHA256File(c->file, buf)) 580e96025a1Sespie return 0; 58158559f60Stedu } else if (strcmp(c->algo, "SHA512") == 0) { 582e96025a1Sespie recodehash(c->hash, SHA512_DIGEST_STRING_LENGTH-1); 583e96025a1Sespie if (!SHA512File(c->file, buf)) 584e96025a1Sespie return 0; 58558559f60Stedu } else { 58658559f60Stedu errx(1, "can't handle algorithm %s", c->algo); 58758559f60Stedu } 58858559f60Stedu if (strcmp(c->hash, buf) != 0) { 589e96025a1Sespie return 0; 59058559f60Stedu } 59158559f60Stedu if (!quiet) 59258559f60Stedu printf("%s: OK\n", c->file); 593e96025a1Sespie return 1; 59458559f60Stedu } 595e96025a1Sespie 596e96025a1Sespie static void 597e96025a1Sespie verifychecksums(char *msg, int argc, char **argv, int quiet) 598e96025a1Sespie { 599e96025a1Sespie struct ohash_info info = { 0, NULL, ecalloc, efree, NULL }; 600e96025a1Sespie struct ohash myh; 601e96025a1Sespie struct checksum c; 602a5b6fcd7Stedu char *e, *line, *endline; 603db6a8127Stedu int hasfailed = 0; 604db6a8127Stedu int i, rv; 605e96025a1Sespie unsigned int slot; 606e96025a1Sespie 607e96025a1Sespie ohash_init(&myh, 6, &info); 608a5b6fcd7Stedu if (argc) { 609e96025a1Sespie for (i = 0; i < argc; i++) { 610e96025a1Sespie slot = ohash_qlookup(&myh, argv[i]); 611e96025a1Sespie e = ohash_find(&myh, slot); 612e96025a1Sespie if (e == NULL) 613e96025a1Sespie ohash_insert(&myh, slot, argv[i]); 614e96025a1Sespie } 615e96025a1Sespie } 616e96025a1Sespie 617e96025a1Sespie line = msg; 618e96025a1Sespie while (line && *line) { 619e96025a1Sespie if ((endline = strchr(line, '\n'))) 620e96025a1Sespie *endline++ = '\0'; 621e96025a1Sespie rv = sscanf(line, "%31s (%1023s = %223s", 622e96025a1Sespie c.algo, c.file, c.hash); 623e96025a1Sespie if (rv != 3 || c.file[0] == 0 || c.file[strlen(c.file)-1] != ')') 624e96025a1Sespie errx(1, "unable to parse checksum line %s", line); 625db6a8127Stedu c.file[strlen(c.file) - 1] = '\0'; 626e96025a1Sespie line = endline; 627e96025a1Sespie if (argc) { 628e96025a1Sespie slot = ohash_qlookup(&myh, c.file); 629e96025a1Sespie e = ohash_find(&myh, slot); 630e96025a1Sespie if (e != NULL) { 631db6a8127Stedu if (verifychecksum(&c, quiet) != 0) 632e96025a1Sespie ohash_remove(&myh, slot); 633e96025a1Sespie } 634e96025a1Sespie } else { 635db6a8127Stedu if (verifychecksum(&c, quiet) == 0) { 636a5b6fcd7Stedu slot = ohash_qlookup(&myh, c.file); 637a5b6fcd7Stedu e = ohash_find(&myh, slot); 638a5b6fcd7Stedu if (e == NULL) { 639a5b6fcd7Stedu if (!(e = strdup(c.file))) 640a5b6fcd7Stedu err(1, "strdup"); 641a5b6fcd7Stedu ohash_insert(&myh, slot, e); 642a5b6fcd7Stedu } 64358559f60Stedu } 64458559f60Stedu } 645e96025a1Sespie } 646e96025a1Sespie 647a5b6fcd7Stedu for (e = ohash_first(&myh, &slot); e != NULL; e = ohash_next(&myh, &slot)) { 648a5b6fcd7Stedu fprintf(stderr, "%s: FAIL\n", e); 649e96025a1Sespie hasfailed = 1; 650a5b6fcd7Stedu if (argc == 0) 651a5b6fcd7Stedu free(e); 652e96025a1Sespie } 653e96025a1Sespie ohash_delete(&myh); 6548972cca3Stedu if (hasfailed) 65558559f60Stedu exit(1); 65658559f60Stedu } 65758559f60Stedu 65858559f60Stedu static void 65958559f60Stedu check(const char *pubkeyfile, const char *sigfile, int quiet, int argc, 66058559f60Stedu char **argv) 66158559f60Stedu { 662ffebbc61Stedu unsigned long long msglen; 66358559f60Stedu uint8_t *msg; 66458559f60Stedu 665ffebbc61Stedu msg = verifyembedded(pubkeyfile, sigfile, quiet, &msglen); 666a40f4206Stedu verifychecksums((char *)msg, argc, argv, quiet); 66758559f60Stedu 668ffebbc61Stedu free(msg); 66958559f60Stedu } 67058559f60Stedu #endif 67158559f60Stedu 6724215a5deStedu int 6734215a5deStedu main(int argc, char **argv) 6744215a5deStedu { 67527f66874Stedu const char *pubkeyfile = NULL, *seckeyfile = NULL, *msgfile = NULL, 6764215a5deStedu *sigfile = NULL; 6774215a5deStedu char sigfilebuf[1024]; 678bd7b638bStedu const char *comment = "signify"; 6794215a5deStedu int ch, rounds; 68027f66874Stedu int embedded = 0; 68158559f60Stedu int quiet = 0; 682a6bade58Stedu enum { 683a6bade58Stedu NONE, 68458559f60Stedu CHECK, 685a6bade58Stedu GENERATE, 68637f70c32Stedu INSPECT, 687a6bade58Stedu SIGN, 688a6bade58Stedu VERIFY 689a6bade58Stedu } verb = NONE; 690a6bade58Stedu 6914215a5deStedu 6924215a5deStedu rounds = 42; 6934215a5deStedu 69458559f60Stedu while ((ch = getopt(argc, argv, "CGISVc:em:np:qs:x:")) != -1) { 6954215a5deStedu switch (ch) { 696665ab7d9Stedu #ifndef VERIFYONLY 69758559f60Stedu case 'C': 69858559f60Stedu if (verb) 69958559f60Stedu usage(NULL); 70058559f60Stedu verb = CHECK; 70158559f60Stedu break; 702a6bade58Stedu case 'G': 703a6bade58Stedu if (verb) 704f2adbe28Stedu usage(NULL); 705a6bade58Stedu verb = GENERATE; 7064215a5deStedu break; 70737f70c32Stedu case 'I': 70837f70c32Stedu if (verb) 709f2adbe28Stedu usage(NULL); 71037f70c32Stedu verb = INSPECT; 71137f70c32Stedu break; 7124215a5deStedu case 'S': 713a6bade58Stedu if (verb) 714f2adbe28Stedu usage(NULL); 715a6bade58Stedu verb = SIGN; 7164215a5deStedu break; 717665ab7d9Stedu #endif 7184215a5deStedu case 'V': 719a6bade58Stedu if (verb) 720f2adbe28Stedu usage(NULL); 721a6bade58Stedu verb = VERIFY; 722a6bade58Stedu break; 723bd7b638bStedu case 'c': 724bd7b638bStedu comment = optarg; 725bd7b638bStedu break; 72627f66874Stedu case 'e': 72727f66874Stedu embedded = 1; 72827f66874Stedu break; 729f2adbe28Stedu case 'm': 730f2adbe28Stedu msgfile = optarg; 731f2adbe28Stedu break; 732a6bade58Stedu case 'n': 733a6bade58Stedu rounds = 0; 734a6bade58Stedu break; 735a6bade58Stedu case 'p': 736a6bade58Stedu pubkeyfile = optarg; 737a6bade58Stedu break; 73858559f60Stedu case 'q': 73958559f60Stedu quiet = 1; 74058559f60Stedu break; 741a6bade58Stedu case 's': 742a6bade58Stedu seckeyfile = optarg; 7434215a5deStedu break; 744f2adbe28Stedu case 'x': 745f2adbe28Stedu sigfile = optarg; 746f2adbe28Stedu break; 7474215a5deStedu default: 748f2adbe28Stedu usage(NULL); 7494215a5deStedu break; 7504215a5deStedu } 7514215a5deStedu } 752bcc39c47Stedu argc -= optind; 75342efb9f2Sespie argv += optind; 75442efb9f2Sespie 75558559f60Stedu #ifndef VERIFYONLY 75658559f60Stedu if (verb == CHECK) { 7579fbe12ffStedu if (!sigfile) 7589fbe12ffStedu usage("must specify sigfile"); 75958559f60Stedu check(pubkeyfile, sigfile, quiet, argc, argv); 76058559f60Stedu return 0; 76158559f60Stedu } 76258559f60Stedu #endif 76358559f60Stedu 764f2adbe28Stedu if (argc != 0) 765f2adbe28Stedu usage(NULL); 766f2adbe28Stedu 7679127dd95Stedu if (!sigfile && msgfile) { 768f2adbe28Stedu if (strcmp(msgfile, "-") == 0) 7695d586c2bStedu usage("must specify sigfile with - message"); 7704215a5deStedu if (snprintf(sigfilebuf, sizeof(sigfilebuf), "%s.sig", 77127f66874Stedu msgfile) >= sizeof(sigfilebuf)) 7724215a5deStedu errx(1, "path too long"); 7734215a5deStedu sigfile = sigfilebuf; 7744215a5deStedu } 7759127dd95Stedu 7769127dd95Stedu switch (verb) { 777665ab7d9Stedu #ifndef VERIFYONLY 7789127dd95Stedu case GENERATE: 7799127dd95Stedu if (!pubkeyfile || !seckeyfile) 7805d586c2bStedu usage("must specify pubkey and seckey"); 7819127dd95Stedu generate(pubkeyfile, seckeyfile, rounds, comment); 7829127dd95Stedu break; 7839127dd95Stedu case INSPECT: 7849127dd95Stedu inspect(seckeyfile, pubkeyfile, sigfile); 7859127dd95Stedu break; 7869127dd95Stedu case SIGN: 7879127dd95Stedu if (!msgfile || !seckeyfile) 7885d586c2bStedu usage("must specify message and seckey"); 78927f66874Stedu sign(seckeyfile, msgfile, sigfile, embedded); 7909127dd95Stedu break; 791665ab7d9Stedu #endif 7929127dd95Stedu case VERIFY: 7930f212b48Stedu if (!msgfile) 7945d586c2bStedu usage("must specify message"); 79558559f60Stedu verify(pubkeyfile, msgfile, sigfile, embedded, quiet); 7969127dd95Stedu break; 7979127dd95Stedu default: 7989127dd95Stedu usage(NULL); 7999127dd95Stedu break; 80042efb9f2Sespie } 80142efb9f2Sespie 8024215a5deStedu return 0; 8034215a5deStedu } 804