Wycheproof version 0.6
NOKEYCHECK=True PiperOrigin-RevId: 222384581 Change-Id: I1749fc9e570634e5429c3162787de0539cbcfd4d GitOrigin-RevId: 4f8bfc19eb425f6a1083637b2810002dc0ae5b41
This commit is contained in:
@@ -44,6 +44,7 @@ import org.junit.runners.Suite.SuiteClasses;
|
||||
RsaEncryptionTest.class,
|
||||
RsaKeyTest.class,
|
||||
RsaPssTest.class,
|
||||
RsaOaepTest.class,
|
||||
RsaSignatureTest.class,
|
||||
})
|
||||
@Provider(ProviderType.BOUNCY_CASTLE)
|
||||
|
||||
@@ -45,6 +45,7 @@ import org.junit.runners.Suite.SuiteClasses;
|
||||
RsaEncryptionTest.class,
|
||||
RsaKeyTest.class,
|
||||
RsaPssTest.class,
|
||||
RsaOaepTest.class,
|
||||
RsaSignatureTest.class,
|
||||
})
|
||||
@Provider(ProviderType.BOUNCY_CASTLE)
|
||||
|
||||
@@ -40,6 +40,7 @@ import org.junit.runners.Suite.SuiteClasses;
|
||||
MessageDigestTest.class,
|
||||
RsaEncryptionTest.class,
|
||||
RsaKeyTest.class,
|
||||
RsaOaepTest.class,
|
||||
RsaPssTest.class,
|
||||
RsaSignatureTest.class
|
||||
})
|
||||
|
||||
@@ -41,6 +41,7 @@ import org.junit.runners.Suite.SuiteClasses;
|
||||
MessageDigestTest.class,
|
||||
RsaEncryptionTest.class,
|
||||
RsaKeyTest.class,
|
||||
RsaOaepTest.class,
|
||||
RsaPssTest.class,
|
||||
RsaSignatureTest.class
|
||||
})
|
||||
|
||||
@@ -39,10 +39,13 @@ import org.junit.runners.Suite.SuiteClasses;
|
||||
JsonEcdhTest.class,
|
||||
JsonKeyWrapTest.class,
|
||||
JsonSignatureTest.class,
|
||||
JsonXdhTest.class,
|
||||
MessageDigestTest.class,
|
||||
RsaEncryptionTest.class,
|
||||
RsaKeyTest.class,
|
||||
RsaSignatureTest.class
|
||||
RsaPssTest.class,
|
||||
RsaOaepTest.class,
|
||||
RsaSignatureTest.class,
|
||||
})
|
||||
@Provider(ProviderType.OPENJDK)
|
||||
public final class OpenJDKAllTests {
|
||||
|
||||
@@ -38,10 +38,13 @@ import org.junit.runners.Suite.SuiteClasses;
|
||||
JsonEcdhTest.class,
|
||||
JsonKeyWrapTest.class,
|
||||
JsonSignatureTest.class,
|
||||
JsonXdhTest.class,
|
||||
MessageDigestTest.class,
|
||||
RsaEncryptionTest.class,
|
||||
RsaKeyTest.class,
|
||||
RsaSignatureTest.class
|
||||
RsaPssTest.class,
|
||||
RsaOaepTest.class,
|
||||
RsaSignatureTest.class,
|
||||
})
|
||||
@Provider(ProviderType.OPENJDK)
|
||||
@Fast
|
||||
|
||||
@@ -42,6 +42,7 @@ import org.spongycastle.jce.provider.BouncyCastleProvider;
|
||||
JsonSignatureTest.class,
|
||||
RsaEncryptionTest.class,
|
||||
RsaKeyTest.class,
|
||||
RsaOaepTest.class,
|
||||
RsaPssTest.class,
|
||||
RsaSignatureTest.class,
|
||||
})
|
||||
|
||||
@@ -47,6 +47,7 @@ import org.spongycastle.jce.provider.BouncyCastleProvider;
|
||||
MessageDigestTest.class,
|
||||
RsaEncryptionTest.class,
|
||||
RsaKeyTest.class,
|
||||
RsaOaepTest.class,
|
||||
RsaPssTest.class,
|
||||
RsaSignatureTest.class,
|
||||
})
|
||||
|
||||
@@ -17,8 +17,6 @@ import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import com.google.security.wycheproof.WycheproofRunner.NoPresubmitTest;
|
||||
import com.google.security.wycheproof.WycheproofRunner.ProviderType;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.security.AlgorithmParameters;
|
||||
import java.security.GeneralSecurityException;
|
||||
@@ -50,10 +48,15 @@ import org.junit.runners.JUnit4;
|
||||
// allows to specify paddings, but ignores them, encryption using ByteBuffers doesn't even work
|
||||
// without exceptions, indicating that this hasn't even tested.
|
||||
//
|
||||
// <p> The ECIES interface has changed a few times. The current code is written for
|
||||
// BouncyCastle v 1.59.
|
||||
//
|
||||
// <p>TODO(bleichen):
|
||||
// - compressed points,
|
||||
// - maybe again CipherInputStream, CipherOutputStream,
|
||||
// - BouncyCastle has a KeyPairGenerator for ECIES. Is this one different from EC?
|
||||
// (1) test compressed points,
|
||||
// (2) maybe test with CipherInputStream, CipherOutputStream,
|
||||
// (3) BouncyCastle has a KeyPairGenerator for ECIES. Is this one different from EC?
|
||||
// (4) the tests below cheat to avoid using BouncyCastle specific classes.
|
||||
// (5) consider to move into a BouncyCastle specific package, to avoid test restrictions.
|
||||
@RunWith(JUnit4.class)
|
||||
public class EciesTest {
|
||||
|
||||
@@ -88,56 +91,18 @@ public class EciesTest {
|
||||
byte[] message = "Hello".getBytes("UTF-8");
|
||||
Cipher ecies = Cipher.getInstance("ECIESwithAES-CBC");
|
||||
ecies.init(Cipher.ENCRYPT_MODE, pub);
|
||||
// Gets the parameters used.
|
||||
// Getting the parameters here and setting them below for the decryption avoids the use of
|
||||
// org.bouncycastle.jce.spec.IESParameterSpec and hence making the code provider dependent.
|
||||
// The drawback of this approach is that it can only be used in a test environment.
|
||||
AlgorithmParameters params = ecies.getParameters();
|
||||
byte[] ciphertext = ecies.doFinal(message);
|
||||
System.out.println("testEciesBasic:" + TestUtil.bytesToHex(ciphertext));
|
||||
ecies.init(Cipher.DECRYPT_MODE, priv, ecies.getParameters());
|
||||
ecies.init(Cipher.DECRYPT_MODE, priv, params);
|
||||
byte[] decrypted = ecies.doFinal(ciphertext);
|
||||
assertEquals(TestUtil.bytesToHex(message), TestUtil.bytesToHex(decrypted));
|
||||
}
|
||||
|
||||
/**
|
||||
* ECIES does not allow encryption modes and paddings. If this test fails then we should add
|
||||
* additional tests covering the new algorithms.
|
||||
*/
|
||||
// TODO(bleichen): This test describes BouncyCastles behaviour, but not necessarily what we
|
||||
// expect.
|
||||
@SuppressWarnings("InsecureCryptoUsage")
|
||||
@Test
|
||||
public void testInvalidNames() throws Exception {
|
||||
String[] invalidNames =
|
||||
new String[] {
|
||||
"ECIESWITHAES/CBC/PKCS5PADDING",
|
||||
"ECIESWITHAES/CBC/PKCS7PADDING",
|
||||
/* MOE:insert "ECIESWITHAES/DHAES/NOPADDING", */
|
||||
/* MOE:insert "ECIESWITHDESEDE/DHAES/NOPADDING", */
|
||||
"ECIESWITHAES/ECB/NOPADDING",
|
||||
"ECIESWITHAES/CTR/NOPADDING",
|
||||
};
|
||||
for (String algorithm : invalidNames) {
|
||||
try {
|
||||
Cipher.getInstance(algorithm);
|
||||
fail("unexpected algorithm:" + algorithm);
|
||||
} catch (NoSuchAlgorithmException ex) {
|
||||
// this is expected
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Here are a few names that BouncyCastle accepts. */
|
||||
// TODO(bleichen): This test describes BouncyCastles behaviour, but not necessarily what we
|
||||
// expect.
|
||||
@SuppressWarnings("InsecureCryptoUsage")
|
||||
@Test
|
||||
public void testValidNames() throws Exception {
|
||||
String[] validNames =
|
||||
new String[] {
|
||||
"ECIES/DHAES/PKCS7PADDING", "ECIESWITHAES-CBC/NONE/NOPADDING",
|
||||
};
|
||||
for (String algorithm : validNames) {
|
||||
Cipher.getInstance(algorithm);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* BouncyCastle has a key generation algorithm "ECIES". This test checks that the result are
|
||||
* ECKeys in both cases.
|
||||
@@ -235,14 +200,6 @@ public class EciesTest {
|
||||
fail("This should not work");
|
||||
} catch (GeneralSecurityException ex) {
|
||||
// This is as expected
|
||||
// Bouncy Castle 1.56 throws this exception
|
||||
// MOE:begin_strip
|
||||
// strip this catch from open source release,
|
||||
// until third_party/java/bouncycastle is upgraded to 1.56 or later.
|
||||
} catch (java.lang.IllegalArgumentException ex) {
|
||||
// This is what BouncyCastle < 1.56 throws when the points are not on the curve.
|
||||
// Maybe GeneralSecurityException would be better.
|
||||
// MOE:end_strip
|
||||
} catch (Exception ex) {
|
||||
fail(
|
||||
"Expected subclass of java.security.GeneralSecurityException, but got: "
|
||||
@@ -285,82 +242,91 @@ public class EciesTest {
|
||||
}
|
||||
|
||||
/** BouncyCastle v1.52 uses ECB as default for ECIES with AES. */
|
||||
@NoPresubmitTest(
|
||||
providers = {ProviderType.BOUNCY_CASTLE},
|
||||
bugs = {"b/30424901: won't fix, all BC ECIES modes are banned"}
|
||||
)
|
||||
@Test
|
||||
public void testDefaultEciesWithAes() throws Exception {
|
||||
testNotEcb("ECIESWithAES");
|
||||
}
|
||||
|
||||
/** BouncyCastle v1.52 uses ECB as default for ECIES with DESede. */
|
||||
@NoPresubmitTest(
|
||||
providers = {ProviderType.BOUNCY_CASTLE},
|
||||
bugs = {"b/30424901: won't fix, all BC ECIES modes are banned"}
|
||||
)
|
||||
@Test
|
||||
public void testDefaultEciesWithDESede() throws Exception {
|
||||
testNotEcb("ECIESwithDESede");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether algorithmA is an alias of algorithmB by encrypting with algorithmA and decrypting
|
||||
* with algorithmB.
|
||||
* This test tries to detect ECIES whether AlgorithmParameters are deterministic.
|
||||
*/
|
||||
@SuppressWarnings("InsecureCryptoUsage")
|
||||
public void testIsAlias(String algorithmA, String algorithmB) throws Exception {
|
||||
public void testAlgorithmParameters(String algorithm) throws Exception {
|
||||
Cipher eciesA;
|
||||
Cipher eciesB;
|
||||
// Allowing tests to be skipped, because we don't want to encourage abbreviations.
|
||||
try {
|
||||
eciesA = Cipher.getInstance(algorithmA);
|
||||
eciesA = Cipher.getInstance(algorithm);
|
||||
eciesB = Cipher.getInstance(algorithm);
|
||||
} catch (NoSuchAlgorithmException ex) {
|
||||
System.out.println("Skipping because of:" + ex.toString());
|
||||
return;
|
||||
}
|
||||
try {
|
||||
eciesB = Cipher.getInstance(algorithmB);
|
||||
} catch (NoSuchAlgorithmException ex) {
|
||||
System.out.println("Skipping because of:" + ex.toString());
|
||||
// This test is called with short algorithm names such as just "ECIES".
|
||||
// Requiring full names is typically a good practice. Hence it is OK
|
||||
// to not assigning default algorithms.
|
||||
System.out.println("No implementation for:" + algorithm);
|
||||
return;
|
||||
}
|
||||
ECGenParameterSpec ecSpec = new ECGenParameterSpec("secp256r1");
|
||||
KeyPairGenerator kf = KeyPairGenerator.getInstance("EC");
|
||||
kf.initialize(ecSpec);
|
||||
KeyPair keyPair = kf.generateKeyPair();
|
||||
byte[] message = "Hello".getBytes("UTF-8");
|
||||
eciesA.init(Cipher.ENCRYPT_MODE, keyPair.getPublic());
|
||||
byte[] ciphertext = eciesA.doFinal(message);
|
||||
eciesB.init(Cipher.DECRYPT_MODE, keyPair.getPrivate(), eciesA.getParameters());
|
||||
byte[] decrypted = eciesB.doFinal(ciphertext);
|
||||
assertEquals(TestUtil.bytesToHex(message), TestUtil.bytesToHex(decrypted));
|
||||
PublicKey pub = keyPair.getPublic();
|
||||
byte[] message = new byte[512];
|
||||
eciesA.init(Cipher.ENCRYPT_MODE, pub);
|
||||
eciesB.init(Cipher.ENCRYPT_MODE, pub);
|
||||
AlgorithmParameters paramsA = eciesA.getParameters();
|
||||
AlgorithmParameters paramsB = eciesB.getParameters();
|
||||
// byte[] ciphertextA = eciesA.doFinal(message);
|
||||
byte[] ciphertextB = eciesB.doFinal(message);
|
||||
PrivateKey priv = keyPair.getPrivate();
|
||||
eciesB.init(Cipher.DECRYPT_MODE, priv, paramsA);
|
||||
try {
|
||||
byte[] decrypted = eciesB.doFinal(ciphertextB);
|
||||
String messageHex = TestUtil.bytesToHex(message);
|
||||
String decryptedHex = TestUtil.bytesToHex(decrypted);
|
||||
if (messageHex.equals(decryptedHex)) {
|
||||
System.out.println(algorithm + " does (probably) not randomize AlgorithmParameters");
|
||||
} else {
|
||||
// This is the most interesting case.
|
||||
// The algorithm parameters are randomized but are not authenticated.
|
||||
// This is for example the case for the IV in ECIESWithAES-CBC in BouncyCastle.
|
||||
// If the caller attaches the randomized parameters to the ciphertext then
|
||||
// this would result in malleable encryption.
|
||||
System.out.println(
|
||||
algorithm + " uses randomized (unauthenticated) AlgorithmParameters."
|
||||
+ " message:" + messageHex
|
||||
+ " decrypted:" + decryptedHex
|
||||
+ "\nparamsA:" + paramsA.toString()
|
||||
+ " " + TestUtil.bytesToHex(paramsA.getEncoded())
|
||||
+ "\nparamsB:" + paramsB.toString()
|
||||
+ " " + TestUtil.bytesToHex(paramsB.getEncoded()));
|
||||
}
|
||||
} catch (GeneralSecurityException ex) {
|
||||
System.out.println(algorithm + " uses randomized AlgorithmParameters");
|
||||
}
|
||||
}
|
||||
|
||||
/** Tests whether two distinct algorithm names implement the same cipher */
|
||||
@Test
|
||||
public void testAlias() throws Exception {
|
||||
testIsAlias("ECIESWITHAES-CBC", "ECIESWithAES-CBC");
|
||||
testIsAlias("ECIESWITHAES", "ECIESWithAES");
|
||||
// BouncyCastle v 1.52 ignores mode and padding and considers the following
|
||||
// names as equivalent:
|
||||
// testIsAlias("ECIES/DHAES/PKCS7PADDING", "ECIES");
|
||||
testIsAlias("ECIESWITHAES-CBC/NONE/PKCS7PADDING", "ECIESWITHAES-CBC/NONE/NOPADDING");
|
||||
public void testAlgorithmParameters() throws Exception {
|
||||
testAlgorithmParameters("ECIES");
|
||||
testAlgorithmParameters("ECIESWithAES-CBC");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Encryption with ByteBuffers. This currently fails with BouncyCastle. Possibly this is the same
|
||||
* bug as http://www.bouncycastle.org/jira/browse/BJA-577 and should be fixed in version 1.53.
|
||||
* Encryption with ByteBuffers.
|
||||
* This test failed with BouncyCastle v 1.52 probably because of this bug
|
||||
* http://www.bouncycastle.org/jira/browse/BJA-577
|
||||
*/
|
||||
@NoPresubmitTest(
|
||||
providers = {ProviderType.BOUNCY_CASTLE},
|
||||
bugs = {"b/30424901: won't fix, all BC ECIES modes are banned"}
|
||||
)
|
||||
@Test
|
||||
public void testByteBuffer() throws Exception {
|
||||
ECGenParameterSpec ecSpec = new ECGenParameterSpec("secp256r1");
|
||||
// TODO(bleichen): Not sure what is better here:
|
||||
// BouncyCastle allows EC and ECIES. So far I can't see a difference.
|
||||
// In both cases the test is broken.
|
||||
// In both cases the test is broken for version 1.52.
|
||||
KeyPairGenerator kf = KeyPairGenerator.getInstance("ECIES");
|
||||
kf.initialize(ecSpec);
|
||||
KeyPair keyPair = kf.generateKeyPair();
|
||||
@@ -371,6 +337,7 @@ public class EciesTest {
|
||||
// Encryption
|
||||
Cipher cipher = Cipher.getInstance("ECIESwithAES-CBC");
|
||||
cipher.init(Cipher.ENCRYPT_MODE, pub);
|
||||
AlgorithmParameters params = cipher.getParameters();
|
||||
ByteBuffer ptBuffer = ByteBuffer.wrap(message);
|
||||
ByteBuffer ctBuffer = ByteBuffer.allocate(1024);
|
||||
cipher.doFinal(ptBuffer, ctBuffer);
|
||||
@@ -378,7 +345,7 @@ public class EciesTest {
|
||||
// Decryption
|
||||
ctBuffer.flip();
|
||||
ByteBuffer decrypted = ByteBuffer.allocate(message.length);
|
||||
cipher.init(Cipher.DECRYPT_MODE, priv);
|
||||
cipher.init(Cipher.DECRYPT_MODE, priv, params);
|
||||
cipher.doFinal(ctBuffer, decrypted);
|
||||
assertEquals(TestUtil.bytesToHex(message), TestUtil.bytesToHex(decrypted.array()));
|
||||
}
|
||||
@@ -389,10 +356,6 @@ public class EciesTest {
|
||||
*
|
||||
* <p>This test tries to verify this.
|
||||
*/
|
||||
@NoPresubmitTest(
|
||||
providers = {ProviderType.BOUNCY_CASTLE},
|
||||
bugs = {"b/30424901: won't fix, all BC ECIES modes are banned"}
|
||||
)
|
||||
@Test
|
||||
public void testByteBufferAlias() throws Exception {
|
||||
byte[] message = "Hello".getBytes("UTF-8");
|
||||
@@ -410,11 +373,12 @@ public class EciesTest {
|
||||
ptBuffer.flip();
|
||||
|
||||
ecies.init(Cipher.ENCRYPT_MODE, keyPair.getPublic());
|
||||
AlgorithmParameters params = ecies.getParameters();
|
||||
ByteBuffer ctBuffer = ByteBuffer.wrap(backingArray);
|
||||
ecies.doFinal(ptBuffer, ctBuffer);
|
||||
ctBuffer.flip();
|
||||
|
||||
ecies.init(Cipher.DECRYPT_MODE, keyPair.getPrivate());
|
||||
ecies.init(Cipher.DECRYPT_MODE, keyPair.getPrivate(), params);
|
||||
byte[] decrypted = ecies.doFinal(backingArray, 0, ctBuffer.remaining());
|
||||
assertEquals(TestUtil.bytesToHex(message), TestUtil.bytesToHex(decrypted));
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import java.security.GeneralSecurityException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.spec.GCMParameterSpec;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
@@ -58,9 +59,26 @@ public class JsonAeadTest {
|
||||
return res == 0;
|
||||
}
|
||||
|
||||
protected static void initCipher(Cipher cipher, int opmode, byte[] key, byte[] iv, int tagSize)
|
||||
/**
|
||||
* Returns an initialized instance of Cipher. Typically it is somewhat
|
||||
* time consuming to generate a new instance of Cipher for each encryption.
|
||||
* However, some implementations of ciphers (e.g. AES-GCM in jdk) check that
|
||||
* the same key and nonce are not reused twice in a row to catch simple
|
||||
* programming errors. This precaution can interfere with the tests, since
|
||||
* the test vectors do sometimes repeat nonces. To avoid such problems cipher
|
||||
* instances are not reused.
|
||||
* @param algorithm the cipher algorithm including encryption mode and padding.
|
||||
* @param opmode one of Cipher.ENCRYPT_MODE or Cipher.DECRYPT_MODE
|
||||
* @param key the key bytes
|
||||
* @param iv the bytes of the initialization vector
|
||||
* @param tagSize the expected size of the tag
|
||||
* @return an initialized instance of Cipher
|
||||
* @throws Exception if the initialization failed.
|
||||
*/
|
||||
protected static Cipher getInitializedCipher(
|
||||
String algorithm, int opmode, byte[] key, byte[] iv, int tagSize)
|
||||
throws Exception {
|
||||
String algorithm = cipher.getAlgorithm();
|
||||
Cipher cipher = Cipher.getInstance(algorithm);
|
||||
if (algorithm.equalsIgnoreCase("AES/GCM/NoPadding")) {
|
||||
SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
|
||||
GCMParameterSpec parameters = new GCMParameterSpec(tagSize, iv);
|
||||
@@ -73,9 +91,14 @@ public class JsonAeadTest {
|
||||
// we want to avoid.
|
||||
GCMParameterSpec parameters = new GCMParameterSpec(tagSize, iv);
|
||||
cipher.init(opmode, keySpec, parameters);
|
||||
} else if (algorithm.toUpperCase().startsWith("CHACHA")) {
|
||||
SecretKeySpec keySpec = new SecretKeySpec(key, "ChaCha20");
|
||||
IvParameterSpec parameters = new IvParameterSpec(iv);
|
||||
cipher.init(opmode, keySpec, parameters);
|
||||
} else {
|
||||
fail("Algorithm not supported:" + algorithm);
|
||||
}
|
||||
return cipher;
|
||||
}
|
||||
|
||||
/** Example format for test vectors
|
||||
@@ -115,8 +138,19 @@ public class JsonAeadTest {
|
||||
// Relevant version changes:
|
||||
// <ul>
|
||||
// <li> Version 0.5 adds test vectors for CCM.
|
||||
// <li> Version 0.6 adds test vectors for Chacha20-Poly1305.
|
||||
// Chacha20-Poly1305 is a new cipher added in jdk11.
|
||||
// </ul>
|
||||
final String expectedVersion = "0.5";
|
||||
final String expectedVersion = "0.6";
|
||||
|
||||
// Checking preconditions.
|
||||
try {
|
||||
Cipher.getInstance(algorithm);
|
||||
} catch (NoSuchAlgorithmException ex) {
|
||||
System.out.println("Algorithm is not supported. Skipping test for " + algorithm);
|
||||
return;
|
||||
}
|
||||
|
||||
JsonObject test = JsonUtil.getTestVectors(filename);
|
||||
String generatorVersion = test.get("generatorVersion").getAsString();
|
||||
if (!generatorVersion.equals(expectedVersion)) {
|
||||
@@ -130,13 +164,6 @@ public class JsonAeadTest {
|
||||
int numTests = test.get("numberOfTests").getAsInt();
|
||||
int cntTests = 0;
|
||||
int errors = 0;
|
||||
Cipher cipher;
|
||||
try {
|
||||
cipher = Cipher.getInstance(algorithm);
|
||||
} catch (NoSuchAlgorithmException ex) {
|
||||
System.out.println("Algorithm is not supported. Skipping test for " + algorithm);
|
||||
return;
|
||||
}
|
||||
for (JsonElement g : test.getAsJsonArray("testGroups")) {
|
||||
JsonObject group = g.getAsJsonObject();
|
||||
int tagSize = group.get("tagSize").getAsInt();
|
||||
@@ -157,8 +184,9 @@ public class JsonAeadTest {
|
||||
String result = testcase.get("result").getAsString();
|
||||
|
||||
// Test encryption
|
||||
Cipher cipher;
|
||||
try {
|
||||
initCipher(cipher, Cipher.ENCRYPT_MODE, key, iv, tagSize);
|
||||
cipher = getInitializedCipher(algorithm, Cipher.ENCRYPT_MODE, key, iv, tagSize);
|
||||
} catch (GeneralSecurityException ex) {
|
||||
// Some libraries restrict key size, iv size and tag size.
|
||||
// Because of the initialization of the cipher might fail.
|
||||
@@ -194,16 +222,17 @@ public class JsonAeadTest {
|
||||
}
|
||||
|
||||
// Test decryption
|
||||
Cipher decCipher;
|
||||
try {
|
||||
initCipher(cipher, Cipher.DECRYPT_MODE, key, iv, tagSize);
|
||||
decCipher = getInitializedCipher(algorithm, Cipher.DECRYPT_MODE, key, iv, tagSize);
|
||||
} catch (GeneralSecurityException ex) {
|
||||
System.out.println("Parameters accepted for encryption but not decryption " + tc);
|
||||
errors++;
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
cipher.updateAAD(aad);
|
||||
byte[] decrypted = cipher.doFinal(ciphertext);
|
||||
decCipher.updateAAD(aad);
|
||||
byte[] decrypted = decCipher.doFinal(ciphertext);
|
||||
boolean eq = arrayEquals(decrypted, msg);
|
||||
if (result.equals("invalid")) {
|
||||
System.out.println("Decrypted invalid ciphertext " + tc + " eq:" + eq);
|
||||
@@ -244,5 +273,39 @@ public class JsonAeadTest {
|
||||
public void testAesCcm() throws Exception {
|
||||
testAead("aes_ccm_test.json", "AES/CCM/NoPadding");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tests ChaCha20-Poly1305 defined in RFC 7539.
|
||||
*
|
||||
* <p>The algorithm name for ChaCha20-Poly1305 is not well defined:
|
||||
* jdk11 uses "ChaCha20-Poly1305".
|
||||
* ConsCrypt uses "ChaCha20/Poly1305/NoPadding".
|
||||
* These two implementations implement RFC 7539.
|
||||
*
|
||||
* <p>BouncyCastle has a cipher "ChaCha7539". This implementation
|
||||
* only implements ChaCha20 with a 12 byte IV. An implementation
|
||||
* of RFC 7539 is the class JceChaCha20Poly1305. It is unclear
|
||||
* whether this class can be accessed through the JCA interface.
|
||||
*/
|
||||
@NoPresubmitTest(
|
||||
providers = {ProviderType.BOUNCY_CASTLE},
|
||||
bugs = {"b/117642565"}
|
||||
)
|
||||
@Test
|
||||
public void testChaCha20Poly1305() throws Exception {
|
||||
// A list of potential algorithm names for ChaCha20-Poly1305.
|
||||
String[] algorithms =
|
||||
new String[]{"ChaCha20-Poly1305",
|
||||
"ChaCha20/Poly1305/NoPadding"};
|
||||
for (String name : algorithms) {
|
||||
try {
|
||||
Cipher.getInstance(name);
|
||||
} catch (NoSuchAlgorithmException ex) {
|
||||
continue;
|
||||
}
|
||||
testAead("chacha20_poly1305_test.json", name);
|
||||
return;
|
||||
}
|
||||
System.out.println("ChaCha20-Poly1305 is not supported: skipping test");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,10 +22,14 @@ import com.google.security.wycheproof.WycheproofRunner.ExcludedTest;
|
||||
import com.google.security.wycheproof.WycheproofRunner.NoPresubmitTest;
|
||||
import com.google.security.wycheproof.WycheproofRunner.ProviderType;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.Signature;
|
||||
import java.security.SignatureException;
|
||||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
@@ -53,30 +57,58 @@ public class JsonSignatureTest {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the algorithm name for a digital signature algorithm with a given message digest. The
|
||||
* algorithm names used in JCA are a bit inconsequential. E.g. a dash is necessary for message
|
||||
* digests (e.g. "SHA-256") but are not used in the corresponding names for digital signatures
|
||||
* (e.g. "SHA256WITHECDSA").
|
||||
* Returns an instance of java.security.Signature for an algorithm name, a digest name and
|
||||
* a signature format. The algorithm names used in JCA are a bit inconsequential. E.g. a dash
|
||||
* is necessary for message digests (e.g. "SHA-256") but are not used in the corresponding
|
||||
* names for digital signatures (e.g. "SHA256WITHECDSA"). Providers sometimes use distinct
|
||||
* algorithm names for the same cryptographic primitive.
|
||||
*
|
||||
* <p>See http://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html
|
||||
*
|
||||
* @param md the name of the message digest (e.g. "SHA-256")
|
||||
* @param signatureAlgorithm the name of the signature algorithm (e.g. "ECDSA")
|
||||
* @return the algorithm name for the signature scheme with the given hash.
|
||||
* @param signatureFormat the format of the signatures. This should be "P1363Format" for
|
||||
* P1363 encoded ECDSA signatures or "" otherwise.
|
||||
* @return an instance of java.security.Signature if the algorithm is known
|
||||
* @throws NoSuchAlgorithmException if the algorithm is not known
|
||||
*/
|
||||
protected static String getAlgorithmName(String md, String signatureAlgorithm) {
|
||||
if (md.equals("SHA-1")) {
|
||||
protected static Signature getSignatureInstance(
|
||||
String md, String signatureAlgorithm, String signatureFormat)
|
||||
throws NoSuchAlgorithmException {
|
||||
if (md.equalsIgnoreCase("SHA-1")) {
|
||||
md = "SHA1";
|
||||
} else if (md.equals("SHA-224")) {
|
||||
} else if (md.equalsIgnoreCase("SHA-224")) {
|
||||
md = "SHA224";
|
||||
} else if (md.equals("SHA-256")) {
|
||||
} else if (md.equalsIgnoreCase("SHA-256")) {
|
||||
md = "SHA256";
|
||||
} else if (md.equals("SHA-384")) {
|
||||
} else if (md.equalsIgnoreCase("SHA-384")) {
|
||||
md = "SHA384";
|
||||
} else if (md.equals("SHA-512")) {
|
||||
} else if (md.equalsIgnoreCase("SHA-512")) {
|
||||
md = "SHA512";
|
||||
}
|
||||
return md + "WITH" + signatureAlgorithm;
|
||||
if (signatureFormat.equalsIgnoreCase("P1363Format")
|
||||
&& signatureAlgorithm.equalsIgnoreCase("ECDSA")) {
|
||||
// The algorithm names for signature schemes with P1363 format have distinct names
|
||||
// in distinct providers. This is mainly the case since the P1363 format has only
|
||||
// been added in jdk11, while providers such as BouncyCastle added the format earlier
|
||||
// than that. Hence the code below just tries known algorithm names.
|
||||
try {
|
||||
String jdkName = md + "WITH" + signatureAlgorithm + "inP1363Format";
|
||||
return Signature.getInstance(jdkName);
|
||||
} catch (NoSuchAlgorithmException ex) {
|
||||
// jdkName is not known.
|
||||
}
|
||||
try {
|
||||
String bcName = md + "WITHPLAIN-" + signatureAlgorithm;
|
||||
return Signature.getInstance(bcName);
|
||||
} catch (NoSuchAlgorithmException ex) {
|
||||
// bcName is not known.
|
||||
}
|
||||
}
|
||||
if (!signatureFormat.isEmpty()) {
|
||||
throw new NoSuchAlgorithmException("Invalid signatureFormat:" + signatureFormat);
|
||||
}
|
||||
return Signature.getInstance(md + "WITH" + signatureAlgorithm);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -100,6 +132,18 @@ public class JsonSignatureTest {
|
||||
return kf.generatePublic(x509keySpec);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a PrivateKey from a JsonObject.
|
||||
*/
|
||||
// This is a false positive, since errorprone cannot track values passed into a method.
|
||||
@SuppressWarnings("InsecureCryptoUsage")
|
||||
protected static PrivateKey getPrivateKey(JsonObject object, String algorithm) throws Exception {
|
||||
KeyFactory kf = KeyFactory.getInstance(algorithm);
|
||||
byte[] encoded = TestUtil.hexToBytes(getString(object, "privateKeyPkcs8"));
|
||||
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encoded);
|
||||
return kf.generatePrivate(keySpec);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the signature verification with test vectors in a given JSON file.
|
||||
*
|
||||
@@ -132,12 +176,14 @@ public class JsonSignatureTest {
|
||||
*
|
||||
* @param filename the filename of the test vectors
|
||||
* @param signatureAlgorithm the algorithm name of the test vectors
|
||||
* @param signatureFormat the format of the signatures. This should be "P1363Format" for
|
||||
* P1363 encoded ECDSA signatures or "" otherwise.
|
||||
* @param allowSkippingKeys if true then keys that cannot be constructed will not fail the test.
|
||||
* This is for example used for files with test vectors that use elliptic curves that are not
|
||||
* commonly supported.
|
||||
**/
|
||||
public void testSignatureScheme(
|
||||
String filename, String signatureAlgorithm, boolean allowSkippingKeys)
|
||||
public void testVerification(
|
||||
String filename, String signatureAlgorithm, String signatureFormat, boolean allowSkippingKeys)
|
||||
throws Exception {
|
||||
// Testing with old test vectors may be a reason for a test failure.
|
||||
// Generally mismatched version numbers are of little or no concern, since
|
||||
@@ -148,7 +194,7 @@ public class JsonSignatureTest {
|
||||
// Versions after 1.0 change the major number if the format changes and change
|
||||
// the minor number if only the test vectors (but not the format) changes.
|
||||
// Subversions are release canddiate for the next version.
|
||||
final String expectedVersion = "0.5";
|
||||
final String expectedVersion = "0.6";
|
||||
JsonObject test = JsonUtil.getTestVectors(filename);
|
||||
String generatorVersion = getString(test, "generatorVersion");
|
||||
if (!generatorVersion.equals(expectedVersion)) {
|
||||
@@ -180,8 +226,13 @@ public class JsonSignatureTest {
|
||||
continue;
|
||||
}
|
||||
String md = getString(group, "sha");
|
||||
String algorithm = getAlgorithmName(md, signatureAlgorithm);
|
||||
Signature verifier = Signature.getInstance(algorithm);
|
||||
Signature verifier;
|
||||
try {
|
||||
verifier = getSignatureInstance(md, signatureAlgorithm, signatureFormat);
|
||||
} catch (NoSuchAlgorithmException ex) {
|
||||
skippedKeys++;
|
||||
continue;
|
||||
}
|
||||
for (JsonElement t : group.getAsJsonArray("tests")) {
|
||||
cntTests++;
|
||||
JsonObject testcase = t.getAsJsonObject();
|
||||
@@ -193,17 +244,20 @@ public class JsonSignatureTest {
|
||||
verifier.initVerify(key);
|
||||
verifier.update(message);
|
||||
boolean verified = false;
|
||||
Exception failure = null;
|
||||
try {
|
||||
verified = verifier.verify(signature);
|
||||
} catch (SignatureException ex) {
|
||||
// verify can throw SignatureExceptions if the signature is malformed.
|
||||
// We don't flag these cases and simply consider the signature as invalid.
|
||||
verified = false;
|
||||
failure = ex;
|
||||
} catch (java.lang.ArithmeticException ex) {
|
||||
// b/33446454 The Sun provider may throw an ArithmeticException instead of
|
||||
// the expected SignatureException for DSA signatures.
|
||||
// We should eventually remove this.
|
||||
verified = false;
|
||||
failure = ex;
|
||||
} catch (Exception ex) {
|
||||
// Other exceptions (i.e. unchecked exceptions) are considered as error
|
||||
// since a third party should never be able to cause such exceptions.
|
||||
@@ -218,9 +272,14 @@ public class JsonSignatureTest {
|
||||
+ " sig:"
|
||||
+ sig);
|
||||
verified = false;
|
||||
failure = ex;
|
||||
errors++;
|
||||
}
|
||||
if (!verified && result.equals("valid")) {
|
||||
String reason = "";
|
||||
if (failure != null) {
|
||||
reason = " reason:" + failure;
|
||||
}
|
||||
System.out.println(
|
||||
"Valid "
|
||||
+ signatureAlgorithm
|
||||
@@ -230,7 +289,8 @@ public class JsonSignatureTest {
|
||||
+ " tcId:"
|
||||
+ tcid
|
||||
+ " sig:"
|
||||
+ sig);
|
||||
+ sig
|
||||
+ reason);
|
||||
errors++;
|
||||
} else if (verified && result.equals("invalid")) {
|
||||
System.out.println(
|
||||
@@ -259,131 +319,316 @@ public class JsonSignatureTest {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests signature generation of deterministic signature schemes such as RSA-PKCS#1 v1.5.
|
||||
*
|
||||
* <p>The test expects that signatures are fully complying with the standards.
|
||||
* E.g. it is acceptable when RSA-PKCS#1 verification considers ASN encodings of the
|
||||
* digest name with a missing NULL value for legacy reasons. However, it is considered not
|
||||
* acceptable when the signature generation does not include the NULL value.
|
||||
*
|
||||
* @param filename the filename of the test vectors
|
||||
* @param signatureAlgorithm the algorithm name of the test vectors (e.g. "RSA")
|
||||
* @param signatureFormat the format of the signatures. This should be "P1363Format" for
|
||||
* P1363 encoded ECDSA signatures or "" otherwise.
|
||||
* @param allowSkippingKeys if true then keys that cannot be constructed will not fail the test.
|
||||
*/
|
||||
public void testSigning(
|
||||
String filename, String signatureAlgorithm, String signatureFormat,
|
||||
boolean allowSkippingKeys) throws Exception {
|
||||
final String expectedVersion = "0.6";
|
||||
JsonObject test = JsonUtil.getTestVectors(filename);
|
||||
String generatorVersion = getString(test, "generatorVersion");
|
||||
if (!generatorVersion.equals(expectedVersion)) {
|
||||
System.out.println(
|
||||
signatureAlgorithm
|
||||
+ ": expecting test vectors with version "
|
||||
+ expectedVersion
|
||||
+ " found vectors with version "
|
||||
+ generatorVersion);
|
||||
}
|
||||
int cntTests = 0;
|
||||
int errors = 0;
|
||||
int skippedKeys = 0;
|
||||
for (JsonElement g : test.getAsJsonArray("testGroups")) {
|
||||
JsonObject group = g.getAsJsonObject();
|
||||
PrivateKey key;
|
||||
try {
|
||||
key = getPrivateKey(group, signatureAlgorithm);
|
||||
} catch (GeneralSecurityException ex) {
|
||||
skippedKeys++;
|
||||
continue;
|
||||
}
|
||||
String md = getString(group, "sha");
|
||||
Signature signer;
|
||||
try {
|
||||
signer = getSignatureInstance(md, signatureAlgorithm, signatureFormat);
|
||||
} catch (NoSuchAlgorithmException ex) {
|
||||
skippedKeys++;
|
||||
continue;
|
||||
}
|
||||
for (JsonElement t : group.getAsJsonArray("tests")) {
|
||||
JsonObject testcase = t.getAsJsonObject();
|
||||
String result = getString(testcase, "result");
|
||||
byte[] message = getBytes(testcase, "msg");
|
||||
byte[] signature = getBytes(testcase, "sig");
|
||||
int tcid = testcase.get("tcId").getAsInt();
|
||||
String expectedSig = TestUtil.bytesToHex(signature);
|
||||
try {
|
||||
signer.initSign(key);
|
||||
signer.update(message);
|
||||
String sig = TestUtil.bytesToHex(signer.sign());
|
||||
if (!sig.equals(expectedSig)) {
|
||||
System.out.println(
|
||||
"Incorrect signature generated "
|
||||
+ filename
|
||||
+ " tcId:"
|
||||
+ tcid
|
||||
+ " expected:"
|
||||
+ expectedSig
|
||||
+ " sig:"
|
||||
+ sig);
|
||||
errors++;
|
||||
} else {
|
||||
cntTests++;
|
||||
}
|
||||
} catch (InvalidKeyException | SignatureException ex) {
|
||||
if (result.equals("valid")) {
|
||||
System.out.println(
|
||||
"Failed to sign "
|
||||
+ filename
|
||||
+ " tcId:"
|
||||
+ tcid
|
||||
+ " with exception:"
|
||||
+ ex);
|
||||
|
||||
errors++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
System.out.println("Number of skipped keys:" + skippedKeys);
|
||||
System.out.println("Number of signatures verified:" + cntTests);
|
||||
assertEquals(0, errors);
|
||||
if (!allowSkippingKeys) {
|
||||
assertEquals(0, skippedKeys);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEcdsa() throws Exception {
|
||||
testSignatureScheme("ecdsa_test.json", "ECDSA", true);
|
||||
testVerification("ecdsa_test.json", "ECDSA", "", true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSecp224r1Sha224() throws Exception {
|
||||
testSignatureScheme("ecdsa_secp224r1_sha224_test.json", "ECDSA", false);
|
||||
testVerification("ecdsa_secp224r1_sha224_test.json", "ECDSA", "", false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSecp224r1Sha256() throws Exception {
|
||||
testSignatureScheme("ecdsa_secp224r1_sha256_test.json", "ECDSA", false);
|
||||
testVerification("ecdsa_secp224r1_sha256_test.json", "ECDSA", "", false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSecp224r1Sha512() throws Exception {
|
||||
testSignatureScheme("ecdsa_secp224r1_sha512_test.json", "ECDSA", false);
|
||||
testVerification("ecdsa_secp224r1_sha512_test.json", "ECDSA", "", false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSecp256r1Sha256() throws Exception {
|
||||
testSignatureScheme("ecdsa_secp256r1_sha256_test.json", "ECDSA", false);
|
||||
testVerification("ecdsa_secp256r1_sha256_test.json", "ECDSA", "", false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSecp256r1Sha512() throws Exception {
|
||||
testSignatureScheme("ecdsa_secp256r1_sha512_test.json", "ECDSA", false);
|
||||
testVerification("ecdsa_secp256r1_sha512_test.json", "ECDSA", "", false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSecp384r1Sha384() throws Exception {
|
||||
testSignatureScheme("ecdsa_secp384r1_sha384_test.json", "ECDSA", false);
|
||||
testVerification("ecdsa_secp384r1_sha384_test.json", "ECDSA", "", false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSecp384r1Sha512() throws Exception {
|
||||
testSignatureScheme("ecdsa_secp384r1_sha512_test.json", "ECDSA", false);
|
||||
testVerification("ecdsa_secp384r1_sha512_test.json", "ECDSA", "", false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSecp521r1Sha512() throws Exception {
|
||||
testSignatureScheme("ecdsa_secp521r1_sha512_test.json", "ECDSA", false);
|
||||
testVerification("ecdsa_secp521r1_sha512_test.json", "ECDSA", "", false);
|
||||
}
|
||||
|
||||
// Testing curves that may not be supported by a provider.
|
||||
@Test
|
||||
public void testSecp256k1Sha256() throws Exception {
|
||||
testSignatureScheme("ecdsa_secp256k1_sha256_test.json", "ECDSA", true);
|
||||
testVerification("ecdsa_secp256k1_sha256_test.json", "ECDSA", "", true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSecp256k1Sha512() throws Exception {
|
||||
testSignatureScheme("ecdsa_secp256k1_sha512_test.json", "ECDSA", true);
|
||||
testVerification("ecdsa_secp256k1_sha512_test.json", "ECDSA", "", true);
|
||||
}
|
||||
|
||||
@NoPresubmitTest(
|
||||
providers = {ProviderType.OPENJDK},
|
||||
bugs = {"b/117643131"}
|
||||
)
|
||||
@Test
|
||||
public void testBrainpoolP224r1Sha224() throws Exception {
|
||||
testSignatureScheme("ecdsa_brainpoolP224r1_sha224_test.json", "ECDSA", true);
|
||||
testVerification("ecdsa_brainpoolP224r1_sha224_test.json", "ECDSA", "", true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBrainpoolP256r1Sha256() throws Exception {
|
||||
testSignatureScheme("ecdsa_brainpoolP256r1_sha256_test.json", "ECDSA", true);
|
||||
testVerification("ecdsa_brainpoolP256r1_sha256_test.json", "ECDSA", "", true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBrainpoolP320r1Sha384() throws Exception {
|
||||
testSignatureScheme("ecdsa_brainpoolP320r1_sha384_test.json", "ECDSA", true);
|
||||
testVerification("ecdsa_brainpoolP320r1_sha384_test.json", "ECDSA", "", true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBrainpoolP384r1Sha384() throws Exception {
|
||||
testSignatureScheme("ecdsa_brainpoolP384r1_sha384_test.json", "ECDSA", true);
|
||||
testVerification("ecdsa_brainpoolP384r1_sha384_test.json", "ECDSA", "", true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBrainpoolP512r1Sha512() throws Exception {
|
||||
testSignatureScheme("ecdsa_brainpoolP512r1_sha512_test.json", "ECDSA", true);
|
||||
testVerification("ecdsa_brainpoolP512r1_sha512_test.json", "ECDSA", "", true);
|
||||
}
|
||||
|
||||
// jdk11 adds P1363 encoded signatures.
|
||||
@Test
|
||||
public void testSecp224r1Sha224inP1363Format() throws Exception {
|
||||
testVerification("ecdsa_secp224r1_sha224_p1363_test.json", "ECDSA", "P1363Format", true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSecp224r1Sha256inP1363Format() throws Exception {
|
||||
testVerification("ecdsa_secp224r1_sha256_p1363_test.json", "ECDSA", "P1363Format", true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSecp224r1Sha512inP1363Format() throws Exception {
|
||||
testVerification("ecdsa_secp224r1_sha512_p1363_test.json", "ECDSA", "P1363Format", true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSecp256r1Sha256inP1363Format() throws Exception {
|
||||
testVerification("ecdsa_secp256r1_sha256_p1363_test.json", "ECDSA", "P1363Format", true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSecp256r1Sha512inP1363Format() throws Exception {
|
||||
testVerification("ecdsa_secp256r1_sha512_p1363_test.json", "ECDSA", "P1363Format", true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSecp384r1Sha384inP1363Format() throws Exception {
|
||||
testVerification("ecdsa_secp384r1_sha384_p1363_test.json", "ECDSA", "P1363Format", true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSecp384r1Sha512inP1363Format() throws Exception {
|
||||
testVerification("ecdsa_secp384r1_sha512_p1363_test.json", "ECDSA", "P1363Format", true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSecp521r1Sha512inP1363Format() throws Exception {
|
||||
testVerification("ecdsa_secp521r1_sha512_p1363_test.json", "ECDSA", "P1363Format", true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSecp256k1Sha256inP1363Format() throws Exception {
|
||||
testVerification("ecdsa_secp256k1_sha256_p1363_test.json", "ECDSA", "P1363Format", true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSecp256k1Sha512inP1363Format() throws Exception {
|
||||
testVerification("ecdsa_secp256k1_sha512_p1363_test.json", "ECDSA", "P1363Format", true);
|
||||
}
|
||||
|
||||
@NoPresubmitTest(
|
||||
providers = {ProviderType.OPENJDK},
|
||||
bugs = {"b/117643131"}
|
||||
)
|
||||
@Test
|
||||
public void testBrainpoolP224r1Sha224inP1363Format() throws Exception {
|
||||
testVerification("ecdsa_brainpoolP224r1_sha224_p1363_test.json", "ECDSA", "P1363Format", true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBrainpoolP256r1Sha256inP1363Format() throws Exception {
|
||||
testVerification("ecdsa_brainpoolP256r1_sha256_p1363_test.json", "ECDSA", "P1363Format", true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBrainpoolP320r1Sha384inP1363Format() throws Exception {
|
||||
testVerification("ecdsa_brainpoolP320r1_sha384_p1363_test.json", "ECDSA", "P1363Format", true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBrainpoolP384r1Sha384inP1363Format() throws Exception {
|
||||
testVerification("ecdsa_brainpoolP384r1_sha384_p1363_test.json", "ECDSA", "P1363Format", true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBrainpoolP512r1Sha512inP1363Format() throws Exception {
|
||||
testVerification("ecdsa_brainpoolP512r1_sha512_p1363_test.json", "ECDSA", "P1363Format", true);
|
||||
}
|
||||
|
||||
// Testing RSA PKCS#1 v1.5 signatures.
|
||||
@Test
|
||||
public void testRsaSigning() throws Exception {
|
||||
testSigning("rsa_sig_gen_misc_test.json", "RSA", "", false);
|
||||
}
|
||||
|
||||
// Testing RSA signatures.
|
||||
@Test
|
||||
public void testRsaSignatures() throws Exception {
|
||||
testSignatureScheme("rsa_signature_test.json", "RSA", false);
|
||||
testVerification("rsa_signature_test.json", "RSA", "", false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRsaSignature2048sha224() throws Exception {
|
||||
testSignatureScheme("rsa_signature_2048_sha224_test.json", "RSA", false);
|
||||
testVerification("rsa_signature_2048_sha224_test.json", "RSA", "", false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRsaSignatures2048sha256() throws Exception {
|
||||
testSignatureScheme("rsa_signature_2048_sha256_test.json", "RSA", false);
|
||||
testVerification("rsa_signature_2048_sha256_test.json", "RSA", "", false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRsaSignatures2048sha512() throws Exception {
|
||||
testSignatureScheme("rsa_signature_2048_sha512_test.json", "RSA", false);
|
||||
testVerification("rsa_signature_2048_sha512_test.json", "RSA", "", false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRsaSignatures3072sha256() throws Exception {
|
||||
testSignatureScheme("rsa_signature_3072_sha256_test.json", "RSA", false);
|
||||
testVerification("rsa_signature_3072_sha256_test.json", "RSA", "", false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRsaSignatures3072sha384() throws Exception {
|
||||
testSignatureScheme("rsa_signature_3072_sha384_test.json", "RSA", false);
|
||||
testVerification("rsa_signature_3072_sha384_test.json", "RSA", "", false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRsaSignatures3072sha512() throws Exception {
|
||||
testSignatureScheme("rsa_signature_3072_sha512_test.json", "RSA", false);
|
||||
testVerification("rsa_signature_3072_sha512_test.json", "RSA", "", false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRsaSignatures4096sha384() throws Exception {
|
||||
testSignatureScheme("rsa_signature_4096_sha384_test.json", "RSA", false);
|
||||
testVerification("rsa_signature_4096_sha384_test.json", "RSA", "", false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRsaSignatures4096sha512() throws Exception {
|
||||
testSignatureScheme("rsa_signature_4096_sha512_test.json", "RSA", false);
|
||||
testVerification("rsa_signature_4096_sha512_test.json", "RSA", "", false);
|
||||
}
|
||||
|
||||
// Testing DSA signatures.
|
||||
@@ -396,7 +641,7 @@ public class JsonSignatureTest {
|
||||
comment = "Conscrypt does not support DSA.")
|
||||
@Test
|
||||
public void testDsa() throws Exception {
|
||||
testSignatureScheme("dsa_test.json", "DSA", false);
|
||||
testVerification("dsa_test.json", "DSA", "", false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,182 @@
|
||||
/**
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.google.security.wycheproof;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
import javax.crypto.KeyAgreement;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
/**
|
||||
* This test uses test vectors in JSON format to check implementations of XDH.
|
||||
*
|
||||
* XDH is a Diffie-Hellman key agreement scheme.
|
||||
* It has been added to jdk11 (http://openjdk.java.net/jeps/324)
|
||||
*/
|
||||
@RunWith(JUnit4.class)
|
||||
public class JsonXdhTest {
|
||||
|
||||
/** Convenience mehtod to get a String from a JsonObject */
|
||||
protected static String getString(JsonObject object, String name) throws Exception {
|
||||
return object.get(name).getAsString();
|
||||
}
|
||||
|
||||
/** Convenience method to get a byte array from a JsonObject */
|
||||
protected static byte[] getBytes(JsonObject object, String name) throws Exception {
|
||||
return JsonUtil.asByteArray(object.get(name));
|
||||
}
|
||||
|
||||
/**
|
||||
* This test requires test vectors where the public key is X.509 encoded and
|
||||
* the private key is PKCS #8 encoded. This is different than the ECDH tests where
|
||||
* private key in the test vectors is an integer.
|
||||
*
|
||||
* The main reason for using this encoding is that the tests below can be implemented
|
||||
* without using new interfaces such as XECPublicKey, XECPrivateKey, so that the
|
||||
* code still compiles on older jdk versions and tests are simply skipped.
|
||||
*
|
||||
* Example for test vector
|
||||
* {
|
||||
* "algorithm" : "XDH",
|
||||
* "generatorVersion" : "0.6",
|
||||
* "numberOfTests" : 32,
|
||||
* "header" : [],
|
||||
* "testGroups" : [
|
||||
* {
|
||||
* "curve" : "curve25519",
|
||||
* "type" : "XDHComp",
|
||||
* "tests" : [
|
||||
* {
|
||||
* "tcId" : 1,
|
||||
* "comment" : "normal case",
|
||||
* "public" : "302c300706032b...",
|
||||
* "private" : "302e0201003007...",
|
||||
* "shared" : "87b7f212b627f7...",
|
||||
* "result" : "valid",
|
||||
* "flags" : []
|
||||
* },
|
||||
* ...
|
||||
**/
|
||||
public void testXdhComp(String filename) throws Exception {
|
||||
// Checks the precondition for this test.
|
||||
// XDH has been added in jdk11.
|
||||
try {
|
||||
KeyAgreement.getInstance("XDH");
|
||||
} catch (NoSuchAlgorithmException ex) {
|
||||
System.out.println("XDH is not supported: skipping test");
|
||||
return;
|
||||
}
|
||||
|
||||
final String expectedVersion = "0.6";
|
||||
JsonObject test = JsonUtil.getTestVectors(filename);
|
||||
String generatorVersion = test.get("generatorVersion").getAsString();
|
||||
if (!generatorVersion.equals(expectedVersion)) {
|
||||
System.out.println(
|
||||
"XDH: expecting test vectors with version "
|
||||
+ expectedVersion
|
||||
+ " found vectors with version "
|
||||
+ generatorVersion);
|
||||
}
|
||||
int numTests = test.get("numberOfTests").getAsInt();
|
||||
int passedTests = 0;
|
||||
int rejectedTests = 0; // invalid test vectors leading to exceptions
|
||||
int skippedTests = 0; // valid test vectors leading to exceptions
|
||||
int errors = 0;
|
||||
for (JsonElement g : test.getAsJsonArray("testGroups")) {
|
||||
JsonObject group = g.getAsJsonObject();
|
||||
for (JsonElement t : group.getAsJsonArray("tests")) {
|
||||
JsonObject testcase = t.getAsJsonObject();
|
||||
int tcid = testcase.get("tcId").getAsInt();
|
||||
String comment = getString(testcase, "comment");
|
||||
byte[] priv = getBytes(testcase, "private");
|
||||
byte[] publicEncoded = getBytes(testcase, "public");
|
||||
String result = getString(testcase, "result");
|
||||
String expectedHex = getString(testcase, "shared");
|
||||
KeyFactory kf = KeyFactory.getInstance("XDH");
|
||||
try {
|
||||
PKCS8EncodedKeySpec privKeySpec = new PKCS8EncodedKeySpec(priv);
|
||||
PrivateKey privKey = kf.generatePrivate(privKeySpec);
|
||||
X509EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(publicEncoded);
|
||||
PublicKey pubKey = kf.generatePublic(pubKeySpec);
|
||||
KeyAgreement ka = KeyAgreement.getInstance("XDH");
|
||||
ka.init(privKey);
|
||||
ka.doPhase(pubKey, true);
|
||||
String sharedHex = TestUtil.bytesToHex(ka.generateSecret());
|
||||
if (result.equals("invalid")) {
|
||||
System.out.println(
|
||||
"Computed XDH with invalid parameters"
|
||||
+ " tcId:"
|
||||
+ tcid
|
||||
+ " comment:"
|
||||
+ comment
|
||||
+ " shared:"
|
||||
+ sharedHex);
|
||||
errors++;
|
||||
} else if (!expectedHex.equals(sharedHex)) {
|
||||
System.out.println(
|
||||
"Incorrect XDH computation"
|
||||
+ " tcId:"
|
||||
+ tcid
|
||||
+ " comment:"
|
||||
+ comment
|
||||
+ "\nshared:"
|
||||
+ sharedHex
|
||||
+ "\nexpected:"
|
||||
+ expectedHex);
|
||||
errors++;
|
||||
} else {
|
||||
passedTests++;
|
||||
}
|
||||
} catch (InvalidKeySpecException | InvalidKeyException | NoSuchAlgorithmException ex) {
|
||||
// These are the exception that we expect to see when a curve is not implemented
|
||||
// or when a key is not valid.
|
||||
if (result.equals("valid")) {
|
||||
skippedTests++;
|
||||
} else {
|
||||
rejectedTests++;
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
// Other exceptions typically indicate that something is wrong with the implementation.
|
||||
System.out.println(
|
||||
"Test vector with tcId:" + tcid + " comment:" + comment + " throws:" + ex.toString());
|
||||
errors++;
|
||||
}
|
||||
}
|
||||
}
|
||||
assertEquals(0, errors);
|
||||
assertEquals(numTests, passedTests + rejectedTests + skippedTests);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testX25519() throws Exception {
|
||||
testXdhComp("x25519_asn_test.json");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testX448() throws Exception {
|
||||
testXdhComp("x448_asn_test.json");
|
||||
}
|
||||
}
|
||||
@@ -153,7 +153,7 @@ public class RsaEncryptionTest {
|
||||
public void testGetExceptionsOAEP() throws Exception {
|
||||
testExceptions("RSA/ECB/OAEPWITHSHA-1ANDMGF1PADDING");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generates PKCS#1 invalid vectors
|
||||
*
|
||||
@@ -179,13 +179,22 @@ public class RsaEncryptionTest {
|
||||
plainPaddedKeys[8] = getPaddedKey(rsaKeyLength, 16);
|
||||
plainPaddedKeys[9] = getPaddedKey(rsaKeyLength, 96);
|
||||
// the decrypted padded plaintext is shorter than RSA key
|
||||
plainPaddedKeys[10] = getPaddedKey(rsaKeyLength - 1, 16);
|
||||
plainPaddedKeys[11] = getPaddedKey(rsaKeyLength - 2, 16);
|
||||
plainPaddedKeys[10] = zeroPad(getPaddedKey(rsaKeyLength - 1, 16), rsaKeyLength);
|
||||
plainPaddedKeys[11] = zeroPad(getPaddedKey(rsaKeyLength - 2, 16), rsaKeyLength);
|
||||
// just 0x00 bytes
|
||||
plainPaddedKeys[12] = new byte[rsaKeyLength];
|
||||
return plainPaddedKeys;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepends data with some zeros, so that the result is keyLength bytes long.
|
||||
*/
|
||||
private byte[] zeroPad(byte[] data, int keyLength) {
|
||||
byte[] padded = new byte[keyLength];
|
||||
System.arraycopy(data, 0, padded, keyLength - data.length, data.length);
|
||||
return padded;
|
||||
}
|
||||
|
||||
private byte[] getPaddedKey(int rsaKeyLength, int symmetricKeyLength) {
|
||||
byte[] key = new byte[rsaKeyLength];
|
||||
// fill all the bytes with non-zero values
|
||||
|
||||
@@ -0,0 +1,394 @@
|
||||
/**
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.google.security.wycheproof;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import java.security.AlgorithmParameters;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.spec.AlgorithmParameterSpec;
|
||||
import java.security.spec.MGF1ParameterSpec;
|
||||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.spec.OAEPParameterSpec;
|
||||
import javax.crypto.spec.PSource;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
/**
|
||||
* Checks implementations of RSA-OAEP.
|
||||
*/
|
||||
@RunWith(JUnit4.class)
|
||||
public class RsaOaepTest {
|
||||
|
||||
/**
|
||||
* A list of algorithm names for RSA-OAEP.
|
||||
*
|
||||
* The standard algorithm names for RSA-OAEP are defined in
|
||||
* https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html
|
||||
*/
|
||||
static String[] OaepAlgorithmNames = {
|
||||
"RSA/None/OAEPPadding",
|
||||
"RSA/None/OAEPwithSHA-1andMGF1Padding",
|
||||
"RSA/None/OAEPwithSHA-224andMGF1Padding",
|
||||
"RSA/None/OAEPwithSHA-256andMGF1Padding",
|
||||
"RSA/None/OAEPwithSHA-384andMGF1Padding",
|
||||
"RSA/None/OAEPwithSHA-512andMGF1Padding",
|
||||
};
|
||||
|
||||
protected static void printParameters(AlgorithmParameterSpec params) {
|
||||
if (params instanceof OAEPParameterSpec) {
|
||||
OAEPParameterSpec oaepParams = (OAEPParameterSpec) params;
|
||||
System.out.println("OAEPParameterSpec");
|
||||
System.out.println("digestAlgorithm:" + oaepParams.getDigestAlgorithm());
|
||||
System.out.println("mgfAlgorithm:" + oaepParams.getMGFAlgorithm());
|
||||
printParameters(oaepParams.getMGFParameters());
|
||||
} else if (params instanceof MGF1ParameterSpec) {
|
||||
MGF1ParameterSpec mgf1Params = (MGF1ParameterSpec) params;
|
||||
System.out.println("MGF1ParameterSpec");
|
||||
System.out.println("digestAlgorithm:" + mgf1Params.getDigestAlgorithm());
|
||||
} else {
|
||||
System.out.println(params.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is not a real test. The JCE algorithm names only specify one hash algorithm. But OAEP
|
||||
* uses two hases. One hash algorithm is used to hash the labels. The other hash algorithm is
|
||||
* used for the mask generation function.
|
||||
*
|
||||
* <p>Different provider use different default values for the hash function that is not specified
|
||||
* in the algorithm name. Jdk uses mgfsha1 as default. BouncyCastle and Conscrypt use the same
|
||||
* hash for labels and mgf. Every provider allows to specify all the parameters using
|
||||
* an OAEPParameterSpec instance.
|
||||
*
|
||||
* <p>This test simply tries a number of algorithm names for RSA-OAEP and prints the OAEP
|
||||
* parameters for the case where no OAEPParameterSpec is used.
|
||||
*/
|
||||
// TODO(bleichen): jdk11 will also add parameters to the RSA keys. This will need more tests.
|
||||
@Test
|
||||
public void testDefaults() throws Exception {
|
||||
String pubKey =
|
||||
"30820122300d06092a864886f70d01010105000382010f003082010a02820101"
|
||||
+ "00bdf90898577911c71c4d9520c5f75108548e8dfd389afdbf9c997769b8594e"
|
||||
+ "7dc51c6a1b88d1670ec4bb03fa550ba6a13d02c430bfe88ae4e2075163017f4d"
|
||||
+ "8926ce2e46e068e88962f38112fc2dbd033e84e648d4a816c0f5bd89cadba0b4"
|
||||
+ "d6cac01832103061cbb704ebacd895def6cff9d988c5395f2169a6807207333d"
|
||||
+ "569150d7f569f7ebf4718ddbfa2cdbde4d82a9d5d8caeb467f71bfc0099b0625"
|
||||
+ "a59d2bad12e3ff48f2fd50867b89f5f876ce6c126ced25f28b1996ee21142235"
|
||||
+ "fb3aef9fe58d9e4ef6e4922711a3bbcd8adcfe868481fd1aa9c13e5c658f5172"
|
||||
+ "617204314665092b4d8dca1b05dc7f4ecd7578b61edeb949275be8751a5a1fab"
|
||||
+ "c30203010001";
|
||||
KeyFactory kf;
|
||||
kf = KeyFactory.getInstance("RSA");
|
||||
X509EncodedKeySpec x509keySpec = new X509EncodedKeySpec(TestUtil.hexToBytes(pubKey));
|
||||
PublicKey key = kf.generatePublic(x509keySpec);
|
||||
for (String oaepName : OaepAlgorithmNames) {
|
||||
try {
|
||||
Cipher c = Cipher.getInstance(oaepName);
|
||||
c.init(Cipher.ENCRYPT_MODE, key);
|
||||
System.out.println("Algorithm " + oaepName + " uses the following defaults");
|
||||
AlgorithmParameters params = c.getParameters();
|
||||
printParameters(params.getParameterSpec(OAEPParameterSpec.class));
|
||||
} catch (NoSuchAlgorithmException ex) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Convenience mehtod to get a String from a JsonObject */
|
||||
protected static String getString(JsonObject object, String name) throws Exception {
|
||||
return object.get(name).getAsString();
|
||||
}
|
||||
|
||||
/** Convenience method to get a byte array from a JsonObject */
|
||||
protected static byte[] getBytes(JsonObject object, String name) throws Exception {
|
||||
return JsonUtil.asByteArray(object.get(name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a PublicKey from a JsonObject.
|
||||
*
|
||||
* <p>object contains the key in multiple formats: "key" : elements of the public key "keyDer":
|
||||
* the key in ASN encoding encoded hexadecimal "keyPem": the key in Pem format encoded hexadecimal
|
||||
* The test can use the format that is most convenient.
|
||||
*/
|
||||
// This is a false positive, since errorprone cannot track values passed into a method.
|
||||
@SuppressWarnings("InsecureCryptoUsage")
|
||||
protected static PrivateKey getPrivateKey(JsonObject object) throws Exception {
|
||||
KeyFactory kf;
|
||||
kf = KeyFactory.getInstance("RSA");
|
||||
byte[] encoded = TestUtil.hexToBytes(getString(object, "privateKeyPkcs8"));
|
||||
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encoded);
|
||||
return kf.generatePrivate(keySpec);
|
||||
}
|
||||
|
||||
protected static String getOaepAlgorithmName(JsonObject group) throws Exception {
|
||||
String mgf = getString(group, "mgf");
|
||||
String mgfSha = getString(group, "mgfSha");
|
||||
return "RSA/ECB/OAEPwith" + mgfSha + "and" + mgf + "Padding";
|
||||
}
|
||||
|
||||
protected static OAEPParameterSpec getOaepParameters(JsonObject group,
|
||||
JsonObject test) throws Exception {
|
||||
String sha = getString(group, "sha");
|
||||
String mgf = getString(group, "mgf");
|
||||
String mgfSha = getString(group, "mgfSha");
|
||||
PSource p = PSource.PSpecified.DEFAULT;
|
||||
if (test.has("label")) {
|
||||
p = new PSource.PSpecified(getBytes(test, "label"));
|
||||
}
|
||||
return new OAEPParameterSpec(sha, mgf, new MGF1ParameterSpec(mgfSha), p);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the signature verification with test vectors in a given JSON file.
|
||||
*
|
||||
* <p> Example format for test vectors
|
||||
* { "algorithm" : "RSA-OAEP",
|
||||
* "generatorVersion" : "0.5",
|
||||
* ...
|
||||
* "testGroups" : [
|
||||
* {
|
||||
* "d" : "...",
|
||||
* "e" : "10001",
|
||||
* "n" : "...",
|
||||
* "keysize" : 2048,
|
||||
* "sha" : "SHA-256",
|
||||
* "mgf" : "MGF1",
|
||||
* "mgfSha" : "SHA-256",
|
||||
* "privateKeyPem" : "-----BEGIN RSA PRIVATE KEY-----\n...",
|
||||
* "privateKeyPkcs8" : "...",
|
||||
* "type" : "RSAES",
|
||||
* "tests" : [
|
||||
* {
|
||||
* "tcId" : 1,
|
||||
* "comment" : "",
|
||||
* "msg" : "30313233343030",
|
||||
* "ct" : "...",
|
||||
* "label" : "",
|
||||
* "result" : "valid",
|
||||
* "flags" : [],
|
||||
* },
|
||||
* ...
|
||||
*
|
||||
* @param filename the filename of the test vectors
|
||||
* @param allowSkippingKeys if true then keys that cannot be constructed will not fail the test.
|
||||
* Most of the tests below are using allowSkippingKeys == false. The reason for doing this
|
||||
* is that providers have distinctive defaults. E.g., no OAEPParameterSpec is given then
|
||||
* BouncyCastle and Conscrypt use the same hash function for hashing the label and for the
|
||||
* mask generation function, while jdk uses MGF1SHA1. This is unfortunate and probably
|
||||
* difficult to fix. Hence, the tests below simply require that providers support each
|
||||
* others default parameters under the assumption that the OAEPParameterSpec is fully
|
||||
* specified.
|
||||
**/
|
||||
public void testOaep(String filename, boolean allowSkippingKeys)
|
||||
throws Exception {
|
||||
// Testing with old test vectors may be a reason for a test failure.
|
||||
// Generally mismatched version numbers are of little or no concern, since
|
||||
// the test vector version change much more frequently than the format.
|
||||
//
|
||||
// Version numbers have the format major.minor[status].
|
||||
// Versions before 1.0 are experimental and use formats that are expected to change.
|
||||
// Versions after 1.0 change the major number if the format changes and change
|
||||
// the minor number if only the test vectors (but not the format) changes.
|
||||
// Versions meant for distribution have no status.
|
||||
final String expectedVersion = "0.5";
|
||||
JsonObject test = JsonUtil.getTestVectors(filename);
|
||||
String generatorVersion = getString(test, "generatorVersion");
|
||||
if (!generatorVersion.equals(expectedVersion)) {
|
||||
System.out.printf("Expecting test vectors with version %s found version %s\n",
|
||||
expectedVersion, generatorVersion);
|
||||
}
|
||||
int numTests = test.get("numberOfTests").getAsInt();
|
||||
int cntTests = 0;
|
||||
int errors = 0;
|
||||
int skippedKeys = 0;
|
||||
Set<String> skippedGroups = new HashSet<String>();
|
||||
for (JsonElement g : test.getAsJsonArray("testGroups")) {
|
||||
JsonObject group = g.getAsJsonObject();
|
||||
PrivateKey key;
|
||||
try {
|
||||
key = getPrivateKey(group);
|
||||
} catch (GeneralSecurityException ex) {
|
||||
skippedKeys++;
|
||||
if (!allowSkippingKeys) {
|
||||
System.out.printf("Key generation throws:%s\n", ex.toString());
|
||||
}
|
||||
continue;
|
||||
}
|
||||
String algorithm = getOaepAlgorithmName(group);
|
||||
Cipher decrypter = Cipher.getInstance(algorithm);
|
||||
for (JsonElement t : group.getAsJsonArray("tests")) {
|
||||
cntTests++;
|
||||
JsonObject testcase = t.getAsJsonObject();
|
||||
int tcid = testcase.get("tcId").getAsInt();
|
||||
String messageHex = TestUtil.bytesToHex(getBytes(testcase, "msg"));
|
||||
OAEPParameterSpec params = getOaepParameters(group, testcase);
|
||||
byte[] ciphertext = getBytes(testcase, "ct");
|
||||
String ciphertextHex = TestUtil.bytesToHex(ciphertext);
|
||||
String result = getString(testcase, "result");
|
||||
decrypter.init(Cipher.DECRYPT_MODE, key, params);
|
||||
byte[] decrypted = null;
|
||||
try {
|
||||
decrypted = decrypter.doFinal(ciphertext);
|
||||
} catch (GeneralSecurityException ex) {
|
||||
decrypted = null;
|
||||
} catch (Exception ex) {
|
||||
// Other exceptions (i.e. unchecked exceptions) are considered as error
|
||||
// since a third party should never be able to cause such exceptions.
|
||||
System.out.printf("Decryption throws %s. filename:%s tcId:%d ct:%s\n",
|
||||
ex.toString(), filename, tcid, ciphertextHex);
|
||||
decrypted = null;
|
||||
// TODO(bleichen): BouncyCastle throws some non-conforming exceptions.
|
||||
// For the moment we do not count this as a problem to avoid that
|
||||
// more serious bugs remain hidden.
|
||||
// errors++;
|
||||
}
|
||||
if (decrypted == null && result.equals("valid")) {
|
||||
System.out.printf(
|
||||
"Valid ciphertext not decrypted. filename:%s tcId:%d ct:%s\n",
|
||||
filename, tcid, ciphertextHex);
|
||||
errors++;
|
||||
} else if (decrypted != null) {
|
||||
String decryptedHex = TestUtil.bytesToHex(decrypted);
|
||||
if (result.equals("invalid")) {
|
||||
System.out.printf(
|
||||
"Invalid ciphertext decrypted. filename:%s tcId:%d expected:%s decrypted:%s\n",
|
||||
filename, tcid, messageHex, decryptedHex);
|
||||
errors++;
|
||||
} else if (!decryptedHex.equals(messageHex)) {
|
||||
System.out.printf(
|
||||
"Incorrect decryption. filename:%s tcId:%d expected:%s decrypted:%s\n",
|
||||
filename, tcid, messageHex, decryptedHex);
|
||||
errors++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
System.out.println("OAEP: Number of skipped keys:" + skippedKeys);
|
||||
for (String s : skippedGroups) {
|
||||
System.out.println("Skipped groups where " + s);
|
||||
}
|
||||
assertEquals(0, errors);
|
||||
if (skippedKeys == 0) {
|
||||
assertEquals(numTests, cntTests);
|
||||
} else {
|
||||
assertTrue(allowSkippingKeys);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRsaOaep2048Sha1Mgf1Sha1() throws Exception {
|
||||
testOaep("rsa_oaep_2048_sha1_mgf1sha1_test.json", false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRsaOaep2048Sha224Mgf1Sha1() throws Exception {
|
||||
testOaep("rsa_oaep_2048_sha224_mgf1sha1_test.json", false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRsaOaep2048Sha224Mgf1Sha224() throws Exception {
|
||||
testOaep("rsa_oaep_2048_sha224_mgf1sha224_test.json", false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRsaOaep2048Sha256Mgf1Sha1() throws Exception {
|
||||
testOaep("rsa_oaep_2048_sha256_mgf1sha1_test.json", false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRsaOaep2048Sha256Mgf1Sha256() throws Exception {
|
||||
testOaep("rsa_oaep_2048_sha256_mgf1sha256_test.json", false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRsaOaep2048Sha384Mgf1Sha1() throws Exception {
|
||||
testOaep("rsa_oaep_2048_sha384_mgf1sha1_test.json", false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRsaOaep2048Sha384Mgf1Sha384() throws Exception {
|
||||
testOaep("rsa_oaep_2048_sha384_mgf1sha384_test.json", false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRsaOaep2048Sha512Mgf1Sha1() throws Exception {
|
||||
testOaep("rsa_oaep_2048_sha512_mgf1sha1_test.json", false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRsaOaep2048Sha512Mgf1Sha512() throws Exception {
|
||||
testOaep("rsa_oaep_2048_sha512_mgf1sha512_test.json", false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRsaOaep3072Sha256Mgf1Sha1() throws Exception {
|
||||
testOaep("rsa_oaep_3072_sha256_mgf1sha1_test.json", false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRsaOaep3072Sha256Mgf1Sha256() throws Exception {
|
||||
testOaep("rsa_oaep_3072_sha256_mgf1sha256_test.json", false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRsaOaep3072Sha512Mgf1Sha1() throws Exception {
|
||||
testOaep("rsa_oaep_3072_sha512_mgf1sha1_test.json", false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRsaOaep3072Sha512Mgf1Sha512() throws Exception {
|
||||
testOaep("rsa_oaep_3072_sha512_mgf1sha512_test.json", false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRsaOaep4096Sha256Mgf1Sha1() throws Exception {
|
||||
testOaep("rsa_oaep_4096_sha256_mgf1sha1_test.json", false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRsaOaep4096Sha256Mgf1Sha256() throws Exception {
|
||||
testOaep("rsa_oaep_4096_sha256_mgf1sha256_test.json", false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRsaOaep4096Sha512Mgf1Sha1() throws Exception {
|
||||
testOaep("rsa_oaep_4096_sha512_mgf1sha1_test.json", false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRsaOaep4096Sha512Mgf1Sha512() throws Exception {
|
||||
testOaep("rsa_oaep_4096_sha512_mgf1sha512_test.json", false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRsaOaepMisc() throws Exception {
|
||||
testOaep("rsa_oaep_misc_test.json", false);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -84,6 +84,11 @@ public class RsaPssTest {
|
||||
return;
|
||||
}
|
||||
AlgorithmParameters params = verifier.getParameters();
|
||||
if (params == null) {
|
||||
// No defaults are specified. This is a good choice since this avoid
|
||||
// incompatible implementations.
|
||||
return;
|
||||
}
|
||||
PSSParameterSpec pssParams = params.getParameterSpec(PSSParameterSpec.class);
|
||||
assertEquals("digestAlgorithm", expectedHash, pssParams.getDigestAlgorithm());
|
||||
assertEquals("mgfAlgorithm", expectedMgf, pssParams.getMGFAlgorithm());
|
||||
@@ -109,9 +114,10 @@ public class RsaPssTest {
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* <p>The standard algorithm names for RSA-PSS are defined in the section "Signature Algorithms"
|
||||
* of https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html
|
||||
* I.e. the standard names have the format <digest>with<encryption>and<mgf>, e.g.,
|
||||
* <p>The algorithm name for RSA-PSS used in jdk11 is "RSASSA-PSS". Previously, the algorithm
|
||||
* names for RSA-PSS were defined in the section "Signature Algorithms" of
|
||||
* https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html
|
||||
* I.e. the proposed standard names had the format <digest>with<encryption>and<mgf>, e.g.,
|
||||
* SHA256withRSAandMGF1. This name only specifies the hashAlgorithm and the mask generation
|
||||
* algorithm, but not the hash used for the mask generation algorithm, the salt length and
|
||||
* the trailerField. The missing parameters can be explicitly specified with and instance
|
||||
@@ -136,6 +142,12 @@ public class RsaPssTest {
|
||||
testDefaultForAlgorithm("SHA3-256withRSAandMGF1", "SHA3-256", "MGF1", "SHA3-256", 32, 1);
|
||||
testDefaultForAlgorithm("SHA3-384withRSAandMGF1", "SHA3-384", "MGF1", "SHA3-384", 48, 1);
|
||||
testDefaultForAlgorithm("SHA3-512withRSAandMGF1", "SHA3-512", "MGF1", "SHA3-512", 64, 1);
|
||||
// TODO(bleichen): Should there be any expectations for RSASSA-PSS?
|
||||
// RSASSA-PSS does not specify any parameters. Using the default values from RFC 8017
|
||||
// (i.e. SHA-1 for both hashes) leads to potential weaknesses. jdk11 requires that
|
||||
// the parameters are always specified. BouncyCastle however uses the SHA-1 default.
|
||||
// Is this a bug?
|
||||
// testDefaultForAlgorithm("RSASSA-PSS", "SHA-256", "MGF1", "SHA-256", 32, 1);
|
||||
}
|
||||
|
||||
/** Convenience mehtod to get a String from a JsonObject */
|
||||
@@ -150,17 +162,24 @@ public class RsaPssTest {
|
||||
|
||||
/**
|
||||
* Returns the algorithm name for the RSA-PSS signature scheme.
|
||||
* algorithm names used in JCA are a bit inconsequential. E.g. a dash is necessary for message
|
||||
* digests (e.g. "SHA-256") but are not used in the corresponding names for digital signatures
|
||||
* (e.g. "SHA256WITHRSAandMGF1").
|
||||
*
|
||||
* <p>See http://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html
|
||||
* Oracle previously specified that algorithm names for RSA-PSS are strings like
|
||||
* "SHA256WITHRSAandMGF1".
|
||||
* See http://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html
|
||||
*
|
||||
* <p> But the implementation in jdk11 only support "RSASSA-PSS". This function simply attempts
|
||||
* to return an algorithm name that works.
|
||||
*
|
||||
* @param group A json dictionary containing a field "sha" with message digest (e.g. "SHA-256")
|
||||
* and the a field "mgf" for the mask generation function (e.g. "MGF1").
|
||||
* @return the algorithm name for the signature scheme with the given hash.
|
||||
* @return the algorithm name
|
||||
*/
|
||||
protected static String getAlgorithmName(JsonObject group) throws Exception {
|
||||
try {
|
||||
Signature.getInstance("RSASSA-PSS");
|
||||
return "RSASSA-PSS";
|
||||
} catch (NoSuchAlgorithmException ex) {
|
||||
// RSASSA-PSS is not known. Try the other option.
|
||||
}
|
||||
String md = getString(group, "sha");
|
||||
String mgf = getString(group, "mgf");
|
||||
if (md.equals("SHA-1")) {
|
||||
@@ -248,7 +267,7 @@ public class RsaPssTest {
|
||||
// Versions after 1.0 change the major number if the format changes and change
|
||||
// the minor number if only the test vectors (but not the format) changes.
|
||||
// Versions meant for distribution have no status.
|
||||
final String expectedVersion = "0.5";
|
||||
final String expectedVersion = "0.6";
|
||||
JsonObject test = JsonUtil.getTestVectors(filename);
|
||||
String generatorVersion = getString(test, "generatorVersion");
|
||||
if (!generatorVersion.equals(expectedVersion)) {
|
||||
@@ -278,7 +297,7 @@ public class RsaPssTest {
|
||||
skippedKeys++;
|
||||
skippedAlgorithms.add(algorithm);
|
||||
} else {
|
||||
System.out.println("Failed to generate verifier for " + algorithm);
|
||||
System.out.println("Failed to generate verifier for " + algorithm + ex);
|
||||
errors++;
|
||||
}
|
||||
continue;
|
||||
@@ -294,12 +313,14 @@ public class RsaPssTest {
|
||||
verifier.initVerify(key);
|
||||
verifier.update(message);
|
||||
boolean verified = false;
|
||||
Exception reason = null;
|
||||
try {
|
||||
verified = verifier.verify(signature);
|
||||
} catch (SignatureException ex) {
|
||||
// verify can throw SignatureExceptions if the signature is malformed.
|
||||
// We don't flag these cases and simply consider the signature as invalid.
|
||||
verified = false;
|
||||
reason = ex;
|
||||
} catch (Exception ex) {
|
||||
// Other exceptions (i.e. unchecked exceptions) are considered as error
|
||||
// since a third party should never be able to cause such exceptions.
|
||||
@@ -316,13 +337,18 @@ public class RsaPssTest {
|
||||
errors++;
|
||||
}
|
||||
if (!verified && result.equals("valid")) {
|
||||
String comment = "";
|
||||
if (reason != null) {
|
||||
comment = " exception:" + reason;
|
||||
}
|
||||
System.out.println(
|
||||
"Valid signature not verified. "
|
||||
+ filename
|
||||
+ " tcId:"
|
||||
+ tcid
|
||||
+ " sig:"
|
||||
+ sig);
|
||||
+ sig
|
||||
+ comment);
|
||||
errors++;
|
||||
} else if (verified && result.equals("invalid")) {
|
||||
System.out.println(
|
||||
@@ -350,7 +376,7 @@ public class RsaPssTest {
|
||||
|
||||
@Test
|
||||
public void testRsaPss2048Sha256() throws Exception {
|
||||
testRsaPss("rsa_pss_2048_sha256_mgf1_32_test.json", false);
|
||||
testRsaPss("rsa_pss_2048_sha256_mgf1_32_test.json", true);
|
||||
}
|
||||
|
||||
@NoPresubmitTest(
|
||||
@@ -359,34 +385,22 @@ public class RsaPssTest {
|
||||
)
|
||||
@Test
|
||||
public void testRsaPss3072Sha256() throws Exception {
|
||||
testRsaPss("rsa_pss_3072_sha256_mgf1_32_test.json", false);
|
||||
testRsaPss("rsa_pss_3072_sha256_mgf1_32_test.json", true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRsaPss4096Sha256() throws Exception {
|
||||
testRsaPss("rsa_pss_4096_sha256_mgf1_32_test.json", false);
|
||||
testRsaPss("rsa_pss_4096_sha256_mgf1_32_test.json", true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRsaPss4096Sha512() throws Exception {
|
||||
testRsaPss("rsa_pss_4096_sha512_mgf1_32_test.json", false);
|
||||
testRsaPss("rsa_pss_4096_sha512_mgf1_32_test.json", true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRsaPss2048Sha256NoSalt() throws Exception {
|
||||
testRsaPss("rsa_pss_2048_sha256_mgf1_0_test.json", false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks RSA-PSS with various combinations of hashes and salt lengths.
|
||||
* Some providers restrict the range of supported parameters.
|
||||
* E.g. BouncyCastle requires that the signature hash and the mgf hash
|
||||
* are the same. The test expects that unsupported combinations are
|
||||
* rejected during the initialization of the Signature instance.
|
||||
*/
|
||||
@Test
|
||||
public void testRsaPssMisc() throws Exception {
|
||||
testRsaPss("rsa_pss_misc_test.json", true);
|
||||
testRsaPss("rsa_pss_2048_sha256_mgf1_0_test.json", true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user