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