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