From 7f6a0a82eafb84548e5a298485533a2fd506f98d Mon Sep 17 00:00:00 2001 From: Jonathan McDowell Date: Mon, 4 Nov 2013 20:52:38 -0800 Subject: [PATCH] Add SHA1x hash support A hash variant that uses 8 SHA-1 instances and results in a double width hash (320 bits instead of SHA-1's 160 bits). This was used in older versions of PGP and there are quite a few keys on the keyservers with signatures using it. Added mainly to enable the sig hash checking routine to do some sanity checking on these signatures. --- Makefile.in | 4 +- sha1x.c | 144 ++++++++++++++++++++++++++++++++++++++++++++++++++++ sha1x.h | 40 +++++++++++++++ sigcheck.c | 9 ++++ 4 files changed, 196 insertions(+), 1 deletion(-) create mode 100644 sha1x.c create mode 100644 sha1x.h diff --git a/Makefile.in b/Makefile.in index 90db9f9..da116ac 100644 --- a/Makefile.in +++ b/Makefile.in @@ -21,7 +21,8 @@ PROGS = add lookup hashquery gpgwww onak splitkeys onak-mail.pl stripkey \ wotsap CORE_OBJS = armor.o charfuncs.o decodekey.o getcgi.o hash.o marshal.o \ keyid.o keyindex.o ll.o mem.o onak-conf.o parsekey.o sigcheck.o \ - log.o photoid.o wordlist.o cleanup.o merge.o sendsync.o keyarray.o + log.o photoid.o wordlist.o cleanup.o merge.o sendsync.o keyarray.o \ + sha1x.o ifeq (x@NETTLE_LIBS@, x) CORE_OBJS += md5.o sha1.o endif @@ -29,6 +30,7 @@ SRCS = armor.c parsekey.c merge.c keyid.c md5.c sha1.c main.c getcgi.c mem.c \ keyindex.c stats.c lookup.c add.c keydb_$(DBTYPE).c ll.c hash.c \ gpgwww.c onak-conf.c charfuncs.c sendsync.c log.c photoid.c sigcheck.c \ wordlist.c cleankey.c cleanup.c keyarray.c hashquery.c marshal.c \ + sha1x.c \ $(foreach be,@BACKENDS@,keydb_$(be).c) PROGS_LDFLAGS_EXTRA = diff --git a/sha1x.c b/sha1x.c new file mode 100644 index 0000000..1242440 --- /dev/null +++ b/sha1x.c @@ -0,0 +1,144 @@ +/* + * sha1x.c - Double width SHA-1 as per PGP 5.5 + * + * Copyright 2013 Jonathan McDowell + * + * This is based on the description / code from PGP 5.5, where it is called + * "SHA Double". I have seen reference to SHA1X elsewhere, which is a more + * concise name, so I have used that here. + * + * I can't imagine there is a good reason to use this code other than for + * verifying signatures on ancient PGP keys. + * + * Placed into the public domain. + */ + +#include "config.h" + +#include +#include +#include + +#ifdef HAVE_NETTLE +#include +#else +#include "sha1.h" +#endif +#include "sha1x.h" + +#define BUFSIZE 64 + +void sha1x_init(struct sha1x_ctx *ctx) +{ + unsigned char zeros[3]; + + zeros[0] = zeros[1] = zeros[2] = 0; + sha1_init(&ctx->a); + sha1_init(&ctx->b); + sha1_init(&ctx->c); + sha1_init(&ctx->d); + + sha1_update(&ctx->b, 1, zeros); + sha1_update(&ctx->c, 2, zeros); + sha1_update(&ctx->d, 3, zeros); + + /* We start at 0, so even */ + ctx->odd = false; +} + +void sha1x_update(struct sha1x_ctx *ctx, unsigned length, const uint8_t *data) +{ + uint8_t evenbuf[BUFSIZE], *evenp; + uint8_t oddbuf[BUFSIZE], *oddp; + bool newodd; + + oddp = oddbuf; + evenp = evenbuf; + + /* Track whether our first byte next time round is even or odd */ + newodd = ctx->odd ^ (length & 1); + + /* If our first byte is odd this time, add it to the odd buffer */ + if (ctx->odd && length != 0) { + *oddp++ = *data++; + length--; + } + ctx->odd = newodd; + + while (length != 0) { + while (length != 0 && oddp < oddbuf + BUFSIZE) { + *evenp++ = *data++; + length--; + if (length == 0) { + break; + } + *oddp++ = *data++; + length--; + } + sha1_update(&ctx->a, evenp - evenbuf, evenbuf); + sha1_update(&ctx->b, evenp - evenbuf, evenbuf); + sha1_update(&ctx->c, oddp - oddbuf, oddbuf); + sha1_update(&ctx->d, oddp - oddbuf, oddbuf); + + oddp = oddbuf; + evenp = evenbuf; + } +} + +void sha1x_digest(struct sha1x_ctx *ctx, unsigned length, uint8_t *digest) +{ + uint8_t sha1final[8][SHA1_DIGEST_SIZE]; + uint8_t zeros[7]; + struct sha1_ctx e, f, g, h; + int i; + + sha1_digest(&ctx->a, SHA1_DIGEST_SIZE, sha1final[0]); + sha1_digest(&ctx->b, SHA1_DIGEST_SIZE, sha1final[1]); + sha1_digest(&ctx->c, SHA1_DIGEST_SIZE, sha1final[2]); + sha1_digest(&ctx->d, SHA1_DIGEST_SIZE, sha1final[3]); + + /* XOR sha1-c into sha1-a & sha1-d into sha1-b */ + for (i = 0; i < SHA1_DIGEST_SIZE; i++) { + sha1final[0][i] ^= sha1final[2][i]; + sha1final[1][i] ^= sha1final[3][i]; + } + + sha1_init(&e); + sha1_init(&f); + sha1_init(&g); + sha1_init(&h); + + memset(zeros, 0, sizeof(zeros)); + sha1_update(&e, 4, zeros); + sha1_update(&f, 5, zeros); + sha1_update(&g, 6, zeros); + sha1_update(&h, 7, zeros); + + sha1_update(&e, SHA1_DIGEST_SIZE, sha1final[0]); + sha1_update(&f, SHA1_DIGEST_SIZE, sha1final[0]); + sha1_update(&g, SHA1_DIGEST_SIZE, sha1final[1]); + sha1_update(&h, SHA1_DIGEST_SIZE, sha1final[1]); + + sha1_digest(&e, SHA1_DIGEST_SIZE, sha1final[4]); + sha1_digest(&f, SHA1_DIGEST_SIZE, sha1final[5]); + sha1_digest(&g, SHA1_DIGEST_SIZE, sha1final[6]); + sha1_digest(&h, SHA1_DIGEST_SIZE, sha1final[7]); + + /* XOR sha1-g into sha1-e & sha1-h into sha1-f */ + for (i = 0; i < SHA1_DIGEST_SIZE; i++) { + sha1final[4][i] ^= sha1final[6][i]; + sha1final[5][i] ^= sha1final[7][i]; + } + + if (length > SHA1X_DIGEST_SIZE) { + length = SHA1X_DIGEST_SIZE; + } + + for (i = 0; i < length; i++) { + if (i < SHA1_DIGEST_SIZE) { + digest[i] = sha1final[4][i]; + } else { + digest[i] = sha1final[6][i - SHA1_DIGEST_SIZE]; + } + } +} diff --git a/sha1x.h b/sha1x.h new file mode 100644 index 0000000..f1d5dca --- /dev/null +++ b/sha1x.h @@ -0,0 +1,40 @@ +/* + * sha1x.h - Double width SHA-1 as per PGP 5.5 + * + * Copyright 2013 Jonathan McDowell + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + */ + +#ifndef __SHA1X_H__ +#define __SHA1X_H__ + +#include +#include + +#ifdef HAVE_NETTLE +#include +#else +#include "sha1.h" +#endif + +#define SHA1X_DIGEST_SIZE (2 * SHA1_DIGEST_SIZE) + +struct sha1x_ctx { + struct sha1_ctx a, b, c, d; + bool odd; + unsigned char result[SHA1X_DIGEST_SIZE]; +}; + +void sha1x_init(struct sha1x_ctx *ctx); +void sha1x_update(struct sha1x_ctx *ctx, unsigned length, const uint8_t *data); +void sha1x_digest(struct sha1x_ctx *ctx, unsigned length, uint8_t *digest); + +#endif /* __SHA1X_H__ */ diff --git a/sigcheck.c b/sigcheck.c index cbc28b8..900d351 100644 --- a/sigcheck.c +++ b/sigcheck.c @@ -34,6 +34,7 @@ #include "md5.h" #include "sha1.h" #endif +#include "sha1x.h" int check_packet_sighash(struct openpgp_publickey *key, struct openpgp_packet *packet, @@ -43,6 +44,7 @@ int check_packet_sighash(struct openpgp_publickey *key, uint8_t *sighash; size_t siglen, unhashedlen; struct sha1_ctx sha1_context; + struct sha1x_ctx sha1x_context; struct md5_ctx md5_context; #ifdef NETTLE_WITH_RIPEMD160 struct ripemd160_ctx ripemd160_context; @@ -187,6 +189,13 @@ int check_packet_sighash(struct openpgp_publickey *key, logthing(LOGTHING_INFO, "RIPEMD160 support not available."); return -1; #endif + case OPENPGP_HASH_SHA1X: + sha1x_init(&sha1x_context); + for (i = 0; i < chunks; i++) { + sha1x_update(&sha1x_context, hashlen[i], hashdata[i]); + } + sha1x_digest(&sha1x_context, 20, hash); + break; case OPENPGP_HASH_SHA224: #ifdef NETTLE_WITH_SHA224 sha224_init(&sha224_context); -- 2.39.2