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