Wycheproof version 0.6

NOKEYCHECK=True
PiperOrigin-RevId: 222384581
Change-Id: I1749fc9e570634e5429c3162787de0539cbcfd4d
GitOrigin-RevId: 4f8bfc19eb425f6a1083637b2810002dc0ae5b41
This commit is contained in:
bleichen
2018-11-21 03:35:55 -08:00
committed by Tink Team
parent 9035918140
commit 95552fc2e2
117 changed files with 76902 additions and 3572 deletions
@@ -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);
}
}