1 /* 2 * Copyright (c) 2003, 2018, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 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 23 * questions. 24 */ 25 26 package sun.security.ssl; 27 28 import java.io.ByteArrayOutputStream; 29 import java.io.IOException; 30 import java.nio.ByteBuffer; 31 import java.security.MessageDigest; 32 import java.util.Arrays; 33 import java.util.LinkedList; 34 import javax.crypto.SecretKey; 35 import sun.security.util.MessageDigestSpi2; 36 37 final class HandshakeHash { 38 private TranscriptHash transcriptHash; 39 private LinkedList<byte[]> reserves; // one handshake message per entry 40 private boolean hasBeenUsed; 41 42 HandshakeHash() { 43 this.transcriptHash = new CacheOnlyHash(); 44 this.reserves = new LinkedList<>(); 45 this.hasBeenUsed = false; 46 } 47 48 // fix the negotiated protocol version and cipher suite 49 void determine(ProtocolVersion protocolVersion, 50 CipherSuite cipherSuite) { 51 if (!(transcriptHash instanceof CacheOnlyHash)) { 52 throw new IllegalStateException( 53 "Not expected instance of transcript hash"); 54 } 55 56 CacheOnlyHash coh = (CacheOnlyHash)transcriptHash; 57 if (protocolVersion.useTLS13PlusSpec()) { 58 transcriptHash = new T13HandshakeHash(cipherSuite); 59 } else if (protocolVersion.useTLS12PlusSpec()) { 60 transcriptHash = new T12HandshakeHash(cipherSuite); 61 } else if (protocolVersion.useTLS10PlusSpec()) { 62 transcriptHash = new T10HandshakeHash(cipherSuite); 63 } else { 64 transcriptHash = new S30HandshakeHash(cipherSuite); 65 } 66 67 byte[] reserved = coh.baos.toByteArray(); 68 if (reserved.length != 0) { 69 transcriptHash.update(reserved, 0, reserved.length); 70 } 71 } 72 73 HandshakeHash copy() { 74 if (transcriptHash instanceof CacheOnlyHash) { 75 HandshakeHash result = new HandshakeHash(); 76 result.transcriptHash = ((CacheOnlyHash)transcriptHash).copy(); 77 result.reserves = new LinkedList<>(reserves); 78 result.hasBeenUsed = hasBeenUsed; 79 return result; 80 } else { 81 throw new IllegalStateException("Hash does not support copying"); 82 } 83 } 84 85 void receive(byte[] input) { 86 reserves.add(Arrays.copyOf(input, input.length)); 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) { 94 if (input.hasArray()) { 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(), 134 input.position() + input.arrayOffset(), input.remaining()); 135 } else { 136 int inPos = input.position(); 137 byte[] holder = new byte[input.remaining()]; 138 input.get(holder); 139 input.position(inPos); 140 transcriptHash.update(holder, 0, holder.length); 141 } 142 } 143 144 // Use one handshake message if it has not been used. 145 void utilize() { 146 if (hasBeenUsed) { 147 return; 148 } 149 if (reserves.size() != 0) { 150 byte[] holder = reserves.remove(); 151 transcriptHash.update(holder, 0, holder.length); 152 hasBeenUsed = true; 153 } 154 } 155 156 // Consume one handshake message if it has not been consumed. 157 void consume() { 158 if (hasBeenUsed) { 159 hasBeenUsed = false; 160 return; 161 } 162 if (reserves.size() != 0) { 163 byte[] holder = reserves.remove(); 164 transcriptHash.update(holder, 0, holder.length); 165 } 166 } 167 168 void update() { 169 while (reserves.size() != 0) { 170 byte[] holder = reserves.remove(); 171 transcriptHash.update(holder, 0, holder.length); 172 } 173 hasBeenUsed = false; 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"); 384 } 385 } else { 386 md5Clone = JsseJce.getMessageDigest("MD5"); 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); 435 } else { 436 throw new Exception( 437 "Digest does not support implUpdate(SecretKey)"); 438 } 439 } catch (Exception e) { 440 throw new RuntimeException( 441 "Could not obtain encoded key and " 442 + "MessageDigest cannot digest key", e); 443 } 444 } 445 } 446 447 // TLS 1.0 and TLS 1.1 448 static final class T10HandshakeHash implements TranscriptHash { 449 private final TranscriptHash md5; 450 private final TranscriptHash sha; 451 private final ByteArrayOutputStream baos; 452 453 T10HandshakeHash(CipherSuite cipherSuite) { 454 MessageDigest mdMD5 = JsseJce.getMessageDigest("MD5"); 455 MessageDigest mdSHA = JsseJce.getMessageDigest("SHA"); 456 457 boolean hasArchived = false; 458 if (mdMD5 instanceof Cloneable) { 459 md5 = new CloneableHash(mdMD5); 460 } else { 461 hasArchived = true; 462 md5 = new NonCloneableHash(mdMD5); 463 } 464 if (mdSHA instanceof Cloneable) { 465 sha = new CloneableHash(mdSHA); 466 } else { 467 hasArchived = true; 468 sha = new NonCloneableHash(mdSHA); 469 } 470 471 if (hasArchived) { 472 this.baos = null; 473 } else { 474 this.baos = new ByteArrayOutputStream(); 475 } 476 } 477 478 @Override 479 public void update(byte[] input, int offset, int length) { 480 md5.update(input, offset, length); 481 sha.update(input, offset, length); 482 if (baos != null) { 483 baos.write(input, offset, length); 484 } 485 } 486 487 @Override 488 public byte[] digest() { 489 byte[] digest = new byte[36]; 490 System.arraycopy(md5.digest(), 0, digest, 0, 16); 491 System.arraycopy(sha.digest(), 0, digest, 16, 20); 492 493 return digest; 494 } 495 496 byte[] digest(String algorithm) { 497 if ("RSA".equalsIgnoreCase(algorithm)) { 498 return digest(); 499 } else { 500 return sha.digest(); 501 } 502 } 503 504 @Override 505 public byte[] archived() { 506 if (baos != null) { 507 return baos.toByteArray(); 508 } else if (md5 instanceof NonCloneableHash) { 509 return md5.archived(); 510 } else { 511 return sha.archived(); 512 } 513 } 514 } 515 516 static final class T12HandshakeHash implements TranscriptHash { 517 private final TranscriptHash transcriptHash; 518 private final ByteArrayOutputStream baos; 519 520 T12HandshakeHash(CipherSuite cipherSuite) { 521 MessageDigest md = 522 JsseJce.getMessageDigest(cipherSuite.hashAlg.name); 523 if (md instanceof Cloneable) { 524 transcriptHash = new CloneableHash(md); 525 this.baos = null; 526 } else { 527 transcriptHash = new NonCloneableHash(md); 528 this.baos = new ByteArrayOutputStream(); 529 } 530 } 531 532 @Override 533 public void update(byte[] input, int offset, int length) { 534 transcriptHash.update(input, offset, length); 535 if (baos != null) { 536 baos.write(input, offset, length); 537 } 538 } 539 540 @Override 541 public byte[] digest() { 542 return transcriptHash.digest(); 543 } 544 545 @Override 546 public byte[] archived() { 547 if (baos != null) { 548 return baos.toByteArray(); 549 } else { 550 return transcriptHash.archived(); 551 } 552 } 553 } 554 555 static final class T13HandshakeHash implements TranscriptHash { 556 private final TranscriptHash transcriptHash; 557 private final ByteArrayOutputStream baos; 558 559 T13HandshakeHash(CipherSuite cipherSuite) { 560 MessageDigest md = 561 JsseJce.getMessageDigest(cipherSuite.hashAlg.name); 562 if (md instanceof Cloneable) { 563 transcriptHash = new CloneableHash(md); 564 this.baos = null; 565 } else { 566 transcriptHash = new NonCloneableHash(md); 567 this.baos = new ByteArrayOutputStream(); 568 } 569 } 570 571 @Override 572 public void update(byte[] input, int offset, int length) { 573 transcriptHash.update(input, offset, length); 574 if (baos != null) { 575 baos.write(input, offset, length); 576 } 577 } 578 579 @Override 580 public byte[] digest() { 581 return transcriptHash.digest(); 582 } 583 584 @Override 585 public byte[] archived() { 586 if (baos != null) { 587 return baos.toByteArray(); 588 } else { 589 return transcriptHash.archived(); 590 } 591 592 // throw new UnsupportedOperationException("Not supported yet."); 593 } 594 } 595 596 static final class CloneableHash implements TranscriptHash { 597 private final MessageDigest md; 598 599 CloneableHash(MessageDigest md) { 600 this.md = md; 601 } 602 603 @Override 604 public void update(byte[] input, int offset, int length) { 605 md.update(input, offset, length); 606 } 607 608 @Override 609 public byte[] digest() { 610 try { 611 return ((MessageDigest)md.clone()).digest(); 612 } catch (CloneNotSupportedException ex) { 613 // unlikely 614 return new byte[0]; 615 } 616 } 617 618 @Override 619 public byte[] archived() { 620 throw new UnsupportedOperationException("Not supported yet."); 621 } 622 } 623 624 static final class NonCloneableHash implements TranscriptHash { 625 private final MessageDigest md; 626 private final ByteArrayOutputStream baos = new ByteArrayOutputStream(); 627 628 NonCloneableHash(MessageDigest md) { 629 this.md = md; 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(); 647 } 648 } 649 }