1*0274047dSderaadt /* $OpenBSD: signify.c,v 1.133 2019/09/09 13:50:06 deraadt 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" 83c610539bStedu "\t%1$s -S [-enz] [-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) 145f87dcb1dStedu errx(1, "unable to parse %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))) 19264e2033cStedu err(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); 2477c51e329Sderaadt freezero(header, strlen(header)); 2484215a5deStedu close(fd); 2494215a5deStedu } 2504215a5deStedu 2514215a5deStedu static void 252fdf669dbStedu kdf(uint8_t *salt, size_t saltlen, int rounds, int allowstdin, int confirm, 2534333161aStedu uint8_t *key, size_t keylen) 2544215a5deStedu { 2554215a5deStedu char pass[1024]; 2562adf8afcStedu int rppflags = RPP_ECHO_OFF; 2573873fb89Stedu const char *errstr = NULL; 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)) 2743873fb89Stedu errstr = "unable to read passphrase"; 2753873fb89Stedu if (!errstr && strcmp(pass, pass2) != 0) 2763873fb89Stedu errstr = "passwords don't match"; 277fdf669dbStedu explicit_bzero(pass2, sizeof(pass2)); 278fdf669dbStedu } 2793873fb89Stedu if (!errstr && bcrypt_pbkdf(pass, strlen(pass), salt, saltlen, key, 2804215a5deStedu keylen, rounds) == -1) 2813873fb89Stedu errstr = "bcrypt pbkdf"; 282c1ca80caStedu explicit_bzero(pass, sizeof(pass)); 2833873fb89Stedu if (errstr) 2843873fb89Stedu errx(1, "%s", errstr); 2854215a5deStedu } 2864215a5deStedu 2874215a5deStedu static void 2884215a5deStedu signmsg(uint8_t *seckey, uint8_t *msg, unsigned long long msglen, 2894215a5deStedu uint8_t *sig) 2904215a5deStedu { 2914215a5deStedu unsigned long long siglen; 2924215a5deStedu uint8_t *sigbuf; 2934215a5deStedu 2944215a5deStedu sigbuf = xmalloc(msglen + SIGBYTES); 2954215a5deStedu crypto_sign_ed25519(sigbuf, &siglen, msg, msglen, seckey); 2964215a5deStedu memcpy(sig, sigbuf, SIGBYTES); 2974215a5deStedu free(sigbuf); 2984215a5deStedu } 2994215a5deStedu 3004215a5deStedu static void 301bd7b638bStedu generate(const char *pubkeyfile, const char *seckeyfile, int rounds, 302bd7b638bStedu const char *comment) 3034215a5deStedu { 3044215a5deStedu uint8_t digest[SHA512_DIGEST_LENGTH]; 3054215a5deStedu struct pubkey pubkey; 3064215a5deStedu struct enckey enckey; 3074215a5deStedu uint8_t xorkey[sizeof(enckey.seckey)]; 308534dbeadStedu uint8_t keynum[KEYNUMLEN]; 309bd7b638bStedu char commentbuf[COMMENTMAXLEN]; 3104215a5deStedu SHA2_CTX ctx; 311b3fe1a3aStedu int i, nr; 3124215a5deStedu 3134215a5deStedu crypto_sign_ed25519_keypair(pubkey.pubkey, enckey.seckey); 314534dbeadStedu arc4random_buf(keynum, sizeof(keynum)); 3154215a5deStedu 3164215a5deStedu SHA512Init(&ctx); 3174215a5deStedu SHA512Update(&ctx, enckey.seckey, sizeof(enckey.seckey)); 3184215a5deStedu SHA512Final(digest, &ctx); 3194215a5deStedu 3204215a5deStedu memcpy(enckey.pkalg, PKALG, 2); 3214215a5deStedu memcpy(enckey.kdfalg, KDFALG, 2); 3224215a5deStedu enckey.kdfrounds = htonl(rounds); 323534dbeadStedu memcpy(enckey.keynum, keynum, KEYNUMLEN); 3244215a5deStedu arc4random_buf(enckey.salt, sizeof(enckey.salt)); 325fdf669dbStedu kdf(enckey.salt, sizeof(enckey.salt), rounds, 1, 1, xorkey, sizeof(xorkey)); 3264215a5deStedu memcpy(enckey.checksum, digest, sizeof(enckey.checksum)); 3274215a5deStedu for (i = 0; i < sizeof(enckey.seckey); i++) 3284215a5deStedu enckey.seckey[i] ^= xorkey[i]; 329c1ca80caStedu explicit_bzero(digest, sizeof(digest)); 330c1ca80caStedu explicit_bzero(xorkey, sizeof(xorkey)); 3314215a5deStedu 332924f56e4Stedu nr = snprintf(commentbuf, sizeof(commentbuf), "%s secret key", comment); 333515e489cSderaadt if (nr < 0 || nr >= sizeof(commentbuf)) 33441b393a4Stedu errx(1, "comment too long"); 3359b252276Stedu writekeyfile(seckeyfile, commentbuf, &enckey, 3369b252276Stedu sizeof(enckey), O_EXCL, 0600); 337c1ca80caStedu explicit_bzero(&enckey, sizeof(enckey)); 3384215a5deStedu 3394215a5deStedu memcpy(pubkey.pkalg, PKALG, 2); 340534dbeadStedu memcpy(pubkey.keynum, keynum, KEYNUMLEN); 341924f56e4Stedu nr = snprintf(commentbuf, sizeof(commentbuf), "%s public key", comment); 342515e489cSderaadt if (nr < 0 || nr >= sizeof(commentbuf)) 34341b393a4Stedu errx(1, "comment too long"); 3449b252276Stedu writekeyfile(pubkeyfile, commentbuf, &pubkey, 3459b252276Stedu sizeof(pubkey), O_EXCL, 0666); 3469b252276Stedu } 3479b252276Stedu 3483a4a9206Sespie static const char * 349763e1148Stedu check_keyname_compliance(const char *pubkeyfile, const char *seckeyfile) 350763e1148Stedu { 3513a4a9206Sespie const char *pos; 352c5c473cbStedu size_t len; 353763e1148Stedu 3543a4a9206Sespie /* basename may or may not modify input */ 3553a4a9206Sespie pos = strrchr(seckeyfile, '/'); 3563a4a9206Sespie if (pos != NULL) 3573a4a9206Sespie seckeyfile = pos + 1; 3583a4a9206Sespie 3593a4a9206Sespie len = strlen(seckeyfile); 360763e1148Stedu if (len < 5) /* ?.key */ 361763e1148Stedu goto bad; 3623a4a9206Sespie if (strcmp(seckeyfile + len - 4, ".sec") != 0) 3633a4a9206Sespie goto bad; 3643a4a9206Sespie if (pubkeyfile != NULL) { 3653a4a9206Sespie pos = strrchr(pubkeyfile, '/'); 3663a4a9206Sespie if (pos != NULL) 3673a4a9206Sespie pubkeyfile = pos + 1; 3683a4a9206Sespie 3693a4a9206Sespie if (strlen(pubkeyfile) != len) 3703a4a9206Sespie goto bad; 3713a4a9206Sespie if (strcmp(pubkeyfile + len - 4, ".pub") != 0) 372763e1148Stedu goto bad; 373763e1148Stedu if (strncmp(pubkeyfile, seckeyfile, len - 4) != 0) 374763e1148Stedu goto bad; 3753a4a9206Sespie } 376763e1148Stedu 3773a4a9206Sespie return seckeyfile; 378763e1148Stedu bad: 379763e1148Stedu errx(1, "please use naming scheme of keyname.pub and keyname.sec"); 380763e1148Stedu } 381763e1148Stedu 3823ccd7401Sespie uint8_t * 383ff9e4f8aStedu createsig(const char *seckeyfile, const char *msgfile, uint8_t *msg, 384ff9e4f8aStedu unsigned long long msglen) 3854215a5deStedu { 3864215a5deStedu struct enckey enckey; 387ff9e4f8aStedu uint8_t xorkey[sizeof(enckey.seckey)]; 388ff9e4f8aStedu struct sig sig; 3899b252276Stedu char *sighdr; 390ff9e4f8aStedu uint8_t digest[SHA512_DIGEST_LENGTH]; 391ff9e4f8aStedu int i, nr, rounds; 392ff9e4f8aStedu SHA2_CTX ctx; 3939b252276Stedu char comment[COMMENTMAXLEN], sigcomment[COMMENTMAXLEN]; 3944215a5deStedu 3951453d2a0Stedu readb64file(seckeyfile, &enckey, sizeof(enckey), comment); 3964215a5deStedu 3973a4a9206Sespie if (strcmp(seckeyfile, "-") == 0) { 398924f56e4Stedu nr = snprintf(sigcomment, sizeof(sigcomment), 399924f56e4Stedu "signature from %s", comment); 4003a4a9206Sespie } else { 4013a4a9206Sespie const char *keyname = check_keyname_compliance(NULL, 4023a4a9206Sespie seckeyfile); 4033a4a9206Sespie nr = snprintf(sigcomment, sizeof(sigcomment), 4043a4a9206Sespie VERIFYWITH "%.*s.pub", (int)strlen(keyname) - 4, keyname); 4053a4a9206Sespie } 406515e489cSderaadt if (nr < 0 || nr >= sizeof(sigcomment)) 40741b393a4Stedu errx(1, "comment too long"); 4089b252276Stedu 409ff9e4f8aStedu if (memcmp(enckey.kdfalg, KDFALG, 2) != 0) 410ff9e4f8aStedu errx(1, "unsupported KDF"); 411ff9e4f8aStedu rounds = ntohl(enckey.kdfrounds); 412ff9e4f8aStedu kdf(enckey.salt, sizeof(enckey.salt), rounds, strcmp(msgfile, "-") != 0, 413ff9e4f8aStedu 0, xorkey, sizeof(xorkey)); 414ff9e4f8aStedu for (i = 0; i < sizeof(enckey.seckey); i++) 415ff9e4f8aStedu enckey.seckey[i] ^= xorkey[i]; 416ff9e4f8aStedu explicit_bzero(xorkey, sizeof(xorkey)); 417ff9e4f8aStedu SHA512Init(&ctx); 418ff9e4f8aStedu SHA512Update(&ctx, enckey.seckey, sizeof(enckey.seckey)); 419ff9e4f8aStedu SHA512Final(digest, &ctx); 420ff9e4f8aStedu if (memcmp(enckey.checksum, digest, sizeof(enckey.checksum)) != 0) 421ff9e4f8aStedu errx(1, "incorrect passphrase"); 422ff9e4f8aStedu explicit_bzero(digest, sizeof(digest)); 423ff9e4f8aStedu 424ff9e4f8aStedu signmsg(enckey.seckey, msg, msglen, sig.sig); 425ff9e4f8aStedu memcpy(sig.keynum, enckey.keynum, KEYNUMLEN); 426ff9e4f8aStedu explicit_bzero(&enckey, sizeof(enckey)); 427ff9e4f8aStedu 428ff9e4f8aStedu memcpy(sig.pkalg, PKALG, 2); 429ff9e4f8aStedu 430ff9e4f8aStedu sighdr = createheader(sigcomment, &sig, sizeof(sig)); 431ff9e4f8aStedu return sighdr; 432ff9e4f8aStedu } 433ff9e4f8aStedu 434ff9e4f8aStedu static void 435ff9e4f8aStedu sign(const char *seckeyfile, const char *msgfile, const char *sigfile, 436ff9e4f8aStedu int embedded) 437ff9e4f8aStedu { 438ff9e4f8aStedu uint8_t *msg; 439ff9e4f8aStedu char *sighdr; 440ff9e4f8aStedu int fd; 441ff9e4f8aStedu unsigned long long msglen; 442ff9e4f8aStedu 4439b252276Stedu msg = readmsg(msgfile, &msglen); 4449b252276Stedu 445ff9e4f8aStedu sighdr = createsig(seckeyfile, msgfile, msg, msglen); 4469b252276Stedu 4479b252276Stedu fd = xopen(sigfile, O_CREAT|O_TRUNC|O_NOFOLLOW|O_WRONLY, 0666); 4489b252276Stedu writeall(fd, sighdr, strlen(sighdr), sigfile); 4499b252276Stedu free(sighdr); 45027f66874Stedu if (embedded) 4519b252276Stedu writeall(fd, msg, msglen, sigfile); 4529b252276Stedu close(fd); 4534215a5deStedu 4544215a5deStedu free(msg); 4554215a5deStedu } 456665ab7d9Stedu #endif 4574215a5deStedu 4584215a5deStedu static void 45948336e31Stedu verifymsg(struct pubkey *pubkey, uint8_t *msg, unsigned long long msglen, 46048336e31Stedu struct sig *sig, int quiet) 4611c9c770cStedu { 4621c9c770cStedu uint8_t *sigbuf, *dummybuf; 4631c9c770cStedu unsigned long long siglen, dummylen; 4641c9c770cStedu 465534dbeadStedu if (memcmp(pubkey->keynum, sig->keynum, KEYNUMLEN) != 0) 46648336e31Stedu errx(1, "verification failed: checked against wrong key"); 46748336e31Stedu 4681c9c770cStedu siglen = SIGBYTES + msglen; 4691c9c770cStedu sigbuf = xmalloc(siglen); 4701c9c770cStedu dummybuf = xmalloc(siglen); 47148336e31Stedu memcpy(sigbuf, sig->sig, SIGBYTES); 4721c9c770cStedu memcpy(sigbuf + SIGBYTES, msg, msglen); 4731c9c770cStedu if (crypto_sign_ed25519_open(dummybuf, &dummylen, sigbuf, siglen, 47448336e31Stedu pubkey->pubkey) == -1) 4751c9c770cStedu errx(1, "signature verification failed"); 47658559f60Stedu if (!quiet) 47758559f60Stedu printf("Signature Verified\n"); 4781c9c770cStedu free(sigbuf); 4791c9c770cStedu free(dummybuf); 4801c9c770cStedu } 4811c9c770cStedu 48218d71116Sespie static void 48318d71116Sespie check_keytype(const char *pubkeyfile, const char *keytype) 48418d71116Sespie { 485a481ce23Stedu const char *p; 486a481ce23Stedu size_t typelen; 48747dc9001Stedu 488a481ce23Stedu if (!(p = strrchr(pubkeyfile, '-'))) 489a481ce23Stedu goto bad; 490a481ce23Stedu p++; 491a481ce23Stedu typelen = strlen(keytype); 492a481ce23Stedu if (strncmp(p, keytype, typelen) != 0) 493a481ce23Stedu goto bad; 494a481ce23Stedu if (strcmp(p + typelen, ".pub") != 0) 495a481ce23Stedu goto bad; 496a481ce23Stedu return; 49718d71116Sespie 498a481ce23Stedu bad: 499a481ce23Stedu errx(1, "incorrect keytype: %s is not %s", pubkeyfile, keytype); 50018d71116Sespie } 50118d71116Sespie 5021c9c770cStedu static void 50326d9395eStedu readpubkey(const char *pubkeyfile, struct pubkey *pubkey, 50418d71116Sespie const char *sigcomment, const char *keytype) 5054215a5deStedu { 506e4c55632Stedu const char *safepath = "/etc/signify"; 507*0274047dSderaadt char keypath[PATH_MAX]; 50827f66874Stedu 5090f212b48Stedu if (!pubkeyfile) { 5101118246cStedu pubkeyfile = strstr(sigcomment, VERIFYWITH); 511e4c55632Stedu if (pubkeyfile && strchr(pubkeyfile, '/') == NULL) { 5120f212b48Stedu pubkeyfile += strlen(VERIFYWITH); 51318d71116Sespie if (keytype) 51418d71116Sespie check_keytype(pubkeyfile, keytype); 515e4c55632Stedu if (snprintf(keypath, sizeof(keypath), "%s/%s", 516e4c55632Stedu safepath, pubkeyfile) >= sizeof(keypath)) 517e4c55632Stedu errx(1, "name too long %s", pubkeyfile); 51897524a13Stedu pubkeyfile = keypath; 519b0b02d10Stedu } else 5205d586c2bStedu usage("must specify pubkey"); 5210f212b48Stedu } 52297524a13Stedu readb64file(pubkeyfile, pubkey, sizeof(*pubkey), NULL); 52326d9395eStedu } 52426d9395eStedu 52526d9395eStedu static void 52626d9395eStedu verifysimple(const char *pubkeyfile, const char *msgfile, const char *sigfile, 52718d71116Sespie int quiet, const char *keytype) 52826d9395eStedu { 52926d9395eStedu char sigcomment[COMMENTMAXLEN]; 53026d9395eStedu struct sig sig; 53126d9395eStedu struct pubkey pubkey; 53226d9395eStedu unsigned long long msglen; 53326d9395eStedu uint8_t *msg; 53426d9395eStedu 53526d9395eStedu msg = readmsg(msgfile, &msglen); 53626d9395eStedu 53726d9395eStedu readb64file(sigfile, &sig, sizeof(sig), sigcomment); 53818d71116Sespie readpubkey(pubkeyfile, &pubkey, sigcomment, keytype); 5394215a5deStedu 54048336e31Stedu verifymsg(&pubkey, msg, msglen, &sig, quiet); 541ffebbc61Stedu 542ffebbc61Stedu free(msg); 543ffebbc61Stedu } 544ffebbc61Stedu 545ffebbc61Stedu static uint8_t * 546ffebbc61Stedu verifyembedded(const char *pubkeyfile, const char *sigfile, 54718d71116Sespie int quiet, unsigned long long *msglenp, const char *keytype) 548ffebbc61Stedu { 54926d9395eStedu char sigcomment[COMMENTMAXLEN]; 550ffebbc61Stedu struct sig sig; 551ffebbc61Stedu struct pubkey pubkey; 552ffebbc61Stedu unsigned long long msglen, siglen; 553ffebbc61Stedu uint8_t *msg; 554ffebbc61Stedu 555ffebbc61Stedu msg = readmsg(sigfile, &msglen); 556ffebbc61Stedu 55726d9395eStedu siglen = parseb64file(sigfile, msg, &sig, sizeof(sig), sigcomment); 55818d71116Sespie readpubkey(pubkeyfile, &pubkey, sigcomment, keytype); 55926d9395eStedu 560ffebbc61Stedu msglen -= siglen; 561ffebbc61Stedu memmove(msg, msg + siglen, msglen); 562ffebbc61Stedu msg[msglen] = 0; 563ffebbc61Stedu 564ffebbc61Stedu verifymsg(&pubkey, msg, msglen, &sig, quiet); 565ffebbc61Stedu 566ffebbc61Stedu *msglenp = msglen; 567ffebbc61Stedu return msg; 568ffebbc61Stedu } 569ffebbc61Stedu 570ffebbc61Stedu static void 571ffebbc61Stedu verify(const char *pubkeyfile, const char *msgfile, const char *sigfile, 57218d71116Sespie int embedded, int quiet, const char *keytype) 573ffebbc61Stedu { 574ffebbc61Stedu unsigned long long msglen; 575ffebbc61Stedu uint8_t *msg; 576ffebbc61Stedu int fd; 577ffebbc61Stedu 57827f66874Stedu if (embedded) { 57918d71116Sespie msg = verifyembedded(pubkeyfile, sigfile, quiet, &msglen, 58018d71116Sespie keytype); 5819831e76dStedu fd = xopen(msgfile, O_CREAT|O_TRUNC|O_NOFOLLOW|O_WRONLY, 0666); 58227f66874Stedu writeall(fd, msg, msglen, msgfile); 58387e034adSespie free(msg); 58427f66874Stedu close(fd); 585ffebbc61Stedu } else { 58618d71116Sespie verifysimple(pubkeyfile, msgfile, sigfile, quiet, keytype); 58727f66874Stedu } 5884215a5deStedu } 5894215a5deStedu 59058559f60Stedu #ifndef VERIFYONLY 591ce955b95Stedu #define HASHBUFSIZE 224 59258559f60Stedu struct checksum { 593f30a54aaStedu char file[PATH_MAX]; 594ce955b95Stedu char hash[HASHBUFSIZE]; 595032a32d9Stedu char algo[32]; 59658559f60Stedu }; 59758559f60Stedu 598e96025a1Sespie static void * 599e96025a1Sespie ecalloc(size_t s1, size_t s2, void *data) 600e96025a1Sespie { 601db6a8127Stedu void *p; 602db6a8127Stedu 603dba2cf70Stedu if (!(p = calloc(s1, s2))) 604e96025a1Sespie err(1, "calloc"); 605e96025a1Sespie return p; 606e96025a1Sespie } 607e96025a1Sespie 60858559f60Stedu static void 609e96025a1Sespie efree(void *p, void *data) 610e96025a1Sespie { 611e96025a1Sespie free(p); 612e96025a1Sespie } 613e96025a1Sespie 614e96025a1Sespie static void 615e96025a1Sespie recodehash(char *hash, size_t len) 6160050332bStedu { 617ce955b95Stedu uint8_t data[HASHBUFSIZE / 2]; 6180050332bStedu int i, rv; 6190050332bStedu 620e96025a1Sespie if (strlen(hash) == len) 6210050332bStedu return; 6220050332bStedu if ((rv = b64_pton(hash, data, sizeof(data))) == -1) 6230050332bStedu errx(1, "invalid base64 encoding"); 6240050332bStedu for (i = 0; i < rv; i++) 625ce955b95Stedu snprintf(hash + i * 2, HASHBUFSIZE - i * 2, "%2.2x", data[i]); 6260050332bStedu } 6270050332bStedu 628e96025a1Sespie static int 629e96025a1Sespie verifychecksum(struct checksum *c, int quiet) 63058559f60Stedu { 631e96025a1Sespie char buf[HASHBUFSIZE]; 632db6a8127Stedu 63358559f60Stedu if (strcmp(c->algo, "SHA256") == 0) { 634e96025a1Sespie recodehash(c->hash, SHA256_DIGEST_STRING_LENGTH-1); 635e96025a1Sespie if (!SHA256File(c->file, buf)) 636e96025a1Sespie return 0; 63758559f60Stedu } else if (strcmp(c->algo, "SHA512") == 0) { 638e96025a1Sespie recodehash(c->hash, SHA512_DIGEST_STRING_LENGTH-1); 639e96025a1Sespie if (!SHA512File(c->file, buf)) 640e96025a1Sespie return 0; 64158559f60Stedu } else { 64258559f60Stedu errx(1, "can't handle algorithm %s", c->algo); 64358559f60Stedu } 644b39b450dStedu if (strcmp(c->hash, buf) != 0) 645e96025a1Sespie return 0; 64658559f60Stedu if (!quiet) 64758559f60Stedu printf("%s: OK\n", c->file); 648e96025a1Sespie return 1; 64958559f60Stedu } 650e96025a1Sespie 651e96025a1Sespie static void 652e96025a1Sespie verifychecksums(char *msg, int argc, char **argv, int quiet) 653e96025a1Sespie { 654e96025a1Sespie struct ohash_info info = { 0, NULL, ecalloc, efree, NULL }; 655e96025a1Sespie struct ohash myh; 656e96025a1Sespie struct checksum c; 657a5b6fcd7Stedu char *e, *line, *endline; 658db6a8127Stedu int hasfailed = 0; 659db6a8127Stedu int i, rv; 660e96025a1Sespie unsigned int slot; 661e96025a1Sespie 662e96025a1Sespie ohash_init(&myh, 6, &info); 663a5b6fcd7Stedu if (argc) { 664e96025a1Sespie for (i = 0; i < argc; i++) { 665e96025a1Sespie slot = ohash_qlookup(&myh, argv[i]); 666e96025a1Sespie e = ohash_find(&myh, slot); 667e96025a1Sespie if (e == NULL) 668e96025a1Sespie ohash_insert(&myh, slot, argv[i]); 669e96025a1Sespie } 670e96025a1Sespie } 671e96025a1Sespie 672e96025a1Sespie line = msg; 673e96025a1Sespie while (line && *line) { 674e96025a1Sespie if ((endline = strchr(line, '\n'))) 675e96025a1Sespie *endline++ = '\0'; 676a8e2d00fStedu #if PATH_MAX < 1024 || HASHBUFSIZE < 224 677a8e2d00fStedu #error sizes are wrong 678a8e2d00fStedu #endif 6793111eefdStedu rv = sscanf(line, "%31s (%1023[^)]) = %223s", 680e96025a1Sespie c.algo, c.file, c.hash); 6813111eefdStedu if (rv != 3) 682e96025a1Sespie errx(1, "unable to parse checksum line %s", line); 683e96025a1Sespie line = endline; 684e96025a1Sespie if (argc) { 685e96025a1Sespie slot = ohash_qlookup(&myh, c.file); 686e96025a1Sespie e = ohash_find(&myh, slot); 687e96025a1Sespie if (e != NULL) { 688db6a8127Stedu if (verifychecksum(&c, quiet) != 0) 689e96025a1Sespie ohash_remove(&myh, slot); 690e96025a1Sespie } 691e96025a1Sespie } else { 692db6a8127Stedu if (verifychecksum(&c, quiet) == 0) { 693a5b6fcd7Stedu slot = ohash_qlookup(&myh, c.file); 694a5b6fcd7Stedu e = ohash_find(&myh, slot); 695a5b6fcd7Stedu if (e == NULL) { 696a5b6fcd7Stedu if (!(e = strdup(c.file))) 697a5b6fcd7Stedu err(1, "strdup"); 698a5b6fcd7Stedu ohash_insert(&myh, slot, e); 699a5b6fcd7Stedu } 70058559f60Stedu } 70158559f60Stedu } 702e96025a1Sespie } 703e96025a1Sespie 704a5b6fcd7Stedu for (e = ohash_first(&myh, &slot); e != NULL; e = ohash_next(&myh, &slot)) { 705a5b6fcd7Stedu fprintf(stderr, "%s: FAIL\n", e); 706e96025a1Sespie hasfailed = 1; 707a5b6fcd7Stedu if (argc == 0) 708a5b6fcd7Stedu free(e); 709e96025a1Sespie } 710e96025a1Sespie ohash_delete(&myh); 7118972cca3Stedu if (hasfailed) 71258559f60Stedu exit(1); 71358559f60Stedu } 71458559f60Stedu 71558559f60Stedu static void 71658559f60Stedu check(const char *pubkeyfile, const char *sigfile, int quiet, int argc, 71758559f60Stedu char **argv) 71858559f60Stedu { 719ffebbc61Stedu unsigned long long msglen; 72058559f60Stedu uint8_t *msg; 72158559f60Stedu 72218d71116Sespie msg = verifyembedded(pubkeyfile, sigfile, quiet, &msglen, NULL); 723a40f4206Stedu verifychecksums((char *)msg, argc, argv, quiet); 72458559f60Stedu 725ffebbc61Stedu free(msg); 72658559f60Stedu } 7273ccd7401Sespie 7283ccd7401Sespie void * 7293ccd7401Sespie verifyzdata(uint8_t *zdata, unsigned long long zdatalen, 7303ccd7401Sespie const char *filename, const char *pubkeyfile, const char *keytype) 7313ccd7401Sespie { 7323ccd7401Sespie struct sig sig; 7333ccd7401Sespie char sigcomment[COMMENTMAXLEN]; 7343ccd7401Sespie unsigned long long siglen; 7353ccd7401Sespie struct pubkey pubkey; 7363ccd7401Sespie 7373ccd7401Sespie if (zdatalen < sizeof(sig)) 7383ccd7401Sespie errx(1, "signature too short in %s", filename); 7393ccd7401Sespie siglen = parseb64file(filename, zdata, &sig, sizeof(sig), 7403ccd7401Sespie sigcomment); 7413ccd7401Sespie readpubkey(pubkeyfile, &pubkey, sigcomment, keytype); 7423ccd7401Sespie zdata += siglen; 7433ccd7401Sespie zdatalen -= siglen; 7443ccd7401Sespie verifymsg(&pubkey, zdata, zdatalen, &sig, 1); 7453ccd7401Sespie return zdata; 7463ccd7401Sespie } 74758559f60Stedu #endif 74858559f60Stedu 7494215a5deStedu int 7504215a5deStedu main(int argc, char **argv) 7514215a5deStedu { 75227f66874Stedu const char *pubkeyfile = NULL, *seckeyfile = NULL, *msgfile = NULL, 7534215a5deStedu *sigfile = NULL; 754f30a54aaStedu char sigfilebuf[PATH_MAX]; 755bd7b638bStedu const char *comment = "signify"; 75618d71116Sespie char *keytype = NULL; 757c610539bStedu int ch; 758c610539bStedu int none = 0; 75927f66874Stedu int embedded = 0; 76058559f60Stedu int quiet = 0; 7613ccd7401Sespie int gzip = 0; 762a6bade58Stedu enum { 763a6bade58Stedu NONE, 76458559f60Stedu CHECK, 765a6bade58Stedu GENERATE, 766a6bade58Stedu SIGN, 767a6bade58Stedu VERIFY 768a6bade58Stedu } verb = NONE; 769a6bade58Stedu 7700bd1216cSderaadt if (pledge("stdio rpath wpath cpath tty", NULL) == -1) 7710bd1216cSderaadt err(1, "pledge"); 7724215a5deStedu 7733ccd7401Sespie while ((ch = getopt(argc, argv, "CGSVzc:em:np:qs:t:x:")) != -1) { 7744215a5deStedu switch (ch) { 775665ab7d9Stedu #ifndef VERIFYONLY 77658559f60Stedu case 'C': 77758559f60Stedu if (verb) 77858559f60Stedu usage(NULL); 77958559f60Stedu verb = CHECK; 78058559f60Stedu break; 781a6bade58Stedu case 'G': 782a6bade58Stedu if (verb) 783f2adbe28Stedu usage(NULL); 784a6bade58Stedu verb = GENERATE; 7854215a5deStedu break; 7864215a5deStedu case 'S': 787a6bade58Stedu if (verb) 788f2adbe28Stedu usage(NULL); 789a6bade58Stedu verb = SIGN; 7904215a5deStedu break; 7913ccd7401Sespie case 'z': 7923ccd7401Sespie gzip = 1; 7933ccd7401Sespie break; 794665ab7d9Stedu #endif 7954215a5deStedu case 'V': 796a6bade58Stedu if (verb) 797f2adbe28Stedu usage(NULL); 798a6bade58Stedu verb = VERIFY; 799a6bade58Stedu break; 800bd7b638bStedu case 'c': 801bd7b638bStedu comment = optarg; 802bd7b638bStedu break; 80327f66874Stedu case 'e': 80427f66874Stedu embedded = 1; 80527f66874Stedu break; 806f2adbe28Stedu case 'm': 807f2adbe28Stedu msgfile = optarg; 808f2adbe28Stedu break; 809a6bade58Stedu case 'n': 810c610539bStedu none = 1; 811a6bade58Stedu break; 812a6bade58Stedu case 'p': 813a6bade58Stedu pubkeyfile = optarg; 814a6bade58Stedu break; 81558559f60Stedu case 'q': 81658559f60Stedu quiet = 1; 81758559f60Stedu break; 818a6bade58Stedu case 's': 819a6bade58Stedu seckeyfile = optarg; 8204215a5deStedu break; 82118d71116Sespie case 't': 82218d71116Sespie keytype = optarg; 82318d71116Sespie break; 824f2adbe28Stedu case 'x': 825f2adbe28Stedu sigfile = optarg; 826f2adbe28Stedu break; 8274215a5deStedu default: 828f2adbe28Stedu usage(NULL); 8294215a5deStedu break; 8304215a5deStedu } 8314215a5deStedu } 832bcc39c47Stedu argc -= optind; 83342efb9f2Sespie argv += optind; 83442efb9f2Sespie 835da1d8f10Stedu if (embedded && gzip) 836da1d8f10Stedu errx(1, "can't combine -e and -z options"); 837da1d8f10Stedu 83875f1004aSbluhm if (setvbuf(stdout, NULL, _IOLBF, 0) != 0) 83975f1004aSbluhm err(1, "setvbuf"); 84075f1004aSbluhm 84158559f60Stedu #ifndef VERIFYONLY 84258559f60Stedu if (verb == CHECK) { 843de861b93Stedu if (pledge("stdio rpath", NULL) == -1) 844de861b93Stedu err(1, "pledge"); 8459fbe12ffStedu if (!sigfile) 8469fbe12ffStedu usage("must specify sigfile"); 84758559f60Stedu check(pubkeyfile, sigfile, quiet, argc, argv); 84858559f60Stedu return 0; 84958559f60Stedu } 85058559f60Stedu #endif 85158559f60Stedu 852f2adbe28Stedu if (argc != 0) 853f2adbe28Stedu usage(NULL); 854f2adbe28Stedu 8559127dd95Stedu if (!sigfile && msgfile) { 856b3fe1a3aStedu int nr; 857f2adbe28Stedu if (strcmp(msgfile, "-") == 0) 8585d586c2bStedu usage("must specify sigfile with - message"); 859924f56e4Stedu nr = snprintf(sigfilebuf, sizeof(sigfilebuf), 860924f56e4Stedu "%s.sig", msgfile); 861515e489cSderaadt if (nr < 0 || nr >= sizeof(sigfilebuf)) 8624215a5deStedu errx(1, "path too long"); 8634215a5deStedu sigfile = sigfilebuf; 8644215a5deStedu } 8659127dd95Stedu 8669127dd95Stedu switch (verb) { 867665ab7d9Stedu #ifndef VERIFYONLY 8689127dd95Stedu case GENERATE: 869de861b93Stedu /* no pledge */ 8709127dd95Stedu if (!pubkeyfile || !seckeyfile) 8715d586c2bStedu usage("must specify pubkey and seckey"); 872763e1148Stedu check_keyname_compliance(pubkeyfile, seckeyfile); 873c610539bStedu generate(pubkeyfile, seckeyfile, none ? 0 : 42, comment); 8749127dd95Stedu break; 8759127dd95Stedu case SIGN: 876de861b93Stedu /* no pledge */ 87709ecb553Stedu if (gzip) { 87812f6f7dbSespie if (!msgfile || !seckeyfile || !sigfile) 87912f6f7dbSespie usage("must specify message sigfile seckey"); 880c610539bStedu zsign(seckeyfile, msgfile, sigfile, none); 88109ecb553Stedu } else { 8829127dd95Stedu if (!msgfile || !seckeyfile) 8835d586c2bStedu usage("must specify message and seckey"); 88427f66874Stedu sign(seckeyfile, msgfile, sigfile, embedded); 8853ccd7401Sespie } 8869127dd95Stedu break; 887665ab7d9Stedu #endif 8889127dd95Stedu case VERIFY: 88909ecb553Stedu if ((embedded || gzip) && 89009ecb553Stedu (msgfile && strcmp(msgfile, "-") != 0)) { 89109ecb553Stedu /* will need to create output file */ 892de861b93Stedu if (pledge("stdio rpath wpath cpath", NULL) == -1) 893de861b93Stedu err(1, "pledge"); 894de861b93Stedu } else { 895de861b93Stedu if (pledge("stdio rpath", NULL) == -1) 896de861b93Stedu err(1, "pledge"); 897de861b93Stedu } 89809ecb553Stedu if (gzip) { 8993ccd7401Sespie zverify(pubkeyfile, msgfile, sigfile, keytype); 90009ecb553Stedu } else { 9010f212b48Stedu if (!msgfile) 9025d586c2bStedu usage("must specify message"); 9033ccd7401Sespie verify(pubkeyfile, msgfile, sigfile, embedded, 9043ccd7401Sespie quiet, keytype); 9053ccd7401Sespie } 9069127dd95Stedu break; 9079127dd95Stedu default: 908de861b93Stedu if (pledge("stdio", NULL) == -1) 909de861b93Stedu err(1, "pledge"); 9109127dd95Stedu usage(NULL); 9119127dd95Stedu break; 91242efb9f2Sespie } 91342efb9f2Sespie 9144215a5deStedu return 0; 9154215a5deStedu } 916