21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
22 * or visit www.oracle.com if you need additional information or have any |
22 * or visit www.oracle.com if you need additional information or have any |
23 * questions. |
23 * questions. |
24 */ |
24 */ |
25 |
25 |
26 |
|
27 package sun.security.ssl; |
26 package sun.security.ssl; |
28 |
27 |
29 import java.io.ByteArrayOutputStream; |
28 import java.io.ByteArrayOutputStream; |
30 import java.security.*; |
29 import java.io.IOException; |
31 import java.util.Locale; |
|
32 import java.nio.ByteBuffer; |
30 import java.nio.ByteBuffer; |
33 |
31 import java.security.MessageDigest; |
34 /** |
32 import java.util.Arrays; |
35 * Abstraction for the SSL/TLS hash of all handshake messages that is |
33 import java.util.LinkedList; |
36 * maintained to verify the integrity of the negotiation. Internally, |
34 import javax.crypto.SecretKey; |
37 * it consists of an MD5 and an SHA1 digest. They are used in the client |
35 import sun.security.util.MessageDigestSpi2; |
38 * and server finished messages and in certificate verify messages (if sent). |
36 |
39 * |
|
40 * This class transparently deals with cloneable and non-cloneable digests. |
|
41 * |
|
42 * This class now supports TLS 1.2 also. The key difference for TLS 1.2 |
|
43 * is that you cannot determine the hash algorithms for CertificateVerify |
|
44 * at a early stage. On the other hand, it's simpler than TLS 1.1 (and earlier) |
|
45 * that there is no messy MD5+SHA1 digests. |
|
46 * |
|
47 * You need to obey these conventions when using this class: |
|
48 * |
|
49 * 1. protocolDetermined(version) should be called when the negotiated |
|
50 * protocol version is determined. |
|
51 * |
|
52 * 2. Before protocolDetermined() is called, only update(), and reset() |
|
53 * and setFinishedAlg() can be called. |
|
54 * |
|
55 * 3. After protocolDetermined() is called, reset() cannot be called. |
|
56 * |
|
57 * 4. After protocolDetermined() is called, if the version is pre-TLS 1.2, |
|
58 * getFinishedHash() cannot be called. Otherwise, |
|
59 * getMD5Clone() and getSHAClone() cannot be called. |
|
60 * |
|
61 * 5. getMD5Clone() and getSHAClone() can only be called after |
|
62 * protocolDetermined() is called and version is pre-TLS 1.2. |
|
63 * |
|
64 * 6. getFinishedHash() can only be called after protocolDetermined() |
|
65 * and setFinishedAlg() have been called and the version is TLS 1.2. |
|
66 * |
|
67 * Suggestion: Call protocolDetermined() and setFinishedAlg() |
|
68 * as early as possible. |
|
69 * |
|
70 * Example: |
|
71 * <pre> |
|
72 * HandshakeHash hh = new HandshakeHash(...) |
|
73 * hh.protocolDetermined(ProtocolVersion.TLS12); |
|
74 * hh.update(clientHelloBytes); |
|
75 * hh.setFinishedAlg("SHA-256"); |
|
76 * hh.update(serverHelloBytes); |
|
77 * ... |
|
78 * hh.update(CertificateVerifyBytes); |
|
79 * ... |
|
80 * hh.update(finished1); |
|
81 * byte[] finDigest1 = hh.getFinishedHash(); |
|
82 * hh.update(finished2); |
|
83 * byte[] finDigest2 = hh.getFinishedHash(); |
|
84 * </pre> |
|
85 */ |
|
86 final class HandshakeHash { |
37 final class HandshakeHash { |
87 |
38 private TranscriptHash transcriptHash; |
88 // Common |
39 private LinkedList<byte[]> reserves; // one handshake message per entry |
89 |
40 private boolean hasBeenUsed; |
90 // -1: unknown |
41 |
91 // 1: <=TLS 1.1 |
42 HandshakeHash() { |
92 // 2: TLS 1.2 |
43 this.transcriptHash = new CacheOnlyHash(); |
93 private int version = -1; |
44 this.reserves = new LinkedList<>(); |
94 private ByteArrayOutputStream data = new ByteArrayOutputStream(); |
45 this.hasBeenUsed = false; |
95 |
46 } |
96 // For TLS 1.1 |
47 |
97 private MessageDigest md5, sha; |
48 // fix the negotiated protocol version and cipher suite |
98 private final int clonesNeeded; // needs to be saved for later use |
49 void determine(ProtocolVersion protocolVersion, |
99 |
50 CipherSuite cipherSuite) { |
100 // For TLS 1.2 |
51 if (!(transcriptHash instanceof CacheOnlyHash)) { |
101 private MessageDigest finMD; |
52 throw new IllegalStateException( |
102 |
53 "Not expected instance of transcript hash"); |
103 // Cache for input record handshake hash computation |
54 } |
104 private ByteArrayOutputStream reserve = new ByteArrayOutputStream(); |
55 |
105 |
56 CacheOnlyHash coh = (CacheOnlyHash)transcriptHash; |
106 /** |
57 if (protocolVersion.useTLS13PlusSpec()) { |
107 * Create a new HandshakeHash. needCertificateVerify indicates whether |
58 transcriptHash = new T13HandshakeHash(cipherSuite); |
108 * a hash for the certificate verify message is required. |
59 } else if (protocolVersion.useTLS12PlusSpec()) { |
109 */ |
60 transcriptHash = new T12HandshakeHash(cipherSuite); |
110 HandshakeHash(boolean needCertificateVerify) { |
61 } else if (protocolVersion.useTLS10PlusSpec()) { |
111 // We may rework the code later, but for now we use hard-coded number |
62 transcriptHash = new T10HandshakeHash(cipherSuite); |
112 // of clones if the underlying MessageDigests are not cloneable. |
63 } else { |
113 // |
64 transcriptHash = new S30HandshakeHash(cipherSuite); |
114 // The number used here is based on the current handshake protocols and |
65 } |
115 // implementation. It may be changed if the handshake processe gets |
66 |
116 // changed in the future, for example adding a new extension that |
67 byte[] reserved = coh.baos.toByteArray(); |
117 // requires handshake hash. Please be careful about the number of |
68 if (reserved.length != 0) { |
118 // clones if additional handshak hash is required in the future. |
69 transcriptHash.update(reserved, 0, reserved.length); |
119 // |
70 } |
120 // For the current implementation, the handshake hash is required for |
71 } |
121 // the following items: |
72 |
122 // . CertificateVerify handshake message (optional) |
73 HandshakeHash copy() { |
123 // . client Finished handshake message |
74 if (transcriptHash instanceof CacheOnlyHash) { |
124 // . server Finished Handshake message |
75 HandshakeHash result = new HandshakeHash(); |
125 // . the extended Master Secret extension [RFC 7627] |
76 result.transcriptHash = ((CacheOnlyHash)transcriptHash).copy(); |
126 // |
77 result.reserves = new LinkedList<>(reserves); |
127 // Note that a late call to server setNeedClientAuth dose not update |
78 result.hasBeenUsed = hasBeenUsed; |
128 // the number of clones. We may address the issue later. |
79 return result; |
129 // |
80 } else { |
130 // Note for safety, we allocate one more clone for the current |
81 throw new IllegalStateException("Hash does not support copying"); |
131 // implementation. We may consider it more carefully in the future |
82 } |
132 // for the exact number or rework the code in a different way. |
83 } |
133 clonesNeeded = needCertificateVerify ? 5 : 4; |
84 |
134 } |
85 void receive(byte[] input) { |
135 |
86 reserves.add(Arrays.copyOf(input, input.length)); |
136 void reserve(ByteBuffer input) { |
87 } |
|
88 |
|
89 void receive(byte[] input, int offset, int length) { |
|
90 reserves.add(Arrays.copyOfRange(input, offset, offset + length)); |
|
91 } |
|
92 |
|
93 void receive(ByteBuffer input, int length) { |
137 if (input.hasArray()) { |
94 if (input.hasArray()) { |
138 reserve.write(input.array(), |
95 int from = input.position() + input.arrayOffset(); |
|
96 int to = from + length; |
|
97 reserves.add(Arrays.copyOfRange(input.array(), from, to)); |
|
98 } else { |
|
99 int inPos = input.position(); |
|
100 byte[] holder = new byte[length]; |
|
101 input.get(holder); |
|
102 input.position(inPos); |
|
103 reserves.add(Arrays.copyOf(holder, holder.length)); |
|
104 } |
|
105 } |
|
106 void receive(ByteBuffer input) { |
|
107 receive(input, input.remaining()); |
|
108 } |
|
109 |
|
110 // For HelloRetryRequest only! Please use this method very carefully! |
|
111 void push(byte[] input) { |
|
112 reserves.push(Arrays.copyOf(input, input.length)); |
|
113 } |
|
114 |
|
115 // For PreSharedKey to modify the state of the PSK binder hash |
|
116 byte[] removeLastReceived() { |
|
117 return reserves.removeLast(); |
|
118 } |
|
119 |
|
120 void deliver(byte[] input) { |
|
121 update(); |
|
122 transcriptHash.update(input, 0, input.length); |
|
123 } |
|
124 |
|
125 void deliver(byte[] input, int offset, int length) { |
|
126 update(); |
|
127 transcriptHash.update(input, offset, length); |
|
128 } |
|
129 |
|
130 void deliver(ByteBuffer input) { |
|
131 update(); |
|
132 if (input.hasArray()) { |
|
133 transcriptHash.update(input.array(), |
139 input.position() + input.arrayOffset(), input.remaining()); |
134 input.position() + input.arrayOffset(), input.remaining()); |
140 } else { |
135 } else { |
141 int inPos = input.position(); |
136 int inPos = input.position(); |
142 byte[] holder = new byte[input.remaining()]; |
137 byte[] holder = new byte[input.remaining()]; |
143 input.get(holder); |
138 input.get(holder); |
144 input.position(inPos); |
139 input.position(inPos); |
145 reserve.write(holder, 0, holder.length); |
140 transcriptHash.update(holder, 0, holder.length); |
146 } |
141 } |
147 } |
142 } |
148 |
143 |
149 void reserve(byte[] b, int offset, int len) { |
144 // Use one handshake message if it has not been used. |
150 reserve.write(b, offset, len); |
145 void utilize() { |
151 } |
146 if (hasBeenUsed) { |
152 |
147 return; |
153 void reload() { |
148 } |
154 if (reserve.size() != 0) { |
149 if (reserves.size() != 0) { |
155 byte[] bytes = reserve.toByteArray(); |
150 byte[] holder = reserves.remove(); |
156 reserve.reset(); |
151 transcriptHash.update(holder, 0, holder.length); |
157 update(bytes, 0, bytes.length); |
152 hasBeenUsed = true; |
158 } |
153 } |
159 } |
154 } |
160 |
155 |
161 void update(ByteBuffer input) { |
156 // Consume one handshake message if it has not been consumed. |
162 |
157 void consume() { |
163 // reload if there are reserved messages. |
158 if (hasBeenUsed) { |
164 reload(); |
159 hasBeenUsed = false; |
165 |
160 return; |
166 int inPos = input.position(); |
161 } |
167 switch (version) { |
162 if (reserves.size() != 0) { |
168 case 1: |
163 byte[] holder = reserves.remove(); |
169 md5.update(input); |
164 transcriptHash.update(holder, 0, holder.length); |
170 input.position(inPos); |
165 } |
171 |
166 } |
172 sha.update(input); |
167 |
173 input.position(inPos); |
168 void update() { |
174 |
169 while (reserves.size() != 0) { |
175 break; |
170 byte[] holder = reserves.remove(); |
176 default: |
171 transcriptHash.update(holder, 0, holder.length); |
177 if (finMD != null) { |
172 } |
178 finMD.update(input); |
173 hasBeenUsed = false; |
179 input.position(inPos); |
174 } |
|
175 |
|
176 byte[] digest() { |
|
177 // Note that the reserve handshake message may be not a part of |
|
178 // the expected digest. |
|
179 return transcriptHash.digest(); |
|
180 } |
|
181 |
|
182 void finish() { |
|
183 this.transcriptHash = new CacheOnlyHash(); |
|
184 this.reserves = new LinkedList<>(); |
|
185 this.hasBeenUsed = false; |
|
186 } |
|
187 |
|
188 // Optional |
|
189 byte[] archived() { |
|
190 // Note that the reserve handshake message may be not a part of |
|
191 // the expected digest. |
|
192 return transcriptHash.archived(); |
|
193 } |
|
194 |
|
195 // Optional, TLS 1.0/1.1 only |
|
196 byte[] digest(String algorithm) { |
|
197 T10HandshakeHash hh = (T10HandshakeHash)transcriptHash; |
|
198 return hh.digest(algorithm); |
|
199 } |
|
200 |
|
201 // Optional, SSL 3.0 only |
|
202 byte[] digest(String algorithm, SecretKey masterSecret) { |
|
203 S30HandshakeHash hh = (S30HandshakeHash)transcriptHash; |
|
204 return hh.digest(algorithm, masterSecret); |
|
205 } |
|
206 |
|
207 // Optional, SSL 3.0 only |
|
208 byte[] digest(boolean useClientLabel, SecretKey masterSecret) { |
|
209 S30HandshakeHash hh = (S30HandshakeHash)transcriptHash; |
|
210 return hh.digest(useClientLabel, masterSecret); |
|
211 } |
|
212 |
|
213 public boolean isHashable(byte handshakeType) { |
|
214 return handshakeType != SSLHandshake.HELLO_REQUEST.id && |
|
215 handshakeType != SSLHandshake.HELLO_VERIFY_REQUEST.id; |
|
216 } |
|
217 |
|
218 interface TranscriptHash { |
|
219 void update(byte[] input, int offset, int length); |
|
220 byte[] digest(); |
|
221 byte[] archived(); // optional |
|
222 } |
|
223 |
|
224 // For cache only. |
|
225 private static final class CacheOnlyHash implements TranscriptHash { |
|
226 private final ByteArrayOutputStream baos; |
|
227 |
|
228 CacheOnlyHash() { |
|
229 this.baos = new ByteArrayOutputStream(); |
|
230 } |
|
231 |
|
232 @Override |
|
233 public void update(byte[] input, int offset, int length) { |
|
234 baos.write(input, offset, length); |
|
235 } |
|
236 |
|
237 @Override |
|
238 public byte[] digest() { |
|
239 throw new IllegalStateException( |
|
240 "Not expected call to handshake hash digest"); |
|
241 } |
|
242 |
|
243 @Override |
|
244 public byte[] archived() { |
|
245 return baos.toByteArray(); |
|
246 } |
|
247 |
|
248 CacheOnlyHash copy() { |
|
249 CacheOnlyHash result = new CacheOnlyHash(); |
|
250 try { |
|
251 baos.writeTo(result.baos); |
|
252 } catch (IOException ex) { |
|
253 throw new RuntimeException("unable to to clone hash state"); |
|
254 } |
|
255 return result; |
|
256 } |
|
257 } |
|
258 |
|
259 static final class S30HandshakeHash implements TranscriptHash { |
|
260 static final byte[] MD5_pad1 = genPad(0x36, 48); |
|
261 static final byte[] MD5_pad2 = genPad(0x5c, 48); |
|
262 |
|
263 static final byte[] SHA_pad1 = genPad(0x36, 40); |
|
264 static final byte[] SHA_pad2 = genPad(0x5c, 40); |
|
265 |
|
266 private static final byte[] SSL_CLIENT = { 0x43, 0x4C, 0x4E, 0x54 }; |
|
267 private static final byte[] SSL_SERVER = { 0x53, 0x52, 0x56, 0x52 }; |
|
268 |
|
269 private final MessageDigest mdMD5; |
|
270 private final MessageDigest mdSHA; |
|
271 private final TranscriptHash md5; |
|
272 private final TranscriptHash sha; |
|
273 private final ByteArrayOutputStream baos; |
|
274 |
|
275 S30HandshakeHash(CipherSuite cipherSuite) { |
|
276 this.mdMD5 = JsseJce.getMessageDigest("MD5"); |
|
277 this.mdSHA = JsseJce.getMessageDigest("SHA"); |
|
278 |
|
279 boolean hasArchived = false; |
|
280 if (mdMD5 instanceof Cloneable) { |
|
281 md5 = new CloneableHash(mdMD5); |
|
282 } else { |
|
283 hasArchived = true; |
|
284 md5 = new NonCloneableHash(mdMD5); |
|
285 } |
|
286 if (mdSHA instanceof Cloneable) { |
|
287 sha = new CloneableHash(mdSHA); |
|
288 } else { |
|
289 hasArchived = true; |
|
290 sha = new NonCloneableHash(mdSHA); |
|
291 } |
|
292 |
|
293 if (hasArchived) { |
|
294 this.baos = null; |
|
295 } else { |
|
296 this.baos = new ByteArrayOutputStream(); |
|
297 } |
|
298 } |
|
299 |
|
300 @Override |
|
301 public void update(byte[] input, int offset, int length) { |
|
302 md5.update(input, offset, length); |
|
303 sha.update(input, offset, length); |
|
304 if (baos != null) { |
|
305 baos.write(input, offset, length); |
|
306 } |
|
307 } |
|
308 |
|
309 @Override |
|
310 public byte[] digest() { |
|
311 byte[] digest = new byte[36]; |
|
312 System.arraycopy(md5.digest(), 0, digest, 0, 16); |
|
313 System.arraycopy(sha.digest(), 0, digest, 16, 20); |
|
314 |
|
315 return digest; |
|
316 } |
|
317 |
|
318 @Override |
|
319 public byte[] archived() { |
|
320 if (baos != null) { |
|
321 return baos.toByteArray(); |
|
322 } else if (md5 instanceof NonCloneableHash) { |
|
323 return md5.archived(); |
|
324 } else { |
|
325 return sha.archived(); |
|
326 } |
|
327 } |
|
328 |
|
329 byte[] digest(boolean useClientLabel, SecretKey masterSecret) { |
|
330 MessageDigest md5Clone = cloneMd5(); |
|
331 MessageDigest shaClone = cloneSha(); |
|
332 |
|
333 if (useClientLabel) { |
|
334 md5Clone.update(SSL_CLIENT); |
|
335 shaClone.update(SSL_CLIENT); |
|
336 } else { |
|
337 md5Clone.update(SSL_SERVER); |
|
338 shaClone.update(SSL_SERVER); |
|
339 } |
|
340 |
|
341 updateDigest(md5Clone, MD5_pad1, MD5_pad2, masterSecret); |
|
342 updateDigest(shaClone, SHA_pad1, SHA_pad2, masterSecret); |
|
343 |
|
344 byte[] digest = new byte[36]; |
|
345 System.arraycopy(md5Clone.digest(), 0, digest, 0, 16); |
|
346 System.arraycopy(shaClone.digest(), 0, digest, 16, 20); |
|
347 |
|
348 return digest; |
|
349 } |
|
350 |
|
351 byte[] digest(String algorithm, SecretKey masterSecret) { |
|
352 if ("RSA".equalsIgnoreCase(algorithm)) { |
|
353 MessageDigest md5Clone = cloneMd5(); |
|
354 MessageDigest shaClone = cloneSha(); |
|
355 updateDigest(md5Clone, MD5_pad1, MD5_pad2, masterSecret); |
|
356 updateDigest(shaClone, SHA_pad1, SHA_pad2, masterSecret); |
|
357 |
|
358 byte[] digest = new byte[36]; |
|
359 System.arraycopy(md5Clone.digest(), 0, digest, 0, 16); |
|
360 System.arraycopy(shaClone.digest(), 0, digest, 16, 20); |
|
361 |
|
362 return digest; |
|
363 } else { |
|
364 MessageDigest shaClone = cloneSha(); |
|
365 updateDigest(shaClone, SHA_pad1, SHA_pad2, masterSecret); |
|
366 return shaClone.digest(); |
|
367 } |
|
368 } |
|
369 |
|
370 private static byte[] genPad(int b, int count) { |
|
371 byte[] padding = new byte[count]; |
|
372 Arrays.fill(padding, (byte)b); |
|
373 return padding; |
|
374 } |
|
375 |
|
376 private MessageDigest cloneMd5() { |
|
377 MessageDigest md5Clone; |
|
378 if (mdMD5 instanceof Cloneable) { |
|
379 try { |
|
380 md5Clone = (MessageDigest)mdMD5.clone(); |
|
381 } catch (CloneNotSupportedException ex) { // unlikely |
|
382 throw new RuntimeException( |
|
383 "MessageDigest does no support clone operation"); |
180 } |
384 } |
181 if (input.hasArray()) { |
385 } else { |
182 data.write(input.array(), |
386 md5Clone = JsseJce.getMessageDigest("MD5"); |
183 inPos + input.arrayOffset(), input.remaining()); |
387 md5Clone.update(md5.archived()); |
|
388 } |
|
389 |
|
390 return md5Clone; |
|
391 } |
|
392 |
|
393 private MessageDigest cloneSha() { |
|
394 MessageDigest shaClone; |
|
395 if (mdSHA instanceof Cloneable) { |
|
396 try { |
|
397 shaClone = (MessageDigest)mdSHA.clone(); |
|
398 } catch (CloneNotSupportedException ex) { // unlikely |
|
399 throw new RuntimeException( |
|
400 "MessageDigest does no support clone operation"); |
|
401 } |
|
402 } else { |
|
403 shaClone = JsseJce.getMessageDigest("SHA"); |
|
404 shaClone.update(sha.archived()); |
|
405 } |
|
406 |
|
407 return shaClone; |
|
408 } |
|
409 |
|
410 private static void updateDigest(MessageDigest md, |
|
411 byte[] pad1, byte[] pad2, SecretKey masterSecret) { |
|
412 byte[] keyBytes = "RAW".equals(masterSecret.getFormat()) |
|
413 ? masterSecret.getEncoded() : null; |
|
414 if (keyBytes != null) { |
|
415 md.update(keyBytes); |
|
416 } else { |
|
417 digestKey(md, masterSecret); |
|
418 } |
|
419 md.update(pad1); |
|
420 byte[] temp = md.digest(); |
|
421 |
|
422 if (keyBytes != null) { |
|
423 md.update(keyBytes); |
|
424 } else { |
|
425 digestKey(md, masterSecret); |
|
426 } |
|
427 md.update(pad2); |
|
428 md.update(temp); |
|
429 } |
|
430 |
|
431 private static void digestKey(MessageDigest md, SecretKey key) { |
|
432 try { |
|
433 if (md instanceof MessageDigestSpi2) { |
|
434 ((MessageDigestSpi2)md).engineUpdate(key); |
184 } else { |
435 } else { |
185 byte[] holder = new byte[input.remaining()]; |
436 throw new Exception( |
186 input.get(holder); |
437 "Digest does not support implUpdate(SecretKey)"); |
187 input.position(inPos); |
|
188 data.write(holder, 0, holder.length); |
|
189 } |
438 } |
190 break; |
439 } catch (Exception e) { |
191 } |
440 throw new RuntimeException( |
192 } |
441 "Could not obtain encoded key and " |
193 |
442 + "MessageDigest cannot digest key", e); |
194 void update(byte handshakeType, byte[] handshakeBody) { |
443 } |
195 |
444 } |
196 // reload if there are reserved messages. |
445 } |
197 reload(); |
446 |
198 |
447 // TLS 1.0 and TLS 1.1 |
199 switch (version) { |
448 static final class T10HandshakeHash implements TranscriptHash { |
200 case 1: |
449 private final TranscriptHash md5; |
201 md5.update(handshakeType); |
450 private final TranscriptHash sha; |
202 sha.update(handshakeType); |
451 private final ByteArrayOutputStream baos; |
203 |
452 |
204 md5.update((byte)((handshakeBody.length >> 16) & 0xFF)); |
453 T10HandshakeHash(CipherSuite cipherSuite) { |
205 sha.update((byte)((handshakeBody.length >> 16) & 0xFF)); |
454 MessageDigest mdMD5 = JsseJce.getMessageDigest("MD5"); |
206 md5.update((byte)((handshakeBody.length >> 8) & 0xFF)); |
455 MessageDigest mdSHA = JsseJce.getMessageDigest("SHA"); |
207 sha.update((byte)((handshakeBody.length >> 8) & 0xFF)); |
456 |
208 md5.update((byte)(handshakeBody.length & 0xFF)); |
457 boolean hasArchived = false; |
209 sha.update((byte)(handshakeBody.length & 0xFF)); |
458 if (mdMD5 instanceof Cloneable) { |
210 |
459 md5 = new CloneableHash(mdMD5); |
211 md5.update(handshakeBody); |
460 } else { |
212 sha.update(handshakeBody); |
461 hasArchived = true; |
213 break; |
462 md5 = new NonCloneableHash(mdMD5); |
214 default: |
463 } |
215 if (finMD != null) { |
464 if (mdSHA instanceof Cloneable) { |
216 finMD.update(handshakeType); |
465 sha = new CloneableHash(mdSHA); |
217 finMD.update((byte)((handshakeBody.length >> 16) & 0xFF)); |
466 } else { |
218 finMD.update((byte)((handshakeBody.length >> 8) & 0xFF)); |
467 hasArchived = true; |
219 finMD.update((byte)(handshakeBody.length & 0xFF)); |
468 sha = new NonCloneableHash(mdSHA); |
220 finMD.update(handshakeBody); |
469 } |
221 } |
470 |
222 data.write(handshakeType); |
471 if (hasArchived) { |
223 data.write((byte)((handshakeBody.length >> 16) & 0xFF)); |
472 this.baos = null; |
224 data.write((byte)((handshakeBody.length >> 8) & 0xFF)); |
473 } else { |
225 data.write((byte)(handshakeBody.length & 0xFF)); |
474 this.baos = new ByteArrayOutputStream(); |
226 data.write(handshakeBody, 0, handshakeBody.length); |
475 } |
227 break; |
476 } |
228 } |
477 |
229 } |
478 @Override |
230 |
479 public void update(byte[] input, int offset, int length) { |
231 void update(byte[] b, int offset, int len) { |
480 md5.update(input, offset, length); |
232 |
481 sha.update(input, offset, length); |
233 // reload if there are reserved messages. |
482 if (baos != null) { |
234 reload(); |
483 baos.write(input, offset, length); |
235 |
484 } |
236 switch (version) { |
485 } |
237 case 1: |
486 |
238 md5.update(b, offset, len); |
487 @Override |
239 sha.update(b, offset, len); |
488 public byte[] digest() { |
240 break; |
489 byte[] digest = new byte[36]; |
241 default: |
490 System.arraycopy(md5.digest(), 0, digest, 0, 16); |
242 if (finMD != null) { |
491 System.arraycopy(sha.digest(), 0, digest, 16, 20); |
243 finMD.update(b, offset, len); |
492 |
244 } |
493 return digest; |
245 data.write(b, offset, len); |
494 } |
246 break; |
495 |
247 } |
496 byte[] digest(String algorithm) { |
248 } |
497 if ("RSA".equalsIgnoreCase(algorithm)) { |
249 |
498 return digest(); |
250 /** |
499 } else { |
251 * Reset the remaining digests. Note this does *not* reset the number of |
500 return sha.digest(); |
252 * digest clones that can be obtained. Digests that have already been |
501 } |
253 * cloned and are gone remain gone. |
502 } |
254 */ |
503 |
255 void reset() { |
504 @Override |
256 if (version != -1) { |
505 public byte[] archived() { |
257 throw new RuntimeException( |
506 if (baos != null) { |
258 "reset() can be only be called before protocolDetermined"); |
507 return baos.toByteArray(); |
259 } |
508 } else if (md5 instanceof NonCloneableHash) { |
260 data.reset(); |
509 return md5.archived(); |
261 } |
510 } else { |
262 |
511 return sha.archived(); |
263 |
512 } |
264 void protocolDetermined(ProtocolVersion pv) { |
513 } |
265 |
514 } |
266 // Do not set again, will ignore |
515 |
267 if (version != -1) { |
516 static final class T12HandshakeHash implements TranscriptHash { |
268 return; |
517 private final TranscriptHash transcriptHash; |
269 } |
518 private final ByteArrayOutputStream baos; |
270 |
519 |
271 if (pv.maybeDTLSProtocol()) { |
520 T12HandshakeHash(CipherSuite cipherSuite) { |
272 version = pv.compareTo(ProtocolVersion.DTLS12) >= 0 ? 2 : 1; |
521 MessageDigest md = |
273 } else { |
522 JsseJce.getMessageDigest(cipherSuite.hashAlg.name); |
274 version = pv.compareTo(ProtocolVersion.TLS12) >= 0 ? 2 : 1; |
523 if (md instanceof Cloneable) { |
275 } |
524 transcriptHash = new CloneableHash(md); |
276 switch (version) { |
525 this.baos = null; |
277 case 1: |
526 } else { |
278 // initiate md5, sha and call update on saved array |
527 transcriptHash = new NonCloneableHash(md); |
279 try { |
528 this.baos = new ByteArrayOutputStream(); |
280 md5 = CloneableDigest.getDigest("MD5", clonesNeeded); |
529 } |
281 sha = CloneableDigest.getDigest("SHA", clonesNeeded); |
530 } |
282 } catch (NoSuchAlgorithmException e) { |
531 |
283 throw new RuntimeException |
532 @Override |
284 ("Algorithm MD5 or SHA not available", e); |
533 public void update(byte[] input, int offset, int length) { |
285 } |
534 transcriptHash.update(input, offset, length); |
286 byte[] bytes = data.toByteArray(); |
535 if (baos != null) { |
287 update(bytes, 0, bytes.length); |
536 baos.write(input, offset, length); |
288 break; |
537 } |
289 case 2: |
538 } |
290 break; |
539 |
291 } |
540 @Override |
292 } |
541 public byte[] digest() { |
293 |
542 return transcriptHash.digest(); |
294 ///////////////////////////////////////////////////////////// |
543 } |
295 // Below are old methods for pre-TLS 1.1 |
544 |
296 ///////////////////////////////////////////////////////////// |
545 @Override |
297 |
546 public byte[] archived() { |
298 /** |
547 if (baos != null) { |
299 * Return a new MD5 digest updated with all data hashed so far. |
548 return baos.toByteArray(); |
300 */ |
549 } else { |
301 MessageDigest getMD5Clone() { |
550 return transcriptHash.archived(); |
302 if (version != 1) { |
551 } |
303 throw new RuntimeException( |
552 } |
304 "getMD5Clone() can be only be called for TLS 1.1"); |
553 } |
305 } |
554 |
306 return cloneDigest(md5); |
555 static final class T13HandshakeHash implements TranscriptHash { |
307 } |
556 private final TranscriptHash transcriptHash; |
308 |
557 private final ByteArrayOutputStream baos; |
309 /** |
558 |
310 * Return a new SHA digest updated with all data hashed so far. |
559 T13HandshakeHash(CipherSuite cipherSuite) { |
311 */ |
560 MessageDigest md = |
312 MessageDigest getSHAClone() { |
561 JsseJce.getMessageDigest(cipherSuite.hashAlg.name); |
313 if (version != 1) { |
562 if (md instanceof Cloneable) { |
314 throw new RuntimeException( |
563 transcriptHash = new CloneableHash(md); |
315 "getSHAClone() can be only be called for TLS 1.1"); |
564 this.baos = null; |
316 } |
565 } else { |
317 return cloneDigest(sha); |
566 transcriptHash = new NonCloneableHash(md); |
318 } |
567 this.baos = new ByteArrayOutputStream(); |
319 |
568 } |
320 private static MessageDigest cloneDigest(MessageDigest digest) { |
569 } |
321 try { |
570 |
322 return (MessageDigest)digest.clone(); |
571 @Override |
323 } catch (CloneNotSupportedException e) { |
572 public void update(byte[] input, int offset, int length) { |
324 // cannot occur for digests generated via CloneableDigest |
573 transcriptHash.update(input, offset, length); |
325 throw new RuntimeException("Could not clone digest", e); |
574 if (baos != null) { |
326 } |
575 baos.write(input, offset, length); |
327 } |
576 } |
328 |
577 } |
329 ///////////////////////////////////////////////////////////// |
578 |
330 // Below are new methods for TLS 1.2 |
579 @Override |
331 ///////////////////////////////////////////////////////////// |
580 public byte[] digest() { |
332 |
581 return transcriptHash.digest(); |
333 private static String normalizeAlgName(String alg) { |
582 } |
334 alg = alg.toUpperCase(Locale.US); |
583 |
335 if (alg.startsWith("SHA")) { |
584 @Override |
336 if (alg.length() == 3) { |
585 public byte[] archived() { |
337 return "SHA-1"; |
586 if (baos != null) { |
338 } |
587 return baos.toByteArray(); |
339 if (alg.charAt(3) != '-') { |
588 } else { |
340 return "SHA-" + alg.substring(3); |
589 return transcriptHash.archived(); |
341 } |
590 } |
342 } |
591 |
343 return alg; |
592 // throw new UnsupportedOperationException("Not supported yet."); |
344 } |
593 } |
345 /** |
594 } |
346 * Specifies the hash algorithm used in Finished. This should be called |
595 |
347 * based in info in ServerHello. |
596 static final class CloneableHash implements TranscriptHash { |
348 * Can be called multiple times. |
597 private final MessageDigest md; |
349 */ |
598 |
350 void setFinishedAlg(String s) { |
599 CloneableHash(MessageDigest md) { |
351 if (s == null) { |
600 this.md = md; |
352 throw new RuntimeException( |
601 } |
353 "setFinishedAlg's argument cannot be null"); |
602 |
354 } |
603 @Override |
355 |
604 public void update(byte[] input, int offset, int length) { |
356 // Can be called multiple times, but only set once |
605 md.update(input, offset, length); |
357 if (finMD != null) return; |
606 } |
358 |
607 |
359 try { |
608 @Override |
360 // See comment in the contructor. |
609 public byte[] digest() { |
361 finMD = CloneableDigest.getDigest(normalizeAlgName(s), 4); |
610 try { |
362 } catch (NoSuchAlgorithmException e) { |
611 return ((MessageDigest)md.clone()).digest(); |
363 throw new Error(e); |
612 } catch (CloneNotSupportedException ex) { |
364 } |
613 // unlikely |
365 finMD.update(data.toByteArray()); |
614 return new byte[0]; |
366 } |
615 } |
367 |
616 } |
368 byte[] getAllHandshakeMessages() { |
617 |
369 return data.toByteArray(); |
618 @Override |
370 } |
619 public byte[] archived() { |
371 |
620 throw new UnsupportedOperationException("Not supported yet."); |
372 /** |
621 } |
373 * Calculates the hash in Finished. Must be called after setFinishedAlg(). |
622 } |
374 * This method can be called twice, for Finished messages of the server |
623 |
375 * side and client side respectively. |
624 static final class NonCloneableHash implements TranscriptHash { |
376 */ |
625 private final MessageDigest md; |
377 byte[] getFinishedHash() { |
626 private final ByteArrayOutputStream baos = new ByteArrayOutputStream(); |
378 try { |
627 |
379 return cloneDigest(finMD).digest(); |
628 NonCloneableHash(MessageDigest md) { |
380 } catch (Exception e) { |
629 this.md = md; |
381 throw new Error("Error during hash calculation", e); |
630 } |
|
631 |
|
632 @Override |
|
633 public void update(byte[] input, int offset, int length) { |
|
634 baos.write(input, offset, length); |
|
635 } |
|
636 |
|
637 @Override |
|
638 public byte[] digest() { |
|
639 byte[] bytes = baos.toByteArray(); |
|
640 md.reset(); |
|
641 return md.digest(bytes); |
|
642 } |
|
643 |
|
644 @Override |
|
645 public byte[] archived() { |
|
646 return baos.toByteArray(); |
382 } |
647 } |
383 } |
648 } |
384 } |
649 } |
385 |
|
386 /** |
|
387 * A wrapper for MessageDigests that simulates cloning of non-cloneable |
|
388 * digests. It uses the standard MessageDigest API and therefore can be used |
|
389 * transparently in place of a regular digest. |
|
390 * |
|
391 * Note that we extend the MessageDigest class directly rather than |
|
392 * MessageDigestSpi. This works because MessageDigest was originally designed |
|
393 * this way in the JDK 1.1 days which allows us to avoid creating an internal |
|
394 * provider. |
|
395 * |
|
396 * It can be "cloned" a limited number of times, which is specified at |
|
397 * construction time. This is achieved by internally maintaining n digests |
|
398 * in parallel. Consequently, it is only 1/n-th times as fast as the original |
|
399 * digest. |
|
400 * |
|
401 * Example: |
|
402 * MessageDigest md = CloneableDigest.getDigest("SHA", 2); |
|
403 * md.update(data1); |
|
404 * MessageDigest md2 = (MessageDigest)md.clone(); |
|
405 * md2.update(data2); |
|
406 * byte[] d1 = md2.digest(); // digest of data1 || data2 |
|
407 * md.update(data3); |
|
408 * byte[] d2 = md.digest(); // digest of data1 || data3 |
|
409 * |
|
410 * This class is not thread safe. |
|
411 * |
|
412 */ |
|
413 final class CloneableDigest extends MessageDigest implements Cloneable { |
|
414 |
|
415 /** |
|
416 * The individual MessageDigests. Initially, all elements are non-null. |
|
417 * When clone() is called, the non-null element with the maximum index is |
|
418 * returned and the array element set to null. |
|
419 * |
|
420 * All non-null element are always in the same state. |
|
421 */ |
|
422 private final MessageDigest[] digests; |
|
423 |
|
424 private CloneableDigest(MessageDigest digest, int n, String algorithm) |
|
425 throws NoSuchAlgorithmException { |
|
426 super(algorithm); |
|
427 digests = new MessageDigest[n]; |
|
428 digests[0] = digest; |
|
429 for (int i = 1; i < n; i++) { |
|
430 digests[i] = JsseJce.getMessageDigest(algorithm); |
|
431 } |
|
432 } |
|
433 |
|
434 /** |
|
435 * Return a MessageDigest for the given algorithm that can be cloned the |
|
436 * specified number of times. If the default implementation supports |
|
437 * cloning, it is returned. Otherwise, an instance of this class is |
|
438 * returned. |
|
439 */ |
|
440 static MessageDigest getDigest(String algorithm, int n) |
|
441 throws NoSuchAlgorithmException { |
|
442 MessageDigest digest = JsseJce.getMessageDigest(algorithm); |
|
443 try { |
|
444 digest.clone(); |
|
445 // already cloneable, use it |
|
446 return digest; |
|
447 } catch (CloneNotSupportedException e) { |
|
448 return new CloneableDigest(digest, n, algorithm); |
|
449 } |
|
450 } |
|
451 |
|
452 /** |
|
453 * Check if this object is still usable. If it has already been cloned the |
|
454 * maximum number of times, there are no digests left and this object can no |
|
455 * longer be used. |
|
456 */ |
|
457 private void checkState() { |
|
458 // XXX handshaking currently doesn't stop updating hashes... |
|
459 // if (digests[0] == null) { |
|
460 // throw new IllegalStateException("no digests left"); |
|
461 // } |
|
462 } |
|
463 |
|
464 @Override |
|
465 protected int engineGetDigestLength() { |
|
466 checkState(); |
|
467 return digests[0].getDigestLength(); |
|
468 } |
|
469 |
|
470 @Override |
|
471 protected void engineUpdate(byte b) { |
|
472 checkState(); |
|
473 for (int i = 0; (i < digests.length) && (digests[i] != null); i++) { |
|
474 digests[i].update(b); |
|
475 } |
|
476 } |
|
477 |
|
478 @Override |
|
479 protected void engineUpdate(byte[] b, int offset, int len) { |
|
480 checkState(); |
|
481 for (int i = 0; (i < digests.length) && (digests[i] != null); i++) { |
|
482 digests[i].update(b, offset, len); |
|
483 } |
|
484 } |
|
485 |
|
486 @Override |
|
487 protected byte[] engineDigest() { |
|
488 checkState(); |
|
489 byte[] digest = digests[0].digest(); |
|
490 digestReset(); |
|
491 return digest; |
|
492 } |
|
493 |
|
494 @Override |
|
495 protected int engineDigest(byte[] buf, int offset, int len) |
|
496 throws DigestException { |
|
497 checkState(); |
|
498 int n = digests[0].digest(buf, offset, len); |
|
499 digestReset(); |
|
500 return n; |
|
501 } |
|
502 |
|
503 /** |
|
504 * Reset all digests after a digest() call. digests[0] has already been |
|
505 * implicitly reset by the digest() call and does not need to be reset |
|
506 * again. |
|
507 */ |
|
508 private void digestReset() { |
|
509 for (int i = 1; (i < digests.length) && (digests[i] != null); i++) { |
|
510 digests[i].reset(); |
|
511 } |
|
512 } |
|
513 |
|
514 @Override |
|
515 protected void engineReset() { |
|
516 checkState(); |
|
517 for (int i = 0; (i < digests.length) && (digests[i] != null); i++) { |
|
518 digests[i].reset(); |
|
519 } |
|
520 } |
|
521 |
|
522 @Override |
|
523 public Object clone() { |
|
524 checkState(); |
|
525 for (int i = digests.length - 1; i >= 0; i--) { |
|
526 if (digests[i] != null) { |
|
527 MessageDigest digest = digests[i]; |
|
528 digests[i] = null; |
|
529 return digest; |
|
530 } |
|
531 } |
|
532 // cannot occur |
|
533 throw new InternalError(); |
|
534 } |
|
535 |
|
536 } |
|