• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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