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