Skip to content

Commit 8325e43

Browse files
peffgitster
authored andcommitted
Makefile: add DC_SHA1 knob
This knob lets you use the sha1dc implementation from: https://github.com/cr-marcstevens/sha1collisiondetection which can detect certain types of collision attacks (even when we only see half of the colliding pair). So it mitigates any attack which consists of getting the "good" half of a collision into a trusted repository, and then later replacing it with the "bad" half. The "good" half is rejected by the victim's version of Git (and even if they run an old version of Git, any sha1dc-enabled git will complain loudly if it ever has to interact with the object). The big downside is that it's slower than either the openssl or block-sha1 implementations. Here are some timings based off of linux.git: - compute sha1 over whole packfile sha1dc: 3.580s blk-sha1: 2.046s (-43%) openssl: 1.335s (-62%) - rev-list --all --objects sha1dc: 33.512s blk-sha1: 33.514s (+0.0%) openssl: 33.650s (+0.4%) - git log --no-merges -10000 -p sha1dc: 8.124s blk-sha1: 7.986s (-1.6%) openssl: 8.203s (+0.9%) - index-pack --verify sha1dc: 4m19s blk-sha1: 2m57s (-32%) openssl: 2m19s (-42%) So overall the sha1 computation with collision detection is about 1.75x slower than block-sha1, and 2.7x slower than sha1. But of course most operations do more than just sha1. Normal object access isn't really slowed at all (both the +/- changes there are well within the run-to-run noise); any changes are drowned out by the other work Git is doing. The most-affected operation is `index-pack --verify`, which is essentially just computing the sha1 on every object. This is similar to the `index-pack` invocation that the receiver of a push or fetch would perform. So clearly there's some extra CPU load here. There will also be some latency for the user, though keep in mind that such an operation will generally be network bound (this is about a 1.2GB packfile). Some of that extra CPU is "free" in the sense that we use it while the pack is streaming in anyway. But most of it comes during the delta-resolution phase, after the whole pack has been received. So we can imagine that for this (quite large) push, the user might have to wait an extra 100 seconds over openssl (which is what we use now). If we assume they can push to us at 20Mbit/s, that's 480s for a 1.2GB pack, which is only 20% slower. Signed-off-by: Jeff King <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent c0c2006 commit 8325e43

File tree

4 files changed

+47
-0
lines changed

4 files changed

+47
-0
lines changed

Makefile

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,10 @@ all::
142142
# Define PPC_SHA1 environment variable when running make to make use of
143143
# a bundled SHA1 routine optimized for PowerPC.
144144
#
145+
# Define DC_SHA1 to unconditionally enable the collision-detecting sha1
146+
# algorithm. This is slower, but may detect attempted collision attacks.
147+
# Takes priority over other *_SHA1 knobs.
148+
#
145149
# Define SHA1_MAX_BLOCK_SIZE to limit the amount of data that will be hashed
146150
# in one call to the platform's SHA1_Update(). e.g. APPLE_COMMON_CRYPTO
147151
# wants 'SHA1_MAX_BLOCK_SIZE=1024L*1024L*1024L' defined.
@@ -1386,6 +1390,11 @@ ifdef APPLE_COMMON_CRYPTO
13861390
SHA1_MAX_BLOCK_SIZE = 1024L*1024L*1024L
13871391
endif
13881392

1393+
ifdef DC_SHA1
1394+
LIB_OBJS += sha1dc/sha1.o
1395+
LIB_OBJS += sha1dc/ubc_check.o
1396+
BASIC_CFLAGS += -DSHA1_DC
1397+
else
13891398
ifdef BLK_SHA1
13901399
LIB_OBJS += block-sha1/sha1.o
13911400
BASIC_CFLAGS += -DSHA1_BLK
@@ -1403,6 +1412,7 @@ else
14031412
endif
14041413
endif
14051414
endif
1415+
endif
14061416

14071417
ifdef SHA1_MAX_BLOCK_SIZE
14081418
LIB_OBJS += compat/sha1-chunked.o

hash.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
#include <CommonCrypto/CommonDigest.h>
88
#elif defined(SHA1_OPENSSL)
99
#include <openssl/sha.h>
10+
#elif defined(SHA1_DC)
11+
#include "sha1dc/sha1.h"
1012
#else /* SHA1_BLK */
1113
#include "block-sha1/sha1.h"
1214
#endif

sha1dc/sha1.c

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1786,3 +1786,23 @@ int SHA1DCFinal(unsigned char output[20], SHA1_CTX *ctx)
17861786
output[19] = (unsigned char)(ctx->ihv[4]);
17871787
return ctx->found_collision;
17881788
}
1789+
1790+
void git_SHA1DCFinal(unsigned char hash[20], SHA1_CTX *ctx)
1791+
{
1792+
if (!SHA1DCFinal(hash, ctx))
1793+
return;
1794+
die("SHA-1 appears to be part of a collision attack: %s",
1795+
sha1_to_hex(hash));
1796+
}
1797+
1798+
void git_SHA1DCUpdate(SHA1_CTX *ctx, const void *vdata, unsigned long len)
1799+
{
1800+
const char *data = vdata;
1801+
/* We expect an unsigned long, but sha1dc only takes an int */
1802+
while (len > INT_MAX) {
1803+
SHA1DCUpdate(ctx, data, INT_MAX);
1804+
data += INT_MAX;
1805+
len -= INT_MAX;
1806+
}
1807+
SHA1DCUpdate(ctx, data, len);
1808+
}

sha1dc/sha1.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,21 @@ void SHA1DCUpdate(SHA1_CTX*, const char*, size_t);
100100
/* returns: 0 = no collision detected, otherwise = collision found => warn user for active attack */
101101
int SHA1DCFinal(unsigned char[20], SHA1_CTX*);
102102

103+
/*
104+
* Same as SHA1DCFinal, but convert collision attack case into a verbose die().
105+
*/
106+
void git_SHA1DCFinal(unsigned char [20], SHA1_CTX *);
107+
108+
/*
109+
* Same as SHA1DCUpdate, but adjust types to match git's usual interface.
110+
*/
111+
void git_SHA1DCUpdate(SHA1_CTX *ctx, const void *data, unsigned long len);
112+
113+
#define platform_SHA_CTX SHA1_CTX
114+
#define platform_SHA1_Init SHA1DCInit
115+
#define platform_SHA1_Update git_SHA1DCUpdate
116+
#define platform_SHA1_Final git_SHA1DCFinal
117+
103118
#if defined(__cplusplus)
104119
}
105120
#endif

0 commit comments

Comments
 (0)