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 }