1
2
3
4
5
6
7
8
9
10
11 package org.eclipse.jgit.util.sha1;
12
13 import static java.nio.charset.StandardCharsets.UTF_8;
14 import static org.junit.Assert.assertEquals;
15 import static org.junit.Assert.assertTrue;
16 import static org.junit.Assert.fail;
17 import static org.junit.Assume.assumeFalse;
18 import static org.junit.Assume.assumeTrue;
19
20 import java.io.IOException;
21 import java.io.InputStream;
22 import java.nio.ByteBuffer;
23 import java.security.MessageDigest;
24 import java.security.NoSuchAlgorithmException;
25
26 import org.eclipse.jgit.junit.MockSystemReader;
27 import org.eclipse.jgit.lib.Constants;
28 import org.eclipse.jgit.lib.ObjectId;
29 import org.eclipse.jgit.util.IO;
30 import org.eclipse.jgit.util.SystemReader;
31 import org.eclipse.jgit.util.sha1.SHA1.Sha1Implementation;
32 import org.junit.After;
33 import org.junit.Before;
34 import org.junit.experimental.theories.DataPoints;
35 import org.junit.experimental.theories.Theories;
36 import org.junit.experimental.theories.Theory;
37 import org.junit.runner.RunWith;
38
39 @RunWith(Theories.class)
40 public class SHA1Test {
41 private static final String TEST1 = "abc";
42
43 private static final String TEST2a = "abcdbcdecdefdefgefghfghighijhi";
44 private static final String TEST2b = "jkijkljklmklmnlmnomnopnopq";
45 private static final String TEST2 = TEST2a + TEST2b;
46
47 @DataPoints
48 public static Sha1Implementation[] getDataPoints() {
49 return new Sha1Implementation[] { Sha1Implementation.JAVA,
50 Sha1Implementation.JDKNATIVE };
51 }
52
53 private Sha1Implementation sha1Implementation;
54
55 public SHA1Test(Sha1Implementation impl) {
56 this.sha1Implementation = impl;
57 }
58
59 @Before
60 public void setUp() throws Exception {
61 MockSystemReader mockSystemReader = new MockSystemReader();
62 SystemReader.setInstance(mockSystemReader);
63 System.setProperty("org.eclipse.jgit.util.sha1.implementation",
64 sha1Implementation.name());
65 }
66
67 @After
68 public void tearDown() {
69 SystemReader.setInstance(null);
70 }
71
72 @Theory
73 public void test0() throws NoSuchAlgorithmException {
74 ObjectId exp = ObjectId
75 .fromString("da39a3ee5e6b4b0d3255bfef95601890afd80709");
76
77 MessageDigest m = MessageDigest.getInstance("SHA-1");
78 m.update(new byte[] {});
79 ObjectId m1 = ObjectId.fromRaw(m.digest());
80
81 SHA1 s = SHA1.newInstance();
82 s.update(new byte[] {});
83 ObjectId s1 = ObjectId.fromRaw(s.digest());
84
85 s.reset();
86 s.update(new byte[] {});
87 ObjectId s2 = s.toObjectId();
88
89 assertEquals(m1, s1);
90 assertEquals(exp, s1);
91 assertEquals(exp, s2);
92 }
93
94 @Theory
95 public void test1() throws NoSuchAlgorithmException {
96 ObjectId exp = ObjectId
97 .fromString("a9993e364706816aba3e25717850c26c9cd0d89d");
98
99 MessageDigest m = MessageDigest.getInstance("SHA-1");
100 m.update(TEST1.getBytes(UTF_8));
101 ObjectId m1 = ObjectId.fromRaw(m.digest());
102
103 SHA1 s = SHA1.newInstance();
104 s.update(TEST1.getBytes(UTF_8));
105 ObjectId s1 = ObjectId.fromRaw(s.digest());
106
107 s.reset();
108 s.update(TEST1.getBytes(UTF_8));
109 ObjectId s2 = s.toObjectId();
110
111 assertEquals(m1, s1);
112 assertEquals(exp, s1);
113 assertEquals(exp, s2);
114 }
115
116 @Theory
117 public void test2() throws NoSuchAlgorithmException {
118 ObjectId exp = ObjectId
119 .fromString("84983e441c3bd26ebaae4aa1f95129e5e54670f1");
120
121 MessageDigest m = MessageDigest.getInstance("SHA-1");
122 m.update(TEST2.getBytes(UTF_8));
123 ObjectId m1 = ObjectId.fromRaw(m.digest());
124
125 SHA1 s = SHA1.newInstance();
126 s.update(TEST2.getBytes(UTF_8));
127 ObjectId s1 = ObjectId.fromRaw(s.digest());
128
129 s.reset();
130 s.update(TEST2.getBytes(UTF_8));
131 ObjectId s2 = s.toObjectId();
132
133 assertEquals(m1, s1);
134 assertEquals(exp, s1);
135 assertEquals(exp, s2);
136 }
137
138 @Theory
139 public void shatteredCollision()
140 throws IOException, NoSuchAlgorithmException {
141 assumeFalse(
142 System.getProperty("org.eclipse.jgit.util.sha1.implementation")
143 .equalsIgnoreCase("jdkNative"));
144
145 byte[] pdf1 = read("shattered-1.pdf", 422435);
146 byte[] pdf2 = read("shattered-2.pdf", 422435);
147 MessageDigest md;
148 SHA1 s;
149
150
151 ObjectId bad = ObjectId
152 .fromString("38762cf7f55934b34d179ae6a4c80cadccbb7f0a");
153 md = MessageDigest.getInstance("SHA-1");
154 md.update(pdf1);
155 assertEquals("shattered-1 collides", bad,
156 ObjectId.fromRaw(md.digest()));
157 s = SHA1.newInstance().setDetectCollision(false);
158 s.update(pdf1);
159 assertEquals("shattered-1 collides", bad, s.toObjectId());
160
161 md = MessageDigest.getInstance("SHA-1");
162 md.update(pdf2);
163 assertEquals("shattered-2 collides", bad,
164 ObjectId.fromRaw(md.digest()));
165 s = SHA1.newInstance().setDetectCollision(false);
166 s.update(pdf2);
167 assertEquals("shattered-2 collides", bad, s.toObjectId());
168
169
170 s = SHA1.newInstance().setDetectCollision(true);
171 s.update(pdf1);
172 try {
173 s.digest();
174 fail("expected " + Sha1CollisionException.class.getSimpleName());
175 } catch (Sha1CollisionException e) {
176 assertEquals(e.getMessage(),
177 "SHA-1 collision detected on " + bad.name());
178 }
179
180 s = SHA1.newInstance().setDetectCollision(true);
181 s.update(pdf2);
182 try {
183 s.digest();
184 fail("expected " + Sha1CollisionException.class.getSimpleName());
185 } catch (Sha1CollisionException e) {
186 assertEquals(e.getMessage(),
187 "SHA-1 collision detected on " + bad.name());
188 }
189 }
190
191 @Theory
192 public void shatteredStoredInGitBlob() throws IOException {
193 assumeFalse(
194 System.getProperty("org.eclipse.jgit.util.sha1.implementation")
195 .equalsIgnoreCase("jdkNative"));
196
197 byte[] pdf1 = read("shattered-1.pdf", 422435);
198 byte[] pdf2 = read("shattered-2.pdf", 422435);
199
200
201
202
203
204 ObjectId id1 = blob(pdf1,
205 SHA1.newInstance().setDetectCollision(true));
206 ObjectId id2 = blob(pdf2,
207 SHA1.newInstance().setDetectCollision(true));
208
209 assertEquals(
210 ObjectId.fromString("ba9aaa145ccd24ef760cf31c74d8f7ca1a2e47b0"),
211 id1);
212 assertEquals(
213 ObjectId.fromString("b621eeccd5c7edac9b7dcba35a8d5afd075e24f2"),
214 id2);
215 }
216
217 @Theory
218 public void detectsShatteredByDefault() throws IOException {
219 assumeFalse(
220 System.getProperty("org.eclipse.jgit.util.sha1.implementation")
221 .equalsIgnoreCase("jdkNative"));
222
223 assumeTrue(System.getProperty("org.eclipse.jgit.util.sha1.detectCollision") == null);
224 assumeTrue(System.getProperty("org.eclipse.jgit.util.sha1.safeHash") == null);
225
226 byte[] pdf1 = read("shattered-1.pdf", 422435);
227 SHA1 s = SHA1.newInstance();
228 s.update(pdf1);
229 try {
230 s.digest();
231 fail("expected " + Sha1CollisionException.class.getSimpleName());
232 } catch (Sha1CollisionException e) {
233 assertTrue("shattered-1 detected", true);
234 }
235 }
236
237 private static ObjectId blob(byte[] pdf1, SHA1 s) {
238 s.update(Constants.encodedTypeString(Constants.OBJ_BLOB));
239 s.update((byte) ' ');
240 s.update(Constants.encodeASCII(pdf1.length));
241 s.update((byte) 0);
242 s.update(pdf1);
243 return s.toObjectId();
244 }
245
246 private byte[] read(String name, int sizeHint) throws IOException {
247 try (InputStream in = getClass().getResourceAsStream(name)) {
248 ByteBuffer buf = IO.readWholeStream(in, sizeHint);
249 byte[] r = new byte[buf.remaining()];
250 buf.get(r);
251 return r;
252 }
253 }
254 }