1 /*
   2  * Copyright (c) 2013, 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.
   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 
  25 package org.graalvm.compiler.hotspot.test;
  26 
  27 import java.io.ByteArrayOutputStream;
  28 import java.io.DataInputStream;
  29 import java.io.IOException;
  30 import java.io.InputStream;
  31 import java.security.AlgorithmParameters;
  32 import java.security.SecureRandom;
  33 
  34 import javax.crypto.Cipher;
  35 import javax.crypto.KeyGenerator;
  36 import javax.crypto.SecretKey;
  37 
  38 import org.graalvm.compiler.code.CompilationResult;
  39 import org.graalvm.compiler.debug.DebugContext;
  40 import org.graalvm.compiler.hotspot.meta.HotSpotGraphBuilderPlugins;
  41 import org.junit.Assert;
  42 import org.junit.Test;
  43 
  44 import jdk.vm.ci.code.InstalledCode;
  45 import jdk.vm.ci.meta.ResolvedJavaMethod;
  46 
  47 /**
  48  * Tests the intrinsification of certain crypto methods.
  49  */
  50 public class HotSpotCryptoSubstitutionTest extends HotSpotGraalCompilerTest {
  51 
  52     @Override
  53     protected InstalledCode addMethod(DebugContext debug, ResolvedJavaMethod method, CompilationResult compResult) {
  54         return getBackend().createDefaultInstalledCode(debug, method, compResult);
  55     }
  56 
  57     SecretKey aesKey;
  58     SecretKey desKey;
  59     byte[] input;
  60     ByteArrayOutputStream aesExpected = new ByteArrayOutputStream();
  61     ByteArrayOutputStream desExpected = new ByteArrayOutputStream();
  62 
  63     public HotSpotCryptoSubstitutionTest() throws Exception {
  64         byte[] seed = {0x4, 0x7, 0x1, 0x1};
  65         SecureRandom random = new SecureRandom(seed);
  66         KeyGenerator aesKeyGen = KeyGenerator.getInstance("AES");
  67         KeyGenerator desKeyGen = KeyGenerator.getInstance("DESede");
  68         aesKeyGen.init(128, random);
  69         desKeyGen.init(168, random);
  70         aesKey = aesKeyGen.generateKey();
  71         desKey = desKeyGen.generateKey();
  72         input = readClassfile16(getClass());
  73 
  74         aesExpected.write(runEncryptDecrypt(aesKey, "AES/CBC/NoPadding"));
  75         aesExpected.write(runEncryptDecrypt(aesKey, "AES/CBC/PKCS5Padding"));
  76 
  77         desExpected.write(runEncryptDecrypt(desKey, "DESede/CBC/NoPadding"));
  78         desExpected.write(runEncryptDecrypt(desKey, "DESede/CBC/PKCS5Padding"));
  79     }
  80 
  81     @Test
  82     public void testAESCryptIntrinsics() throws Exception {
  83         if (compileAndInstall("com.sun.crypto.provider.AESCrypt", HotSpotGraphBuilderPlugins.aesEncryptName, HotSpotGraphBuilderPlugins.aesDecryptName)) {
  84             ByteArrayOutputStream actual = new ByteArrayOutputStream();
  85             actual.write(runEncryptDecrypt(aesKey, "AES/CBC/NoPadding"));
  86             actual.write(runEncryptDecrypt(aesKey, "AES/CBC/PKCS5Padding"));
  87             Assert.assertArrayEquals(aesExpected.toByteArray(), actual.toByteArray());
  88         }
  89     }
  90 
  91     @Test
  92     public void testCipherBlockChainingIntrinsics() throws Exception {
  93         boolean implNames = HotSpotGraphBuilderPlugins.cbcUsesImplNames(runtime().getVMConfig());
  94         String cbcEncryptName = implNames ? "implEncrypt" : "encrypt";
  95         String cbcDecryptName = implNames ? "implDecrypt" : "decrypt";
  96         if (compileAndInstall("com.sun.crypto.provider.CipherBlockChaining", cbcEncryptName, cbcDecryptName)) {
  97             ByteArrayOutputStream actual = new ByteArrayOutputStream();
  98             actual.write(runEncryptDecrypt(aesKey, "AES/CBC/NoPadding"));
  99             actual.write(runEncryptDecrypt(aesKey, "AES/CBC/PKCS5Padding"));
 100             Assert.assertArrayEquals(aesExpected.toByteArray(), actual.toByteArray());
 101 
 102             actual.reset();
 103             actual.write(runEncryptDecrypt(desKey, "DESede/CBC/NoPadding"));
 104             actual.write(runEncryptDecrypt(desKey, "DESede/CBC/PKCS5Padding"));
 105             Assert.assertArrayEquals(desExpected.toByteArray(), actual.toByteArray());
 106         }
 107     }
 108 
 109     /**
 110      * Compiles and installs the substitution for some specified methods. Once installed, the next
 111      * execution of the methods will use the newly installed code.
 112      *
 113      * @param className the name of the class for which substitutions are available
 114      * @param methodNames the names of the substituted methods
 115      * @return true if at least one substitution was compiled and installed
 116      */
 117     private boolean compileAndInstall(String className, String... methodNames) {
 118         if (!runtime().getVMConfig().useAESIntrinsics) {
 119             return false;
 120         }
 121         Class<?> c;
 122         try {
 123             c = Class.forName(className);
 124         } catch (ClassNotFoundException e) {
 125             // It's ok to not find the class - a different security provider
 126             // may have been installed
 127             return false;
 128         }
 129         boolean atLeastOneCompiled = false;
 130         for (String methodName : methodNames) {
 131             if (compileAndInstallSubstitution(c, methodName) != null) {
 132                 atLeastOneCompiled = true;
 133             }
 134         }
 135         return atLeastOneCompiled;
 136     }
 137 
 138     AlgorithmParameters algorithmParameters;
 139 
 140     private byte[] encrypt(byte[] indata, SecretKey key, String algorithm) throws Exception {
 141 
 142         byte[] result = indata;
 143 
 144         Cipher c = Cipher.getInstance(algorithm);
 145         c.init(Cipher.ENCRYPT_MODE, key);
 146         algorithmParameters = c.getParameters();
 147 
 148         byte[] r1 = c.update(result);
 149         byte[] r2 = c.doFinal();
 150 
 151         result = new byte[r1.length + r2.length];
 152         System.arraycopy(r1, 0, result, 0, r1.length);
 153         System.arraycopy(r2, 0, result, r1.length, r2.length);
 154 
 155         return result;
 156     }
 157 
 158     private byte[] decrypt(byte[] indata, SecretKey key, String algorithm) throws Exception {
 159 
 160         byte[] result = indata;
 161 
 162         Cipher c = Cipher.getInstance(algorithm);
 163         c.init(Cipher.DECRYPT_MODE, key, algorithmParameters);
 164 
 165         byte[] r1 = c.update(result);
 166         byte[] r2 = c.doFinal();
 167 
 168         result = new byte[r1.length + r2.length];
 169         System.arraycopy(r1, 0, result, 0, r1.length);
 170         System.arraycopy(r2, 0, result, r1.length, r2.length);
 171         return result;
 172     }
 173 
 174     private static byte[] readClassfile16(Class<? extends HotSpotCryptoSubstitutionTest> c) throws IOException {
 175         String classFilePath = "/" + c.getName().replace('.', '/') + ".class";
 176         InputStream stream = c.getResourceAsStream(classFilePath);
 177         int bytesToRead = stream.available();
 178         bytesToRead -= bytesToRead % 16;
 179         byte[] classFile = new byte[bytesToRead];
 180         new DataInputStream(stream).readFully(classFile);
 181         return classFile;
 182     }
 183 
 184     public byte[] runEncryptDecrypt(SecretKey key, String algorithm) throws Exception {
 185         byte[] indata = input.clone();
 186         byte[] cipher = encrypt(indata, key, algorithm);
 187         byte[] plain = decrypt(cipher, key, algorithm);
 188         Assert.assertArrayEquals(indata, plain);
 189         return plain;
 190     }
 191 }