1*d59d433dSespie /* $OpenBSD: signify.c,v 1.7 2014/01/02 16:34:02 espie 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> 264215a5deStedu #include <err.h> 274215a5deStedu #include <unistd.h> 284215a5deStedu #include <readpassphrase.h> 294215a5deStedu #include <util.h> 304215a5deStedu #include <sha2.h> 314215a5deStedu 324215a5deStedu #include "crypto_api.h" 334215a5deStedu 344215a5deStedu #define streq(a, b) (strcmp(a, b) == 0) 354215a5deStedu 364215a5deStedu #define SIGBYTES crypto_sign_ed25519_BYTES 374215a5deStedu #define SECRETBYTES crypto_sign_ed25519_SECRETKEYBYTES 384215a5deStedu #define PUBLICBYTES crypto_sign_ed25519_PUBLICKEYBYTES 394215a5deStedu 404215a5deStedu #define PKALG "Ed" 414215a5deStedu #define KDFALG "BK" 424215a5deStedu 434215a5deStedu struct enckey { 444215a5deStedu uint8_t pkalg[2]; 454215a5deStedu uint8_t kdfalg[2]; 464215a5deStedu uint32_t kdfrounds; 474215a5deStedu uint8_t salt[16]; 484215a5deStedu uint8_t checksum[8]; 494215a5deStedu uint8_t seckey[SECRETBYTES]; 504215a5deStedu }; 514215a5deStedu 524215a5deStedu struct pubkey { 534215a5deStedu uint8_t pkalg[2]; 544215a5deStedu uint8_t pubkey[PUBLICBYTES]; 554215a5deStedu }; 564215a5deStedu 574215a5deStedu struct sig { 584215a5deStedu uint8_t pkalg[2]; 594215a5deStedu uint8_t sig[SIGBYTES]; 604215a5deStedu }; 614215a5deStedu 624215a5deStedu extern char *__progname; 634215a5deStedu 644215a5deStedu static void 654215a5deStedu usage(void) 664215a5deStedu { 67a6bade58Stedu fprintf(stderr, "usage: %s [-n] [-i input] [-o output] [-p pubkey] [-s seckey] " 68a6bade58Stedu "-G | -S | -V\n", __progname); 694215a5deStedu exit(1); 704215a5deStedu } 714215a5deStedu 724215a5deStedu static int 734215a5deStedu xopen(const char *fname, int flags, mode_t mode) 744215a5deStedu { 754215a5deStedu int fd; 764215a5deStedu 774215a5deStedu fd = open(fname, flags, mode); 784215a5deStedu if (fd == -1) 794215a5deStedu err(1, "open %s", fname); 804215a5deStedu return fd; 814215a5deStedu } 824215a5deStedu 834215a5deStedu static void * 844215a5deStedu xmalloc(size_t len) 854215a5deStedu { 864215a5deStedu void *p; 874215a5deStedu 884215a5deStedu p = malloc(len); 894215a5deStedu if (!p) 904215a5deStedu err(1, "malloc %zu", len); 914215a5deStedu return p; 924215a5deStedu } 934215a5deStedu 944215a5deStedu static void 95*d59d433dSespie readall(int fd, void *buf, size_t len, const char *filename) 964215a5deStedu { 97*d59d433dSespie ssize_t x = read(fd, buf, len); 98*d59d433dSespie if (x == -1) { 99*d59d433dSespie err(1, "read from %s", filename); 100*d59d433dSespie } else if (x != len) { 101*d59d433dSespie errx(1, "short read from %s", filename); 102*d59d433dSespie } 1034215a5deStedu } 1044215a5deStedu 1054215a5deStedu static void 1064215a5deStedu readb64file(const char *filename, void *buf, size_t len) 1074215a5deStedu { 1084215a5deStedu char b64[2048]; 1094215a5deStedu int i, rv, fd; 1104215a5deStedu 1114215a5deStedu fd = xopen(filename, O_RDONLY | O_NOFOLLOW, 0); 1124215a5deStedu memset(b64, 0, sizeof(b64)); 1134215a5deStedu rv = read(fd, b64, sizeof(b64) - 1); 1144215a5deStedu if (rv == -1) 115*d59d433dSespie err(1, "read from %s", filename); 1164215a5deStedu for (i = 0; i < rv; i++) 1174215a5deStedu if (b64[i] == '\n') 1184215a5deStedu break; 1194215a5deStedu if (i == rv) 1204215a5deStedu errx(1, "no newline in %s", filename); 1214215a5deStedu rv = b64_pton(b64 + i, buf, len); 1224215a5deStedu if (rv != len) 1234215a5deStedu errx(1, "invalid b64 encoding in %s", filename); 1244215a5deStedu memset(b64, 0, sizeof(b64)); 1254215a5deStedu close(fd); 1264215a5deStedu if (memcmp(buf, PKALG, 2)) 1274215a5deStedu errx(1, "unsupported file %s", filename); 1284215a5deStedu } 1294215a5deStedu 1304215a5deStedu uint8_t * 1314215a5deStedu readmsg(const char *filename, unsigned long long *msglenp) 1324215a5deStedu { 1334215a5deStedu unsigned long long msglen; 1344215a5deStedu uint8_t *msg; 1354215a5deStedu struct stat sb; 1364215a5deStedu int fd; 1374215a5deStedu 1384215a5deStedu fd = xopen(filename, O_RDONLY | O_NOFOLLOW, 0); 1394215a5deStedu fstat(fd, &sb); 1404215a5deStedu msglen = sb.st_size; 1414215a5deStedu if (msglen > (1UL << 30)) 1424215a5deStedu errx(1, "msg too large in %s", filename); 1434215a5deStedu msg = xmalloc(msglen); 144*d59d433dSespie readall(fd, msg, msglen, filename); 1454215a5deStedu close(fd); 1464215a5deStedu 1474215a5deStedu *msglenp = msglen; 1484215a5deStedu return msg; 1494215a5deStedu } 1504215a5deStedu 1514215a5deStedu static void 152*d59d433dSespie writeall(int fd, const void *buf, size_t len, const char *filename) 1534215a5deStedu { 154*d59d433dSespie ssize_t x = write(fd, buf, len); 155*d59d433dSespie if (x == -1) { 156*d59d433dSespie err(1, "write to %s", filename); 157*d59d433dSespie } else if (x != len) { 158*d59d433dSespie errx(1, "short write to %s", filename); 159*d59d433dSespie } 1604215a5deStedu } 1614215a5deStedu 1624215a5deStedu static void 1634215a5deStedu writeb64file(const char *filename, const char *comment, const void *buf, 1644215a5deStedu size_t len, mode_t mode) 1654215a5deStedu { 1664215a5deStedu char header[1024]; 1674215a5deStedu char b64[1024]; 1684215a5deStedu int fd, rv; 1694215a5deStedu 1704215a5deStedu fd = xopen(filename, O_CREAT|O_EXCL|O_NOFOLLOW|O_RDWR, mode); 1714215a5deStedu snprintf(header, sizeof(header), "signify -- %s\n", comment); 172*d59d433dSespie writeall(fd, header, strlen(header), filename); 1734215a5deStedu if ((rv = b64_ntop(buf, len, b64, sizeof(b64))) == -1) 1744215a5deStedu errx(1, "b64 encode failed"); 175*d59d433dSespie writeall(fd, b64, rv, filename); 1764215a5deStedu memset(b64, 0, sizeof(b64)); 1774215a5deStedu close(fd); 1784215a5deStedu } 1794215a5deStedu 1804215a5deStedu static void 1814215a5deStedu kdf(uint8_t *salt, size_t saltlen, int rounds, uint8_t *key, size_t keylen) 1824215a5deStedu { 1834215a5deStedu char pass[1024]; 1844215a5deStedu 1854215a5deStedu if (rounds == 0) { 1864215a5deStedu memset(key, 0, keylen); 1874215a5deStedu return; 1884215a5deStedu } 1894215a5deStedu 1904215a5deStedu if (!readpassphrase("passphrase: ", pass, sizeof(pass), 0)) 1914215a5deStedu errx(1, "readpassphrase"); 1924215a5deStedu if (bcrypt_pbkdf(pass, strlen(pass), salt, saltlen, key, 1934215a5deStedu keylen, rounds) == -1) 1944215a5deStedu errx(1, "bcrypt pbkdf"); 1954215a5deStedu memset(pass, 0, sizeof(pass)); 1964215a5deStedu } 1974215a5deStedu 1984215a5deStedu static void 1994215a5deStedu signmsg(uint8_t *seckey, uint8_t *msg, unsigned long long msglen, 2004215a5deStedu uint8_t *sig) 2014215a5deStedu { 2024215a5deStedu unsigned long long siglen; 2034215a5deStedu uint8_t *sigbuf; 2044215a5deStedu 2054215a5deStedu sigbuf = xmalloc(msglen + SIGBYTES); 2064215a5deStedu crypto_sign_ed25519(sigbuf, &siglen, msg, msglen, seckey); 2074215a5deStedu memcpy(sig, sigbuf, SIGBYTES); 2084215a5deStedu free(sigbuf); 2094215a5deStedu } 2104215a5deStedu 2114215a5deStedu static void 2124215a5deStedu verifymsg(uint8_t *pubkey, uint8_t *msg, unsigned long long msglen, 2134215a5deStedu uint8_t *sig) 2144215a5deStedu { 2154215a5deStedu uint8_t *sigbuf, *dummybuf; 2164215a5deStedu unsigned long long siglen, dummylen; 2174215a5deStedu 2184215a5deStedu siglen = SIGBYTES + msglen; 2194215a5deStedu sigbuf = xmalloc(siglen); 2204215a5deStedu dummybuf = xmalloc(siglen); 2214215a5deStedu memcpy(sigbuf, sig, SIGBYTES); 2224215a5deStedu memcpy(sigbuf + SIGBYTES, msg, msglen); 2234215a5deStedu if (crypto_sign_ed25519_open(dummybuf, &dummylen, sigbuf, siglen, 2244215a5deStedu pubkey) == -1) 2254215a5deStedu errx(1, "signature failed"); 2264215a5deStedu free(sigbuf); 2274215a5deStedu free(dummybuf); 2284215a5deStedu } 2294215a5deStedu 2304215a5deStedu static void 2314215a5deStedu generate(const char *pubkeyfile, const char *seckeyfile, int rounds) 2324215a5deStedu { 2334215a5deStedu uint8_t digest[SHA512_DIGEST_LENGTH]; 2344215a5deStedu struct pubkey pubkey; 2354215a5deStedu struct enckey enckey; 2364215a5deStedu uint8_t xorkey[sizeof(enckey.seckey)]; 2374215a5deStedu SHA2_CTX ctx; 2384215a5deStedu int i; 2394215a5deStedu 2404215a5deStedu crypto_sign_ed25519_keypair(pubkey.pubkey, enckey.seckey); 2414215a5deStedu 2424215a5deStedu SHA512Init(&ctx); 2434215a5deStedu SHA512Update(&ctx, enckey.seckey, sizeof(enckey.seckey)); 2444215a5deStedu SHA512Final(digest, &ctx); 2454215a5deStedu 2464215a5deStedu memcpy(enckey.pkalg, PKALG, 2); 2474215a5deStedu memcpy(enckey.kdfalg, KDFALG, 2); 2484215a5deStedu enckey.kdfrounds = htonl(rounds); 2494215a5deStedu arc4random_buf(enckey.salt, sizeof(enckey.salt)); 2504215a5deStedu kdf(enckey.salt, sizeof(enckey.salt), rounds, xorkey, sizeof(xorkey)); 2514215a5deStedu memcpy(enckey.checksum, digest, sizeof(enckey.checksum)); 2524215a5deStedu for (i = 0; i < sizeof(enckey.seckey); i++) 2534215a5deStedu enckey.seckey[i] ^= xorkey[i]; 2544215a5deStedu memset(digest, 0, sizeof(digest)); 2554215a5deStedu memset(xorkey, 0, sizeof(xorkey)); 2564215a5deStedu 2574215a5deStedu writeb64file(seckeyfile, "secret key", &enckey, 2584215a5deStedu sizeof(enckey), 0600); 2594215a5deStedu memset(&enckey, 0, sizeof(enckey)); 2604215a5deStedu 2614215a5deStedu memcpy(pubkey.pkalg, PKALG, 2); 2624215a5deStedu writeb64file(pubkeyfile, "public key", &pubkey, 2634215a5deStedu sizeof(pubkey), 0666); 2644215a5deStedu } 2654215a5deStedu 2664215a5deStedu static void 2674215a5deStedu sign(const char *seckeyfile, const char *inputfile, const char *sigfile) 2684215a5deStedu { 2694215a5deStedu struct sig sig; 2704215a5deStedu uint8_t digest[SHA512_DIGEST_LENGTH]; 2714215a5deStedu struct enckey enckey; 2724215a5deStedu uint8_t xorkey[sizeof(enckey.seckey)]; 2734215a5deStedu uint8_t *msg; 2744215a5deStedu unsigned long long msglen; 2754215a5deStedu int i, rounds; 2764215a5deStedu SHA2_CTX ctx; 2774215a5deStedu 2784215a5deStedu readb64file(seckeyfile, &enckey, sizeof(enckey)); 2794215a5deStedu 2804215a5deStedu if (memcmp(enckey.kdfalg, KDFALG, 2)) 2814215a5deStedu errx(1, "unsupported KDF"); 2824215a5deStedu rounds = ntohl(enckey.kdfrounds); 2834215a5deStedu kdf(enckey.salt, sizeof(enckey.salt), rounds, xorkey, sizeof(xorkey)); 2844215a5deStedu for (i = 0; i < sizeof(enckey.seckey); i++) 2854215a5deStedu enckey.seckey[i] ^= xorkey[i]; 2864215a5deStedu memset(xorkey, 0, sizeof(xorkey)); 2874215a5deStedu SHA512Init(&ctx); 2884215a5deStedu SHA512Update(&ctx, enckey.seckey, sizeof(enckey.seckey)); 2894215a5deStedu SHA512Final(digest, &ctx); 2904215a5deStedu if (memcmp(enckey.checksum, digest, sizeof(enckey.checksum))) 2914215a5deStedu errx(1, "incorrect passphrase"); 2924215a5deStedu memset(digest, 0, sizeof(digest)); 2934215a5deStedu 2944215a5deStedu msg = readmsg(inputfile, &msglen); 2954215a5deStedu 2964215a5deStedu signmsg(enckey.seckey, msg, msglen, sig.sig); 2974215a5deStedu memset(&enckey, 0, sizeof(enckey)); 2984215a5deStedu 2994215a5deStedu memcpy(sig.pkalg, PKALG, 2); 3004215a5deStedu writeb64file(sigfile, "signature", &sig, sizeof(sig), 0666); 3014215a5deStedu 3024215a5deStedu free(msg); 3034215a5deStedu } 3044215a5deStedu 3054215a5deStedu static void 3064215a5deStedu verify(const char *pubkeyfile, const char *inputfile, const char *sigfile) 3074215a5deStedu { 3084215a5deStedu struct sig sig; 3094215a5deStedu struct pubkey pubkey; 3104215a5deStedu unsigned long long msglen; 3114215a5deStedu uint8_t *msg; 3124215a5deStedu 3134215a5deStedu readb64file(pubkeyfile, &pubkey, sizeof(pubkey)); 3144215a5deStedu readb64file(sigfile, &sig, sizeof(sig)); 3154215a5deStedu 3164215a5deStedu msg = readmsg(inputfile, &msglen); 3174215a5deStedu 3184215a5deStedu verifymsg(pubkey.pubkey, msg, msglen, sig.sig); 3194215a5deStedu printf("verified\n"); 3204215a5deStedu 3214215a5deStedu free(msg); 3224215a5deStedu } 3234215a5deStedu 3244215a5deStedu int 3254215a5deStedu main(int argc, char **argv) 3264215a5deStedu { 3274215a5deStedu const char *pubkeyfile = NULL, *seckeyfile = NULL, *inputfile = NULL, 3284215a5deStedu *sigfile = NULL; 3294215a5deStedu char sigfilebuf[1024]; 3304215a5deStedu int ch, rounds; 331a6bade58Stedu enum { 332a6bade58Stedu NONE, 333a6bade58Stedu GENERATE, 334a6bade58Stedu SIGN, 335a6bade58Stedu VERIFY 336a6bade58Stedu } verb = NONE; 337a6bade58Stedu 3384215a5deStedu 3394215a5deStedu rounds = 42; 3404215a5deStedu 341a6bade58Stedu while ((ch = getopt(argc, argv, "GSVi:no:p:s:")) != -1) { 3424215a5deStedu switch (ch) { 343a6bade58Stedu case 'G': 344a6bade58Stedu if (verb) 345a6bade58Stedu usage(); 346a6bade58Stedu verb = GENERATE; 3474215a5deStedu break; 3484215a5deStedu case 'S': 349a6bade58Stedu if (verb) 350a6bade58Stedu usage(); 351a6bade58Stedu verb = SIGN; 3524215a5deStedu break; 3534215a5deStedu case 'V': 354a6bade58Stedu if (verb) 355a6bade58Stedu usage(); 356a6bade58Stedu verb = VERIFY; 357a6bade58Stedu break; 358a6bade58Stedu case 'i': 359a6bade58Stedu inputfile = optarg; 360a6bade58Stedu break; 361a6bade58Stedu case 'n': 362a6bade58Stedu rounds = 0; 363a6bade58Stedu break; 364a6bade58Stedu case 'o': 365a6bade58Stedu sigfile = optarg; 366a6bade58Stedu break; 367a6bade58Stedu case 'p': 368a6bade58Stedu pubkeyfile = optarg; 369a6bade58Stedu break; 370a6bade58Stedu case 's': 371a6bade58Stedu seckeyfile = optarg; 3724215a5deStedu break; 3734215a5deStedu default: 3744215a5deStedu usage(); 3754215a5deStedu break; 3764215a5deStedu } 3774215a5deStedu } 378bcc39c47Stedu argc -= optind; 379a6bade58Stedu if (argc != 0) 3804215a5deStedu usage(); 3814215a5deStedu 3824215a5deStedu if (inputfile && !sigfile) { 3834215a5deStedu if (snprintf(sigfilebuf, sizeof(sigfilebuf), "%s.sig", 384bcc39c47Stedu inputfile) >= sizeof(sigfilebuf)) 3854215a5deStedu errx(1, "path too long"); 3864215a5deStedu sigfile = sigfilebuf; 3874215a5deStedu } 3884215a5deStedu 389a6bade58Stedu if (verb == GENERATE) { 3904215a5deStedu if (!pubkeyfile || !seckeyfile) 3914215a5deStedu usage(); 3924215a5deStedu generate(pubkeyfile, seckeyfile, rounds); 393a6bade58Stedu } else if (verb == SIGN) { 3944215a5deStedu if (!seckeyfile || !inputfile) 3954215a5deStedu usage(); 3964215a5deStedu sign(seckeyfile, inputfile, sigfile); 397a6bade58Stedu } else if (verb == VERIFY) { 3984215a5deStedu if (!pubkeyfile || !inputfile) 3994215a5deStedu usage(); 4004215a5deStedu verify(pubkeyfile, inputfile, sigfile); 4014215a5deStedu } else { 4024215a5deStedu usage(); 4034215a5deStedu } 4044215a5deStedu return 0; 4054215a5deStedu } 406