1 /*
   2  * Copyright (c) 2003, 2017, 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.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 import java.io.ByteArrayInputStream;
  25 import java.io.ByteArrayOutputStream;
  26 import java.io.PrintStream;
  27 import java.security.DigestInputStream;
  28 import java.security.DigestOutputStream;
  29 import java.security.MessageDigest;
  30 import java.security.NoSuchAlgorithmException;
  31 import java.security.Security;
  32 import java.util.Arrays;
  33 import java.util.Random;
  34 import jdk.test.lib.RandomFactory;
  35 import static java.lang.System.out;
  36 
  37 /**
  38  * @test
  39  * @bug 8050370 8156059
  40  * @summary MessageDigest tests with DigestIOStream
  41  * @author Kevin Liu
  42  * @key randomness
  43  * @library /test/lib
  44  * @run main/timeout=180 TestDigestIOStream
  45  */
  46 
  47 enum ReadModel {
  48     READ, BUFFER_READ, MIX_READ
  49 }
  50 
  51 public class TestDigestIOStream {
  52 
  53     private static final int[] DATA_LEN_ARRAY = { 1, 50, 2500, 125000,
  54             6250000 };
  55     private static final String[] ALGORITHM_ARRAY = { "MD2", "MD5", "SHA1",
  56             "SHA-224", "SHA-256", "SHA-384", "SHA-512", "SHA3-224", "SHA3-256",
  57             "SHA3-384", "SHA3-512" };
  58 
  59     private static byte[] data;
  60 
  61     private static MessageDigest md = null;
  62 
  63     public static void main(String argv[]) throws Exception {
  64         TestDigestIOStream test = new TestDigestIOStream();
  65         test.run();
  66     }
  67 
  68     public void run() throws Exception {
  69         for (String algorithm : ALGORITHM_ARRAY) {
  70             try {
  71                 md = MessageDigest.getInstance(algorithm);
  72                 for (int length : DATA_LEN_ARRAY) {
  73 
  74                     Random rdm = RandomFactory.getRandom();
  75                     data = new byte[length];
  76                     rdm.nextBytes(data);
  77 
  78                     if (!testMDChange(algorithm, length)) {
  79                         throw new RuntimeException("testMDChange failed at:"
  80                                 + algorithm + "/" + length);
  81                     }
  82                     if (!testMDShare(algorithm, length)) {
  83                         throw new RuntimeException("testMDShare failed at:"
  84                                 + algorithm + "/" + length);
  85                     }
  86                     for (ReadModel readModel : ReadModel.values()) {
  87                         // test Digest function when digest switch on
  88                         if (!testDigestOnOff(algorithm, readModel, true,
  89                                 length)) {
  90                             throw new RuntimeException(
  91                                     "testDigestOn failed at:" + algorithm + "/"
  92                                             + length + "/" + readModel);
  93                         }
  94                         // test Digest function when digest switch off
  95                         if (!testDigestOnOff(algorithm, readModel, false,
  96                                 length)) {
  97                             throw new RuntimeException(
  98                                     "testDigestOff failed at:" + algorithm + "/"
  99                                             + length + "/" + readModel);
 100                         }
 101                     }
 102                 }
 103             } catch (NoSuchAlgorithmException nae) {
 104                 if (algorithm.startsWith("SHA3") && !isSHA3supported()) {
 105                     continue;
 106                 } else {
 107                     throw nae;
 108                 }
 109             }
 110         }
 111         int testNumber = ALGORITHM_ARRAY.length * ReadModel.values().length
 112                 * DATA_LEN_ARRAY.length * 2
 113                 + ALGORITHM_ARRAY.length * DATA_LEN_ARRAY.length * 2;
 114         out.println("All " + testNumber + " Tests Passed");
 115     }
 116 
 117     // SHA-3 hash algorithms are only supported by "SUN" provider
 118     // and "OracleUcrypto" provider on Solaris 12.0 or later
 119     // This method checks if system supports SHA-3
 120     private boolean isSHA3supported() {
 121         if (Security.getProvider("SUN") != null) {
 122             return true;
 123         }
 124         if (Security.getProvider("OracleUcrypto") != null
 125                 && "SunOS".equals(System.getProperty("os.name"))
 126                 && System.getProperty("os.version").compareTo("5.12") >= 0) {
 127             return true;
 128         }
 129         return false;
 130     }
 131 
 132     /**
 133      * Test DigestInputStream and DigestOutputStream digest function when digest
 134      * set on and off
 135      *
 136      * @param algo
 137      *            Message Digest algorithm
 138      * @param readModel
 139      *            which read method used(READ, BUFFER_READ, MIX_READ)
 140      * @param on
 141      *            digest switch(on and off)
 142      * @param dataLength
 143      *            plain test data length.
 144      * @exception Exception
 145      *                throw unexpected exception
 146      */
 147     public boolean testDigestOnOff(String algo, ReadModel readModel, boolean on,
 148             int dataLength) throws Exception {
 149 
 150         // Generate the DigestInputStream/DigestOutputStream object
 151         try (ByteArrayInputStream bais = new ByteArrayInputStream(data);
 152                 DigestInputStream dis = new DigestInputStream(bais,
 153                         MessageDigest.getInstance(algo));
 154                 ByteArrayOutputStream baos = new ByteArrayOutputStream();
 155                 DigestOutputStream dos = new DigestOutputStream(baos,
 156                         MessageDigest.getInstance(algo));
 157                 ByteArrayOutputStream baOut = new ByteArrayOutputStream();) {
 158 
 159             // Perform the update using all available/possible update methods
 160             int k = 0;
 161             byte[] buffer = new byte[5];
 162             boolean enDigest = true;
 163             // Make sure the digest function is on (default)
 164             dis.on(enDigest);
 165             dos.on(enDigest);
 166 
 167             switch (readModel) {
 168             case READ: // use only read()
 169                 while ((k = dis.read()) != -1) {
 170                     if (on) {
 171                         dos.write(k);
 172                     } else {
 173                         dos.write(k);
 174                         if (enDigest) {
 175                             baOut.write(k);
 176                         }
 177                         enDigest = !enDigest;
 178                         dos.on(enDigest);
 179                         dis.on(enDigest);
 180                     }
 181                 }
 182                 break;
 183             case BUFFER_READ: // use only read(byte[], int, int)
 184                 while ((k = dis.read(buffer, 0, buffer.length)) != -1) {
 185                     if (on) {
 186                         dos.write(buffer, 0, k);
 187                     } else {
 188                         dos.write(buffer, 0, k);
 189                         if (enDigest) {
 190                             baOut.write(buffer, 0, k);
 191                         }
 192                         enDigest = !enDigest;
 193                         dis.on(enDigest);
 194                         dos.on(enDigest);
 195                     }
 196                 }
 197                 break;
 198             case MIX_READ: // use both read() and read(byte[], int, int)
 199                 while ((k = dis.read()) != -1) {
 200                     if (on) {
 201                         dos.write(k);
 202                         if ((k = dis.read(buffer, 0, buffer.length)) != -1) {
 203                             dos.write(buffer, 0, k);
 204                         }
 205                     } else {
 206                         dos.write(k);
 207                         if (enDigest) {
 208                             baOut.write(k);
 209                         }
 210                         enDigest = !enDigest;
 211                         dis.on(enDigest);
 212                         dos.on(enDigest);
 213                         if ((k = dis.read(buffer, 0, buffer.length)) != -1) {
 214                             dos.write(buffer, 0, k);
 215                             if (enDigest) {
 216                                 baOut.write(buffer, 0, k);
 217                             }
 218                             enDigest = !enDigest;
 219                             dis.on(enDigest);
 220                             dos.on(enDigest);
 221                         }
 222                     }
 223                 }
 224                 break;
 225             default:
 226                 out.println("ERROR: Invalid read/write combination choice!");
 227                 return false;
 228             }
 229 
 230             // Get the output and the "correct" digest values
 231             byte[] output1 = dis.getMessageDigest().digest();
 232             byte[] output2 = dos.getMessageDigest().digest();
 233             byte[] standard;
 234             if (on) {
 235                 standard = md.digest(data);
 236             } else {
 237                 byte[] dataDigested = baOut.toByteArray();
 238                 standard = md.digest(dataDigested);
 239             }
 240 
 241             // Compare the output byte array value to the input data
 242             if (!MessageDigest.isEqual(data, baos.toByteArray())) {
 243                 out.println("ERROR of " + readModel
 244                         + ": output and input data unexpectedly changed");
 245                 return false;
 246             }
 247             // Compare generated digest values
 248             if (!MessageDigest.isEqual(output1, standard)
 249                     || !MessageDigest.isEqual(output2, standard)) {
 250                 out.println("ERROR" + readModel
 251                         + ": generated digest data unexpectedly changed");
 252                 return false;
 253             }
 254 
 255             return true;
 256         } catch (Exception ex) {
 257             out.println("testDigestOnOff failed at:" + algo + "/" + readModel
 258                     + "/" + dataLength + " with unexpected exception");
 259             throw ex;
 260         }
 261     }
 262 
 263     /**
 264      * Test DigestInputStream and DigestOutputStream digest function when Swap
 265      * the message digest engines between DigestIn/OutputStream
 266      *
 267      * @param algo
 268      *            Message Digest algorithm
 269      * @param dataLength
 270      *            plain test data length.
 271      * @exception Exception
 272      *                throw unexpected exception
 273      */
 274     public boolean testMDChange(String algo, int dataLength) throws Exception {
 275         // Generate the DigestInputStream/DigestOutputStream object
 276         MessageDigest mdIn = MessageDigest.getInstance(algo);
 277         MessageDigest mdOut = MessageDigest.getInstance(algo);
 278         try (ByteArrayInputStream bais = new ByteArrayInputStream(data);
 279                 DigestInputStream dis = new DigestInputStream(bais, mdIn);
 280                 ByteArrayOutputStream baos = new ByteArrayOutputStream();
 281                 DigestOutputStream dos = new DigestOutputStream(baos, mdOut);) {
 282 
 283             // Perform the update using all available/possible update methods
 284             int k = 0;
 285             byte[] buffer = new byte[10];
 286 
 287             // use both read() and read(byte[], int, int)
 288             while ((k = dis.read()) != -1) {
 289                 dos.write(k);
 290                 if ((k = dis.read(buffer, 0, buffer.length)) != -1) {
 291                     dos.write(buffer, 0, k);
 292                 }
 293 
 294                 // Swap the message digest engines between
 295                 // DigestIn/OutputStream objects
 296                 dis.setMessageDigest(mdOut);
 297                 dos.setMessageDigest(mdIn);
 298                 mdIn = dis.getMessageDigest();
 299                 mdOut = dos.getMessageDigest();
 300             }
 301 
 302             // Get the output and the "correct" digest values
 303             byte[] output1 = mdIn.digest();
 304             byte[] output2 = mdOut.digest();
 305             byte[] standard = md.digest(data);
 306 
 307             // Compare generated digest values
 308             return MessageDigest.isEqual(output1, standard)
 309                     && MessageDigest.isEqual(output2, standard);
 310         } catch (Exception ex) {
 311             out.println("testMDChange failed at:" + algo + "/" + dataLength
 312                     + " with unexpected exception");
 313             throw ex;
 314         }
 315     }
 316 
 317     /**
 318      * Test DigestInputStream and DigestOutputStream digest function when use
 319      * same message digest object.
 320      *
 321      * @param algo
 322      *            Message Digest algorithm
 323      * @param dataLength
 324      *            plain test data length.
 325      * @exception Exception
 326      *                throw unexpected exception
 327      */
 328     public boolean testMDShare(String algo, int dataLength) throws Exception {
 329         MessageDigest mdCommon = MessageDigest.getInstance(algo);
 330         // Generate the DigestInputStream/DigestOutputStream object
 331         try (ByteArrayInputStream bais = new ByteArrayInputStream(data);
 332                 DigestInputStream dis = new DigestInputStream(bais, mdCommon);
 333                 ByteArrayOutputStream baos = new ByteArrayOutputStream();
 334                 DigestOutputStream dos = new DigestOutputStream(baos,
 335                         mdCommon);) {
 336 
 337             // Perform the update using all available/possible update methods
 338             int k = 0;
 339             byte[] buffer = new byte[10];
 340 
 341             // use both read() and read(byte[], int, int)
 342             while (k < data.length) {
 343                 int len = dis.read(buffer, 0, buffer.length);
 344                 if (len != -1) {
 345                     k += len;
 346                     if (k < data.length) {
 347                         dos.write(data[k]);
 348                         k++;
 349                         dis.skip(1);
 350                     }
 351                 }
 352             }
 353 
 354             // Get the output and the "correct" digest values
 355             byte[] output = mdCommon.digest();
 356             byte[] standard = md.digest(data);
 357 
 358             // Compare generated digest values
 359             return MessageDigest.isEqual(output, standard);
 360         } catch (Exception ex) {
 361             out.println("TestMDShare failed at:" + algo + "/" + dataLength
 362                     + " with unexpected exception");
 363             throw ex;
 364         }
 365     }
 366 }