1 /** 2 * Licensed under the Apache License, Version 2.0 (the "License"); 3 * you may not use this file except in compliance with the License. 4 * You may obtain a copy of the License at 5 * 6 * http://www.apache.org/licenses/LICENSE-2.0 7 * 8 * Unless required by applicable law or agreed to in writing, software 9 * distributed under the License is distributed on an "AS IS" BASIS, 10 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 * See the License for the specific language governing permissions and 12 * limitations under the License. 13 */ 14 package com.google.security.wycheproof; 15 16 import static org.junit.Assert.assertEquals; 17 import static org.junit.Assert.fail; 18 19 import com.google.gson.JsonElement; 20 import com.google.gson.JsonObject; 21 import java.security.KeyFactory; 22 import java.security.NoSuchAlgorithmException; 23 import java.security.PrivateKey; 24 import java.security.spec.PKCS8EncodedKeySpec; 25 import java.util.Set; 26 import java.util.TreeSet; 27 import javax.crypto.Cipher; 28 import javax.crypto.NoSuchPaddingException; 29 import org.junit.Test; 30 import org.junit.runner.RunWith; 31 import org.junit.runners.JUnit4; 32 33 /** 34 * RSA encryption tests 35 * 36 * @author bleichen@google.com (Daniel Bleichenbacher) 37 */ 38 @RunWith(JUnit4.class) 39 public class RsaEncryptionTest { 40 41 /** 42 * Providers that implement RSA with PKCS1Padding but not OAEP are outdated and should be avoided 43 * even if RSA is currently not used in a project. Such providers promote using an insecure 44 * cipher. There is a great danger that PKCS1Padding is used as a temporary workaround, but later 45 * stays in the project for much longer than necessary. 46 */ 47 @Test testOutdatedProvider()48 public void testOutdatedProvider() throws Exception { 49 try { 50 Cipher c = Cipher.getInstance("RSA/ECB/PKCS1Padding"); 51 try { 52 Cipher.getInstance("RSA/ECB/OAEPWITHSHA-1ANDMGF1PADDING"); 53 } catch (NoSuchPaddingException | NoSuchAlgorithmException ex) { 54 fail("Provider " + c.getProvider().getName() + " is outdated and should not be used."); 55 } 56 } catch (NoSuchPaddingException | NoSuchAlgorithmException ex) { 57 System.out.println("RSA/ECB/PKCS1Padding is not implemented"); 58 } 59 } 60 61 /** 62 * Get a PublicKey from a JsonObject. 63 * 64 * <p>object contains the key in multiple formats: "key" : elements of the public key "keyDer": 65 * the key in ASN encoding encoded hexadecimal "keyPem": the key in Pem format encoded hexadecimal 66 * The test can use the format that is most convenient. 67 */ 68 // This is a false positive, since errorprone cannot track values passed into a method. 69 @SuppressWarnings("InsecureCryptoUsage") getPrivateKey(JsonObject object)70 protected static PrivateKey getPrivateKey(JsonObject object) throws Exception { 71 KeyFactory kf; 72 kf = KeyFactory.getInstance("RSA"); 73 byte[] encoded = TestUtil.hexToBytes(object.get("privateKeyPkcs8").getAsString()); 74 PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encoded); 75 return kf.generatePrivate(keySpec); 76 } 77 78 /** Convenience method to get a byte array from a JsonObject */ getBytes(JsonObject object, String name)79 protected static byte[] getBytes(JsonObject object, String name) throws Exception { 80 return JsonUtil.asByteArray(object.get(name)); 81 } 82 83 /** 84 * Tries decrypting RSA-PKCS #1 v 1.5 encrypted ciphertext. 85 * RSA-PKCS #1 v 1.5 is susceptible to chosen ciphertext attacks. The seriousness of the 86 * attack depends on how much information is leaked when decrypting an invalid ciphertext. 87 * The test vectors with invalid padding contain a flag "InvalidPkcs1Padding". 88 * The test below expects that all test vectors with this flag throw an indistinguishable 89 * exception. 90 * 91 * <p><b>References:</b> 92 * 93 * <ul> 94 * <li>Bleichenbacher, "Chosen ciphertext attacks against protocols based on the RSA encryption 95 * standard PKCS# 1" Crypto 98 96 * <li>Manger, "A chosen ciphertext attack on RSA optimal asymmetric encryption padding (OAEP) 97 * as standardized in PKCS# 1 v2.0", Crypto 2001 This paper shows that OAEP is susceptible 98 * to a chosen ciphertext attack if error messages distinguish between different failure 99 * condidtions. 100 * <li>Bardou, Focardi, Kawamoto, Simionato, Steel, Tsay "Efficient Padding Oracle Attacks on 101 * Cryptographic Hardware", Crypto 2012 The paper shows that small differences on what 102 * information an attacker receives can make a big difference on the number of chosen 103 * message necessary for an attack. 104 * <li>Smart, "Errors matter: Breaking RSA-based PIN encryption with thirty ciphertext validity 105 * queries" RSA conference, 2010 This paper shows that padding oracle attacks can be 106 * successful with even a small number of queries. 107 * </ul> 108 * 109 * <p><b>Some recent bugs:</b> CVE-2012-5081: Java JSSE provider leaked information through 110 * exceptions and timing. Both the PKCS #1 padding and the OAEP padding were broken: 111 * http://www-brs.ub.ruhr-uni-bochum.de/netahtml/HSS/Diss/MeyerChristopher/diss.pdf 112 * 113 * <p><b>What this test does not (yet) cover:</b> 114 * 115 * <ul> 116 * <li>A previous version of one of the provider leaked the block type. (when was this fixed?) 117 * <li>Some attacks require a large number of ciphertexts to be detected if random ciphertexts 118 * are used. Such problems require specifically crafted ciphertexts to run in a unit test. 119 * E.g. "Attacking RSA-based Sessions in SSL/TLS" by V. Klima, O. Pokorny, and T. Rosa: 120 * https://eprint.iacr.org/2003/052/ 121 * <li>Timing leakages because of differences in parsing the padding (e.g. CVE-2015-7827) Such 122 * differences are too small to be reliably detectable in unit tests. 123 * </ul> 124 */ 125 @SuppressWarnings("InsecureCryptoUsage") testDecryption(String filename)126 public void testDecryption(String filename) throws Exception { 127 final String expectedSchema = "rsaes_pkcs1_decrypt_schema.json"; 128 JsonObject test = JsonUtil.getTestVectors(filename); 129 String schema = test.get("schema").getAsString(); 130 if (!schema.equals(expectedSchema)) { 131 System.out.println( 132 "Expecting test vectors with schema " 133 + expectedSchema 134 + " found vectors with schema " 135 + schema); 136 } 137 // Padding oracle attacks become simpler when the decryption leaks detailed information about 138 // invalid paddings. Hence implementations are expected to not include such information in the 139 // exception thrown in the case of an invalid padding. 140 // Test vectors with an invalid padding have a flag "InvalidPkcs1Padding". 141 // Invalid test vectors without this flag are cases where the error are detected before 142 // the ciphertext is decrypted, e.g. if the size of the ciphertext is incorrect. 143 final String invalidPkcs1Padding = "InvalidPkcs1Padding"; 144 Set<String> exceptions = new TreeSet<String>(); 145 146 int errors = 0; 147 Cipher decrypter = Cipher.getInstance("RSA/ECB/PKCS1Padding"); 148 for (JsonElement g : test.getAsJsonArray("testGroups")) { 149 JsonObject group = g.getAsJsonObject(); 150 PrivateKey key = getPrivateKey(group); 151 for (JsonElement t : group.getAsJsonArray("tests")) { 152 JsonObject testcase = t.getAsJsonObject(); 153 int tcid = testcase.get("tcId").getAsInt(); 154 String messageHex = TestUtil.bytesToHex(getBytes(testcase, "msg")); 155 byte[] ciphertext = getBytes(testcase, "ct"); 156 String ciphertextHex = TestUtil.bytesToHex(ciphertext); 157 String result = testcase.get("result").getAsString(); 158 decrypter.init(Cipher.DECRYPT_MODE, key); 159 byte[] decrypted = null; 160 String exception = ""; 161 try { 162 decrypted = decrypter.doFinal(ciphertext); 163 } catch (Exception ex) { 164 // TODO(bleichen): The exception thrown should always be 165 // a GeneralSecurityException. 166 // However, BouncyCastle throws some non-conforming exceptions. 167 // For the moment we do not count this as a problem to avoid that 168 // more serious bugs remain hidden. In particular, the test expects 169 // that all ciphertexts with an invalid padding throw the same 170 // indistinguishable exception. 171 decrypted = null; 172 exception = ex.toString(); 173 for (JsonElement flag : testcase.getAsJsonArray("flags")) { 174 if (flag.getAsString().equals(invalidPkcs1Padding)) { 175 exceptions.add(exception); 176 break; 177 } 178 } 179 } 180 if (decrypted == null && result.equals("valid")) { 181 System.out.printf( 182 "Valid ciphertext not decrypted. filename:%s tcId:%d ct:%s cause:%s\n", 183 filename, tcid, ciphertextHex, exception); 184 errors++; 185 } else if (decrypted != null) { 186 String decryptedHex = TestUtil.bytesToHex(decrypted); 187 if (result.equals("invalid")) { 188 System.out.printf( 189 "Invalid ciphertext decrypted. filename:%s tcId:%d expected:%s decrypted:%s\n", 190 filename, tcid, messageHex, decryptedHex); 191 errors++; 192 } else if (!decryptedHex.equals(messageHex)) { 193 System.out.printf( 194 "Incorrect decryption. filename:%s tcId:%d expected:%s decrypted:%s\n", 195 filename, tcid, messageHex, decryptedHex); 196 errors++; 197 } 198 } 199 } 200 } 201 if (exceptions.size() != 1) { 202 System.out.println("Exceptions for RSA/ECB/PKCS1Padding"); 203 for (String s : exceptions) { 204 System.out.println(s); 205 } 206 fail("Exceptions leak information about the padding"); 207 } 208 assertEquals(0, errors); 209 } 210 211 @Test testDecryption2048()212 public void testDecryption2048() throws Exception { 213 testDecryption("rsa_pkcs1_2048_test.json"); 214 } 215 216 @Test testDecryption3072()217 public void testDecryption3072() throws Exception { 218 testDecryption("rsa_pkcs1_3072_test.json"); 219 } 220 221 @Test testDecryption4096()222 public void testDecryption4096() throws Exception { 223 testDecryption("rsa_pkcs1_4096_test.json"); 224 } 225 } 226