/* $OpenBSD: md5.c,v 1.77 2014/09/13 16:06:36 doug Exp $ */ /* * Copyright (c) 2001,2003,2005-2007,2010,2013,2014 * Todd C. Miller * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * Sponsored in part by the Defense Advanced Research Projects * Agency (DARPA) and Air Force Research Laboratory, Air Force * Materiel Command, USAF, under agreement number F39502-99-1-0512. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define STYLE_MD5 0 #define STYLE_CKSUM 1 #define STYLE_TERSE 2 #define MAX_DIGEST_LEN 128 #define MIN(a,b) (((a)<(b))?(a):(b)) #define MAX(a,b) (((a)>(b))?(a):(b)) union ANY_CTX { #if !defined(SHA2_ONLY) CKSUM_CTX cksum; MD5_CTX md5; RMD160_CTX rmd160; SHA1_CTX sha1; #endif /* !defined(SHA2_ONLY) */ SHA2_CTX sha2; }; struct hash_function { const char *name; size_t digestlen; int style; int base64; void *ctx; /* XXX - only used by digest_file() */ void (*init)(void *); void (*update)(void *, const unsigned char *, unsigned int); void (*final)(unsigned char *, void *); char * (*end)(void *, char *); TAILQ_ENTRY(hash_function) tailq; } functions[] = { #if !defined(SHA2_ONLY) { "CKSUM", CKSUM_DIGEST_LENGTH, STYLE_CKSUM, -1, NULL, (void (*)(void *))CKSUM_Init, (void (*)(void *, const unsigned char *, unsigned int))CKSUM_Update, (void (*)(unsigned char *, void *))CKSUM_Final, (char *(*)(void *, char *))CKSUM_End }, { "MD5", MD5_DIGEST_LENGTH, STYLE_MD5, 0, NULL, (void (*)(void *))MD5Init, (void (*)(void *, const unsigned char *, unsigned int))MD5Update, (void (*)(unsigned char *, void *))MD5Final, (char *(*)(void *, char *))MD5End }, { "RMD160", RMD160_DIGEST_LENGTH, STYLE_MD5, 0, NULL, (void (*)(void *))RMD160Init, (void (*)(void *, const unsigned char *, unsigned int))RMD160Update, (void (*)(unsigned char *, void *))RMD160Final, (char *(*)(void *, char *))RMD160End }, { "SHA1", SHA1_DIGEST_LENGTH, STYLE_MD5, 0, NULL, (void (*)(void *))SHA1Init, (void (*)(void *, const unsigned char *, unsigned int))SHA1Update, (void (*)(unsigned char *, void *))SHA1Final, (char *(*)(void *, char *))SHA1End }, { "SHA224", SHA224_DIGEST_LENGTH, STYLE_MD5, 0, NULL, (void (*)(void *))SHA224Init, (void (*)(void *, const unsigned char *, unsigned int))SHA224Update, (void (*)(unsigned char *, void *))SHA224Final, (char *(*)(void *, char *))SHA224End }, #endif /* !defined(SHA2_ONLY) */ { "SHA256", SHA256_DIGEST_LENGTH, STYLE_MD5, 0, NULL, (void (*)(void *))SHA256Init, (void (*)(void *, const unsigned char *, unsigned int))SHA256Update, (void (*)(unsigned char *, void *))SHA256Final, (char *(*)(void *, char *))SHA256End }, #if !defined(SHA2_ONLY) { "SHA384", SHA384_DIGEST_LENGTH, STYLE_MD5, 0, NULL, (void (*)(void *))SHA384Init, (void (*)(void *, const unsigned char *, unsigned int))SHA384Update, (void (*)(unsigned char *, void *))SHA384Final, (char *(*)(void *, char *))SHA384End }, #endif /* !defined(SHA2_ONLY) */ { "SHA512", SHA512_DIGEST_LENGTH, STYLE_MD5, 0, NULL, (void (*)(void *))SHA512Init, (void (*)(void *, const unsigned char *, unsigned int))SHA512Update, (void (*)(unsigned char *, void *))SHA512Final, (char *(*)(void *, char *))SHA512End }, { NULL, } }; TAILQ_HEAD(hash_list, hash_function); void digest_end(const struct hash_function *, void *, char *, size_t, int); int digest_file(const char *, struct hash_list *, int); int digest_filelist(const char *, struct hash_function *, int, char **); void digest_print(const struct hash_function *, const char *, const char *); #if !defined(SHA2_ONLY) void digest_printstr(const struct hash_function *, const char *, const char *); void digest_string(char *, struct hash_list *); void digest_test(struct hash_list *); void digest_time(struct hash_list *, int); #endif /* !defined(SHA2_ONLY) */ void hash_insert(struct hash_list *, struct hash_function *, int); void usage(void) __attribute__((__noreturn__)); extern char *__progname; int qflag = 0; FILE *ofile = NULL; int main(int argc, char **argv) { struct hash_function *hf, *hftmp; struct hash_list hl; size_t len; char *cp, *input_string, *selective_checklist; const char *optstr; int fl, error, base64, i; int bflag, cflag, pflag, rflag, tflag, xflag; TAILQ_INIT(&hl); input_string = NULL; selective_checklist = NULL; error = bflag = cflag = pflag = qflag = rflag = tflag = xflag = 0; #if !defined(SHA2_ONLY) if (strcmp(__progname, "cksum") == 0) optstr = "a:bC:ch:pqrs:tx"; else #endif /* !defined(SHA2_ONLY) */ optstr = "bC:ch:pqrs:tx"; /* Check for -b option early since it changes behavior. */ while ((fl = getopt(argc, argv, optstr)) != -1) { switch (fl) { case 'b': bflag = 1; break; case '?': usage(); } } optind = 1; optreset = 1; while ((fl = getopt(argc, argv, optstr)) != -1) { switch (fl) { case 'a': while ((cp = strsep(&optarg, " \t,")) != NULL) { if (*cp == '\0') continue; base64 = -1; for (hf = functions; hf->name != NULL; hf++) { len = strlen(hf->name); if (strncasecmp(cp, hf->name, len) != 0) continue; if (cp[len] == '\0') { if (hf->base64 != -1) base64 = bflag; break; /* exact match */ } if (cp[len + 1] == '\0' && (cp[len] == 'b' || cp[len] == 'x')) { base64 = cp[len] == 'b' ? 1 : 0; break; /* match w/ suffix */ } } if (hf->name == NULL) { warnx("unknown algorithm \"%s\"", cp); usage(); } if (hf->base64 == -1 && base64 != -1) { warnx("%s doesn't support %s", hf->name, base64 ? "base64" : "hex"); usage(); } /* Check for dupes. */ TAILQ_FOREACH(hftmp, &hl, tailq) { if (hftmp->base64 == base64 && strcmp(hf->name, hftmp->name) == 0) break; } if (hftmp == NULL) hash_insert(&hl, hf, base64); } break; case 'b': /* has already been parsed */ break; case 'h': ofile = fopen(optarg, "w"); if (ofile == NULL) err(1, "%s", optarg); break; #if !defined(SHA2_ONLY) case 'C': selective_checklist = optarg; break; case 'c': cflag = 1; break; #endif /* !defined(SHA2_ONLY) */ case 'p': pflag = 1; break; case 'q': qflag = 1; break; case 'r': rflag = 1; break; case 's': input_string = optarg; break; case 't': tflag++; break; case 'x': xflag = 1; break; default: usage(); } } argc -= optind; argv += optind; if (ofile == NULL) ofile = stdout; /* Most arguments are mutually exclusive */ fl = pflag + (tflag ? 1 : 0) + xflag + cflag + (input_string != NULL); if (fl > 1 || (fl && argc && cflag == 0) || (rflag && qflag) || (selective_checklist != NULL && argc == 0)) usage(); if (selective_checklist || cflag) { if (TAILQ_FIRST(&hl) != TAILQ_LAST(&hl, hash_list)) errx(1, "only a single algorithm may be specified " "in -C or -c mode"); } /* No algorithm specified, check the name we were called as. */ if (TAILQ_EMPTY(&hl)) { for (hf = functions; hf->name != NULL; hf++) { if (strcasecmp(hf->name, __progname) == 0) break; } if (hf->name == NULL) hf = &functions[0]; /* default to cksum */ hash_insert(&hl, hf, (hf->base64 == -1 ? 0 : bflag)); } if (rflag || qflag) { const int new_style = rflag ? STYLE_CKSUM : STYLE_TERSE; TAILQ_FOREACH(hf, &hl, tailq) { hf->style = new_style; } } #if !defined(SHA2_ONLY) if (tflag) digest_time(&hl, tflag); else if (xflag) digest_test(&hl); else if (input_string) digest_string(input_string, &hl); else if (selective_checklist) { error = digest_filelist(selective_checklist, TAILQ_FIRST(&hl), argc, argv); for (i = 0; i < argc; i++) { if (argv[i] != NULL) { warnx("%s does not exist in %s", argv[i], selective_checklist); error++; } } } else if (cflag) { if (argc == 0) error = digest_filelist("-", TAILQ_FIRST(&hl), 0, NULL); else while (argc--) error += digest_filelist(*argv++, TAILQ_FIRST(&hl), 0, NULL); } else #endif /* !defined(SHA2_ONLY) */ if (pflag || argc == 0) error = digest_file("-", &hl, pflag); else while (argc--) error += digest_file(*argv++, &hl, 0); return(error ? EXIT_FAILURE : EXIT_SUCCESS); } void hash_insert(struct hash_list *hl, struct hash_function *hf, int base64) { struct hash_function *hftmp; hftmp = malloc(sizeof(*hftmp)); if (hftmp == NULL) err(1, NULL); *hftmp = *hf; hftmp->base64 = base64; TAILQ_INSERT_TAIL(hl, hftmp, tailq); } void digest_end(const struct hash_function *hf, void *ctx, char *buf, size_t bsize, int base64) { u_char *digest; if (base64 == 1) { if ((digest = malloc(hf->digestlen)) == NULL) err(1, NULL); hf->final(digest, ctx); if (b64_ntop(digest, hf->digestlen, buf, bsize) == -1) errx(1, "error encoding base64"); memset(digest, 0, hf->digestlen); free(digest); } else { hf->end(ctx, buf); } } #if !defined(SHA2_ONLY) void digest_string(char *string, struct hash_list *hl) { struct hash_function *hf; char digest[MAX_DIGEST_LEN + 1]; union ANY_CTX context; TAILQ_FOREACH(hf, hl, tailq) { hf->init(&context); hf->update(&context, string, (unsigned int)strlen(string)); digest_end(hf, &context, digest, sizeof(digest), hf->base64); digest_printstr(hf, string, digest); } } #endif /* !defined(SHA2_ONLY) */ void digest_print(const struct hash_function *hf, const char *what, const char *digest) { switch (hf->style) { case STYLE_MD5: (void)fprintf(ofile, "%s (%s) = %s\n", hf->name, what, digest); break; case STYLE_CKSUM: (void)fprintf(ofile, "%s %s\n", digest, what); break; case STYLE_TERSE: (void)fprintf(ofile, "%s\n", digest); break; } } void digest_printstr(const struct hash_function *hf, const char *what, const char *digest) { switch (hf->style) { case STYLE_MD5: (void)fprintf(ofile, "%s (\"%s\") = %s\n", hf->name, what, digest); break; case STYLE_CKSUM: (void)fprintf(ofile, "%s %s\n", digest, what); break; case STYLE_TERSE: (void)fprintf(ofile, "%s\n", digest); break; } } int digest_file(const char *file, struct hash_list *hl, int echo) { struct hash_function *hf; FILE *fp; size_t nread; u_char data[32 * 1024]; char digest[MAX_DIGEST_LEN + 1]; if (strcmp(file, "-") == 0) fp = stdin; else if ((fp = fopen(file, "r")) == NULL) { warn("cannot open %s", file); return(1); } TAILQ_FOREACH(hf, hl, tailq) { if ((hf->ctx = malloc(sizeof(union ANY_CTX))) == NULL) err(1, NULL); hf->init(hf->ctx); } while ((nread = fread(data, 1UL, sizeof(data), fp)) != 0) { if (echo) { (void)fwrite(data, nread, 1UL, stdout); if (fflush(stdout) != 0) err(1, "stdout: write error"); } TAILQ_FOREACH(hf, hl, tailq) hf->update(hf->ctx, data, (unsigned int)nread); } if (ferror(fp)) { warn("%s: read error", file); if (fp != stdin) fclose(fp); TAILQ_FOREACH(hf, hl, tailq) { free(hf->ctx); hf->ctx = NULL; } return(1); } if (fp != stdin) fclose(fp); TAILQ_FOREACH(hf, hl, tailq) { digest_end(hf, hf->ctx, digest, sizeof(digest), hf->base64); free(hf->ctx); hf->ctx = NULL; if (fp == stdin) fprintf(ofile, "%s\n", digest); else digest_print(hf, file, digest); } return(0); } #if !defined(SHA2_ONLY) /* * Parse through the input file looking for valid lines. * If one is found, use this checksum and file as a reference and * generate a new checksum against the file on the filesystem. * Print out the result of each comparison. */ int digest_filelist(const char *file, struct hash_function *defhash, int selcount, char **sel) { int found, base64, error, cmp, i; size_t algorithm_max, algorithm_min; const char *algorithm; char *filename, *checksum, *buf, *p; char digest[MAX_DIGEST_LEN + 1]; char *lbuf = NULL; FILE *listfp, *fp; size_t len, nread; u_char data[32 * 1024]; union ANY_CTX context; struct hash_function *hf; if (strcmp(file, "-") == 0) { listfp = stdin; } else if ((listfp = fopen(file, "r")) == NULL) { warn("cannot open %s", file); return(1); } algorithm_max = algorithm_min = strlen(functions[0].name); for (hf = &functions[1]; hf->name != NULL; hf++) { len = strlen(hf->name); algorithm_max = MAX(algorithm_max, len); algorithm_min = MIN(algorithm_min, len); } error = found = 0; while ((buf = fgetln(listfp, &len))) { base64 = 0; if (buf[len - 1] == '\n') buf[len - 1] = '\0'; else { if ((lbuf = malloc(len + 1)) == NULL) err(1, NULL); (void)memcpy(lbuf, buf, len); lbuf[len] = '\0'; buf = lbuf; } while (isspace((unsigned char)*buf)) buf++; /* * Crack the line into an algorithm, filename, and checksum. * Lines are of the form: * ALGORITHM (FILENAME) = CHECKSUM * * Fallback on GNU form: * CHECKSUM FILENAME */ p = strchr(buf, ' '); if (p != NULL && *(p + 1) == '(') { /* BSD form */ *p = '\0'; algorithm = buf; len = strlen(algorithm); if (len > algorithm_max || len < algorithm_min) continue; filename = p + 2; p = strrchr(filename, ')'); if (p == NULL || strncmp(p + 1, " = ", (size_t)3) != 0) continue; *p = '\0'; checksum = p + 4; p = strpbrk(checksum, " \t\r"); if (p != NULL) *p = '\0'; /* * Check that the algorithm is one we recognize. */ for (hf = functions; hf->name != NULL; hf++) { if (strcasecmp(algorithm, hf->name) == 0) break; } if (hf->name == NULL || *checksum == '\0') continue; /* * Check the length to see if this could be * a valid checksum. If hex, it will be 2x the * size of the binary data. For base64, we have * to check both with and without the '=' padding. */ len = strlen(checksum); if (len != hf->digestlen * 2) { size_t len2; if (checksum[len - 1] == '=') { /* use padding */ len2 = 4 * ((hf->digestlen + 2) / 3); } else { /* no padding */ len2 = (4 * hf->digestlen + 2) / 3; } if (len != len2) continue; base64 = 1; } } else { /* could be GNU form */ if ((hf = defhash) == NULL) continue; algorithm = hf->name; checksum = buf; if ((p = strchr(checksum, ' ')) == NULL) continue; if (hf->style == STYLE_CKSUM) { if ((p = strchr(p + 1, ' ')) == NULL) continue; } *p++ = '\0'; while (isspace((unsigned char)*p)) p++; if (*p == '\0') continue; filename = p; p = strpbrk(filename, "\t\r"); if (p != NULL) *p = '\0'; } found = 1; /* * If only a selection of files is wanted, proceed only * if the filename matches one of those in the selection. * Mark found files by setting them to NULL so that we can * detect files that are missing from the checklist later. */ if (sel) { for (i = 0; i < selcount; i++) { if (sel[i] && strcmp(sel[i], filename) == 0) { sel[i] = NULL; break; } } if (i == selcount) continue; } if ((fp = fopen(filename, "r")) == NULL) { warn("cannot open %s", filename); (void)printf("(%s) %s: %s\n", algorithm, filename, (errno == ENOENT ? "MISSING" : "FAILED")); error = 1; continue; } hf->init(&context); while ((nread = fread(data, 1UL, sizeof(data), fp)) > 0) hf->update(&context, data, (unsigned int)nread); if (ferror(fp)) { warn("%s: read error", file); error = 1; fclose(fp); continue; } fclose(fp); digest_end(hf, &context, digest, sizeof(digest), base64); if (base64) cmp = strncmp(checksum, digest, len); else cmp = strcasecmp(checksum, digest); if (cmp == 0) { if (qflag == 0) (void)printf("(%s) %s: OK\n", algorithm, filename); } else { (void)printf("(%s) %s: FAILED\n", algorithm, filename); error = 1; } } if (listfp != stdin) fclose(listfp); if (!found) warnx("%s: no properly formatted checksum lines found", file); if (lbuf != NULL) free(lbuf); return(error || !found); } #define TEST_BLOCK_LEN 10000 #define TEST_BLOCK_COUNT 10000 void digest_time(struct hash_list *hl, int times) { struct hash_function *hf; struct timeval start, stop, res; union ANY_CTX context; u_int i; u_char data[TEST_BLOCK_LEN]; char digest[MAX_DIGEST_LEN + 1]; double elapsed; int count = TEST_BLOCK_COUNT; while (--times > 0 && count < INT_MAX / 10) count *= 10; TAILQ_FOREACH(hf, hl, tailq) { (void)printf("%s time trial. Processing %d %d-byte blocks...", hf->name, count, TEST_BLOCK_LEN); fflush(stdout); /* Initialize data based on block number. */ for (i = 0; i < TEST_BLOCK_LEN; i++) data[i] = (u_char)(i & 0xff); gettimeofday(&start, NULL); hf->init(&context); for (i = 0; i < count; i++) hf->update(&context, data, TEST_BLOCK_LEN); digest_end(hf, &context, digest, sizeof(digest), hf->base64); gettimeofday(&stop, NULL); timersub(&stop, &start, &res); elapsed = res.tv_sec + res.tv_usec / 1000000.0; (void)printf("\nDigest = %s\n", digest); (void)printf("Time = %f seconds\n", elapsed); (void)printf("Speed = %f bytes/second\n", (double)TEST_BLOCK_LEN * count / elapsed); } } void digest_test(struct hash_list *hl) { struct hash_function *hf; union ANY_CTX context; int i; char digest[MAX_DIGEST_LEN + 1]; unsigned char buf[1000]; unsigned const char *test_strings[] = { "", "a", "abc", "message digest", "abcdefghijklmnopqrstuvwxyz", "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" "0123456789", "12345678901234567890123456789012345678901234567890123456789" "012345678901234567890", }; TAILQ_FOREACH(hf, hl, tailq) { (void)printf("%s test suite:\n", hf->name); for (i = 0; i < 8; i++) { hf->init(&context); hf->update((void *)&context, test_strings[i], (unsigned int)strlen(test_strings[i])); digest_end(hf, &context, digest, sizeof(digest), hf->base64); digest_printstr(hf, test_strings[i], digest); } /* Now simulate a string of a million 'a' characters. */ memset(buf, 'a', sizeof(buf)); hf->init(&context); for (i = 0; i < 1000; i++) hf->update(&context, buf, (unsigned int)sizeof(buf)); digest_end(hf, &context, digest, sizeof(digest), hf->base64); digest_print(hf, "one million 'a' characters", digest); } } #endif /* !defined(SHA2_ONLY) */ void usage(void) { #if !defined(SHA2_ONLY) if (strcmp(__progname, "cksum") == 0) fprintf(stderr, "usage: %s [-bcpqrtx] [-a algorithms] [-C checklist] " "[-h hashfile]\n" " [-s string] [file ...]\n", __progname); else #endif /* !defined(SHA2_ONLY) */ fprintf(stderr, "usage:" "\t%s [-bcpqrtx] [-C checklist] [-h hashfile] [-s string] " "[file ...]\n", __progname); exit(EXIT_FAILURE); }