• 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.assertTrue;
18 
19 import com.google.gson.JsonElement;
20 import com.google.gson.JsonObject;
21 import java.security.AlgorithmParameters;
22 import java.security.GeneralSecurityException;
23 import java.security.KeyFactory;
24 import java.security.NoSuchAlgorithmException;
25 import java.security.PrivateKey;
26 import java.security.PublicKey;
27 import java.security.spec.AlgorithmParameterSpec;
28 import java.security.spec.MGF1ParameterSpec;
29 import java.security.spec.PKCS8EncodedKeySpec;
30 import java.security.spec.X509EncodedKeySpec;
31 import javax.crypto.Cipher;
32 import javax.crypto.spec.OAEPParameterSpec;
33 import javax.crypto.spec.PSource;
34 import org.junit.Test;
35 import org.junit.runner.RunWith;
36 import org.junit.runners.JUnit4;
37 
38 /**
39  * Checks implementations of RSA-OAEP.
40  */
41 @RunWith(JUnit4.class)
42 public class RsaOaepTest {
43 
44   /**
45    * A list of algorithm names for RSA-OAEP.
46    *
47    * The standard algorithm names for RSA-OAEP are defined in
48    * https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html
49    */
50   static String[] OaepAlgorithmNames = {
51       "RSA/None/OAEPPadding",
52       "RSA/None/OAEPwithSHA-1andMGF1Padding",
53       "RSA/None/OAEPwithSHA-224andMGF1Padding",
54       "RSA/None/OAEPwithSHA-256andMGF1Padding",
55       "RSA/None/OAEPwithSHA-384andMGF1Padding",
56       "RSA/None/OAEPwithSHA-512andMGF1Padding",
57   };
58 
printParameters(AlgorithmParameterSpec params)59   protected static void printParameters(AlgorithmParameterSpec params) {
60     if (params instanceof OAEPParameterSpec) {
61       OAEPParameterSpec oaepParams = (OAEPParameterSpec) params;
62       System.out.println("OAEPParameterSpec");
63       System.out.println("digestAlgorithm:" + oaepParams.getDigestAlgorithm());
64       System.out.println("mgfAlgorithm:" + oaepParams.getMGFAlgorithm());
65       printParameters(oaepParams.getMGFParameters());
66     } else if (params instanceof MGF1ParameterSpec) {
67       MGF1ParameterSpec mgf1Params = (MGF1ParameterSpec) params;
68       System.out.println("MGF1ParameterSpec");
69       System.out.println("digestAlgorithm:" + mgf1Params.getDigestAlgorithm());
70     } else {
71       System.out.println(params.toString());
72     }
73   }
74 
75   /**
76    * This is not a real test. The JCE algorithm names only specify one hash algorithm. But OAEP
77    * uses two hases. One hash algorithm is used to hash the labels. The other hash algorithm is
78    * used for the mask generation function.
79    *
80    * <p>Different provider use different default values for the hash function that is not specified
81    * in the algorithm name. Jdk uses mgfsha1 as default. BouncyCastle and Conscrypt use the same
82    * hash for labels and mgf. Every provider allows to specify all the parameters using
83    * an OAEPParameterSpec instance.
84    *
85    * <p>This test simply tries a number of algorithm names for RSA-OAEP and prints the OAEP
86    * parameters for the case where no OAEPParameterSpec is used.
87    */
88   // TODO(bleichen): jdk11 will also add parameters to the RSA keys. This will need more tests.
89   @Test
testDefaults()90   public void testDefaults() throws Exception {
91     String pubKey =
92         "30820122300d06092a864886f70d01010105000382010f003082010a02820101"
93             + "00bdf90898577911c71c4d9520c5f75108548e8dfd389afdbf9c997769b8594e"
94             + "7dc51c6a1b88d1670ec4bb03fa550ba6a13d02c430bfe88ae4e2075163017f4d"
95             + "8926ce2e46e068e88962f38112fc2dbd033e84e648d4a816c0f5bd89cadba0b4"
96             + "d6cac01832103061cbb704ebacd895def6cff9d988c5395f2169a6807207333d"
97             + "569150d7f569f7ebf4718ddbfa2cdbde4d82a9d5d8caeb467f71bfc0099b0625"
98             + "a59d2bad12e3ff48f2fd50867b89f5f876ce6c126ced25f28b1996ee21142235"
99             + "fb3aef9fe58d9e4ef6e4922711a3bbcd8adcfe868481fd1aa9c13e5c658f5172"
100             + "617204314665092b4d8dca1b05dc7f4ecd7578b61edeb949275be8751a5a1fab"
101             + "c30203010001";
102     KeyFactory kf;
103     kf = KeyFactory.getInstance("RSA");
104     X509EncodedKeySpec x509keySpec = new X509EncodedKeySpec(TestUtil.hexToBytes(pubKey));
105     PublicKey key = kf.generatePublic(x509keySpec);
106     for (String oaepName : OaepAlgorithmNames) {
107       try {
108         Cipher c = Cipher.getInstance(oaepName);
109         c.init(Cipher.ENCRYPT_MODE, key);
110         System.out.println("Algorithm " + oaepName + " uses the following defaults");
111         AlgorithmParameters params = c.getParameters();
112         printParameters(params.getParameterSpec(OAEPParameterSpec.class));
113       } catch (NoSuchAlgorithmException ex) {
114         continue;
115       }
116     }
117   }
118 
119   /** Convenience mehtod to get a String from a JsonObject */
getString(JsonObject object, String name)120   protected static String getString(JsonObject object, String name) throws Exception {
121     return object.get(name).getAsString();
122   }
123 
124   /** Convenience method to get a byte array from a JsonObject */
getBytes(JsonObject object, String name)125   protected static byte[] getBytes(JsonObject object, String name) throws Exception {
126     return JsonUtil.asByteArray(object.get(name));
127   }
128 
129   /**
130    * Get a PublicKey from a JsonObject.
131    *
132    * <p>object contains the key in multiple formats: "key" : elements of the public key "keyDer":
133    * the key in ASN encoding encoded hexadecimal "keyPem": the key in Pem format encoded hexadecimal
134    * The test can use the format that is most convenient.
135    */
136   // This is a false positive, since errorprone cannot track values passed into a method.
137   @SuppressWarnings("InsecureCryptoUsage")
getPrivateKey(JsonObject object)138   protected static PrivateKey getPrivateKey(JsonObject object) throws Exception {
139     KeyFactory kf;
140     kf = KeyFactory.getInstance("RSA");
141     byte[] encoded = TestUtil.hexToBytes(getString(object, "privateKeyPkcs8"));
142     PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encoded);
143     return kf.generatePrivate(keySpec);
144   }
145 
getOaepAlgorithmName(JsonObject group)146   protected static String getOaepAlgorithmName(JsonObject group) throws Exception {
147     String mgf = getString(group, "mgf");
148     String mgfSha = getString(group, "mgfSha");
149     return "RSA/ECB/OAEPwith" + mgfSha + "and" + mgf + "Padding";
150   }
151 
getOaepParameters(JsonObject group, JsonObject test)152   protected static OAEPParameterSpec getOaepParameters(JsonObject group,
153                                                        JsonObject test) throws Exception {
154     String sha = getString(group, "sha");
155     String mgf = getString(group, "mgf");
156     String mgfSha = getString(group, "mgfSha");
157     PSource p = PSource.PSpecified.DEFAULT;
158     if (test.has("label")) {
159       p = new PSource.PSpecified(getBytes(test, "label"));
160     }
161     return new OAEPParameterSpec(sha, mgf, new MGF1ParameterSpec(mgfSha), p);
162   }
163 
164   /**
165    * Tests the signature verification with test vectors in a given JSON file.
166    *
167    * <p> Example format for test vectors
168    * { "algorithm" : "RSA-OAEP",
169    *   "schema" : "rsaes_oaep_decrypt_schema.json",
170    *   "generatorVersion" : "0.7",
171    *   ...
172    *   "testGroups" : [
173    *     {
174    *       "d" : "...",
175    *       "e" : "10001",
176    *       "n" : "...",
177    *       "keysize" : 2048,
178    *       "sha" : "SHA-256",
179    *       "mgf" : "MGF1",
180    *       "mgfSha" : "SHA-256",
181    *       "privateKeyPem" : "-----BEGIN RSA PRIVATE KEY-----\n...",
182    *       "privateKeyPkcs8" : "...",
183    *       "type" : "RSAES",
184    *       "tests" : [
185    *         {
186    *           "tcId" : 1,
187    *           "comment" : "",
188    *           "msg" : "30313233343030",
189    *           "ct" : "...",
190    *           "label" : "",
191    *           "result" : "valid",
192    *           "flags" : [],
193    *         },
194    *        ...
195    *
196    * @param filename the filename of the test vectors
197    * @param allowSkippingKeys if true then keys that cannot be constructed will not fail the test.
198    *        Most of the tests below are using allowSkippingKeys == false. The reason for doing this
199    *        is that providers have distinctive defaults. E.g., no OAEPParameterSpec is given then
200    *        BouncyCastle and Conscrypt use the same hash function for hashing the label and for the
201    *        mask generation function, while jdk uses MGF1SHA1. This is unfortunate and probably
202    *        difficult to fix. Hence, the tests below simply require that providers support each
203    *        others default parameters under the assumption that the OAEPParameterSpec is fully
204    *        specified.
205    **/
testOaep(String filename, boolean allowSkippingKeys)206   public void testOaep(String filename, boolean allowSkippingKeys)
207       throws Exception {
208     JsonObject test = JsonUtil.getTestVectors(filename);
209 
210     // Compares the expected and actual JSON schema of the test vector file.
211     // Mismatched JSON schemas will likely lead to a test failure.
212     String generatorVersion = getString(test, "generatorVersion");
213     String expectedSchema = "rsaes_oaep_decrypt_schema.json";
214     String actualSchema = getString(test, "schema");
215     if (!expectedSchema.equals(actualSchema)) {
216       System.out.println(
217           "Expecting test vectors with schema "
218               + expectedSchema
219               + " found vectors with schema "
220               + actualSchema
221               + " generatorVersion:"
222               + generatorVersion);
223     }
224 
225     int numTests = test.get("numberOfTests").getAsInt();
226     int cntTests = 0;
227     int errors = 0;
228     int skippedKeys = 0;
229     for (JsonElement g : test.getAsJsonArray("testGroups")) {
230       JsonObject group = g.getAsJsonObject();
231       PrivateKey key;
232       try {
233         key = getPrivateKey(group);
234       } catch (GeneralSecurityException ex) {
235         skippedKeys++;
236         if (!allowSkippingKeys) {
237           System.out.printf("Key generation throws:%s\n", ex.toString());
238         }
239         continue;
240       }
241       String algorithm = getOaepAlgorithmName(group);
242       Cipher decrypter = Cipher.getInstance(algorithm);
243       for (JsonElement t : group.getAsJsonArray("tests")) {
244         cntTests++;
245         JsonObject testcase = t.getAsJsonObject();
246         int tcid = testcase.get("tcId").getAsInt();
247         String messageHex = TestUtil.bytesToHex(getBytes(testcase, "msg"));
248         OAEPParameterSpec params = getOaepParameters(group, testcase);
249         byte[] ciphertext = getBytes(testcase, "ct");
250         String ciphertextHex = TestUtil.bytesToHex(ciphertext);
251         String result = getString(testcase, "result");
252         decrypter.init(Cipher.DECRYPT_MODE, key, params);
253         byte[] decrypted = null;
254         try {
255           decrypted = decrypter.doFinal(ciphertext);
256         } catch (GeneralSecurityException ex) {
257           decrypted = null;
258         } catch (Exception ex) {
259           // Other exceptions (i.e. unchecked exceptions) are considered as error
260           // since a third party should never be able to cause such exceptions.
261           System.out.printf("Decryption throws %s. filename:%s tcId:%d ct:%s\n",
262                             ex.toString(), filename, tcid, ciphertextHex);
263           decrypted = null;
264           // TODO(bleichen): BouncyCastle throws some non-conforming exceptions.
265           //   For the moment we do not count this as a problem to avoid that
266           //   more serious bugs remain hidden.
267           // errors++;
268         }
269         if (decrypted == null && result.equals("valid")) {
270             System.out.printf(
271                 "Valid ciphertext not decrypted. filename:%s tcId:%d ct:%s\n",
272                 filename, tcid, ciphertextHex);
273           errors++;
274         } else if (decrypted != null) {
275           String decryptedHex = TestUtil.bytesToHex(decrypted);
276           if (result.equals("invalid")) {
277             System.out.printf(
278                 "Invalid ciphertext decrypted. filename:%s tcId:%d expected:%s decrypted:%s\n",
279                 filename, tcid, messageHex, decryptedHex);
280              errors++;
281           } else if (!decryptedHex.equals(messageHex)) {
282             System.out.printf(
283                 "Incorrect decryption. filename:%s tcId:%d expected:%s decrypted:%s\n",
284                 filename, tcid, messageHex, decryptedHex);
285              errors++;
286           }
287         }
288       }
289     }
290     assertEquals(0, errors);
291     if (skippedKeys > 0) {
292       System.out.println("RSAES-OAEP: file:" + filename + " skipped key:" + skippedKeys);
293       assertTrue(allowSkippingKeys);
294     } else {
295       assertEquals(numTests, cntTests);
296     }
297   }
298 
299   @Test
testRsaOaep2048Sha1Mgf1Sha1()300   public void testRsaOaep2048Sha1Mgf1Sha1() throws Exception {
301    testOaep("rsa_oaep_2048_sha1_mgf1sha1_test.json", false);
302   }
303 
304   @Test
testRsaOaep2048Sha224Mgf1Sha1()305   public void testRsaOaep2048Sha224Mgf1Sha1() throws Exception {
306    testOaep("rsa_oaep_2048_sha224_mgf1sha1_test.json", false);
307   }
308 
309   @Test
testRsaOaep2048Sha224Mgf1Sha224()310   public void testRsaOaep2048Sha224Mgf1Sha224() throws Exception {
311    testOaep("rsa_oaep_2048_sha224_mgf1sha224_test.json", false);
312   }
313 
314   @Test
testRsaOaep2048Sha256Mgf1Sha1()315   public void testRsaOaep2048Sha256Mgf1Sha1() throws Exception {
316    testOaep("rsa_oaep_2048_sha256_mgf1sha1_test.json", false);
317   }
318 
319   @Test
testRsaOaep2048Sha256Mgf1Sha256()320   public void testRsaOaep2048Sha256Mgf1Sha256() throws Exception {
321    testOaep("rsa_oaep_2048_sha256_mgf1sha256_test.json", false);
322   }
323 
324   @Test
testRsaOaep2048Sha384Mgf1Sha1()325   public void testRsaOaep2048Sha384Mgf1Sha1() throws Exception {
326    testOaep("rsa_oaep_2048_sha384_mgf1sha1_test.json", false);
327   }
328 
329   @Test
testRsaOaep2048Sha384Mgf1Sha384()330   public void testRsaOaep2048Sha384Mgf1Sha384() throws Exception {
331    testOaep("rsa_oaep_2048_sha384_mgf1sha384_test.json", false);
332   }
333 
334   @Test
testRsaOaep2048Sha512Mgf1Sha1()335   public void testRsaOaep2048Sha512Mgf1Sha1() throws Exception {
336    testOaep("rsa_oaep_2048_sha512_mgf1sha1_test.json", false);
337   }
338 
339   @Test
testRsaOaep2048Sha512Mgf1Sha512()340   public void testRsaOaep2048Sha512Mgf1Sha512() throws Exception {
341    testOaep("rsa_oaep_2048_sha512_mgf1sha512_test.json", false);
342   }
343 
344   @Test
testRsaOaep3072Sha256Mgf1Sha1()345   public void testRsaOaep3072Sha256Mgf1Sha1() throws Exception {
346    testOaep("rsa_oaep_3072_sha256_mgf1sha1_test.json", false);
347   }
348 
349   @Test
testRsaOaep3072Sha256Mgf1Sha256()350   public void testRsaOaep3072Sha256Mgf1Sha256() throws Exception {
351    testOaep("rsa_oaep_3072_sha256_mgf1sha256_test.json", false);
352   }
353 
354   @Test
testRsaOaep3072Sha512Mgf1Sha1()355   public void testRsaOaep3072Sha512Mgf1Sha1() throws Exception {
356    testOaep("rsa_oaep_3072_sha512_mgf1sha1_test.json", false);
357   }
358 
359   @Test
testRsaOaep3072Sha512Mgf1Sha512()360   public void testRsaOaep3072Sha512Mgf1Sha512() throws Exception {
361    testOaep("rsa_oaep_3072_sha512_mgf1sha512_test.json", false);
362   }
363 
364   @Test
testRsaOaep4096Sha256Mgf1Sha1()365   public void testRsaOaep4096Sha256Mgf1Sha1() throws Exception {
366    testOaep("rsa_oaep_4096_sha256_mgf1sha1_test.json", false);
367   }
368 
369   @Test
testRsaOaep4096Sha256Mgf1Sha256()370   public void testRsaOaep4096Sha256Mgf1Sha256() throws Exception {
371    testOaep("rsa_oaep_4096_sha256_mgf1sha256_test.json", false);
372   }
373 
374   @Test
testRsaOaep4096Sha512Mgf1Sha1()375   public void testRsaOaep4096Sha512Mgf1Sha1() throws Exception {
376    testOaep("rsa_oaep_4096_sha512_mgf1sha1_test.json", false);
377   }
378 
379   @Test
testRsaOaep4096Sha512Mgf1Sha512()380   public void testRsaOaep4096Sha512Mgf1Sha512() throws Exception {
381    testOaep("rsa_oaep_4096_sha512_mgf1sha512_test.json", false);
382   }
383 
384   @Test
testRsaOaepMisc()385   public void testRsaOaepMisc() throws Exception {
386    testOaep("rsa_oaep_misc_test.json", false);
387   }
388 
389 }
390 
391