Adding test vectors RSA primitives with truncated hashes:

SHA-512/224 and SHA-512/256.

I don't expect these hashes to be heavily used. But there is
some potential for incompatibilities. E.g. BouncyCastle
previously used algorithm names such as "SHA512(224)WITHRSA"
while jdk uses "SHA512/224WithRSA".

RSA-OAEP would in principle allow the same hashes as RSA-PSS,
but the algorithm name in JCA is not well-defined,
using truncated hashes with OAEP doesn't make much sense,
and providers don't seem to implement it.

Test results:
jdk11 supports the truncated signatures.
BouncyCastle does not support the algorithm names
ConsCrypt does not support the algorithms.

Tested in a different CL:
boringSSL does not implement the algorithms.

Bugs: b/120406853 is about an alternative ASN encoding
of PSS-keys. This is an encoding that contains all the
parameters of signature scheme. This will be added by
jdk11. But so far, jdk11 fails to read its own key encodings.
I have test vectors for this encoding, but can't add them
without having at least one library accepting them.
NOKEYCHECK=True
PiperOrigin-RevId: 275018282
GitOrigin-RevId: 42c8b6d8e768858b6ec5a273d53f1e111268ecd7
This commit is contained in:
bleichen
2019-10-16 06:25:50 -07:00
committed by Tink Team
parent e50b5bf1dd
commit 04b8adc7ba
9 changed files with 11277 additions and 26 deletions
@@ -85,6 +85,10 @@ public class JsonSignatureTest {
return "SHA384";
} else if (md.equalsIgnoreCase("SHA-512")) {
return "SHA512";
} else if (md.equalsIgnoreCase("SHA-512/224")) {
return "SHA512/224";
} else if (md.equalsIgnoreCase("SHA-512/256")) {
return "SHA512/256";
}
return md;
}
@@ -759,7 +763,7 @@ public class JsonSignatureTest {
// Testing RSA PKCS#1 v1.5 signatures.
@Test
public void testRsaSigning() throws Exception {
testSigning("rsa_sig_gen_misc_test.json", "RSA", Format.RAW, false);
testSigning("rsa_sig_gen_misc_test.json", "RSA", Format.RAW, true);
}
@Test
@@ -777,6 +781,11 @@ public class JsonSignatureTest {
testVerification("rsa_signature_2048_sha256_test.json", "RSA", Format.RAW, false);
}
@Test
public void testRsaSignatures2048sha384() throws Exception {
testVerification("rsa_signature_2048_sha384_test.json", "RSA", Format.RAW, false);
}
@Test
public void testRsaSignatures2048sha512() throws Exception {
testVerification("rsa_signature_2048_sha512_test.json", "RSA", Format.RAW, false);
@@ -807,6 +816,28 @@ public class JsonSignatureTest {
testVerification("rsa_signature_4096_sha512_test.json", "RSA", Format.RAW, false);
}
// RSA signatures with truncated hashes. Tests may be skipped if the provider
// does not support the hash.
@Test
public void testRsaSignatures2048sha512_224() throws Exception {
testVerification("rsa_signature_2048_sha512_224_test.json", "RSA", Format.RAW, true);
}
@Test
public void testRsaSignatures2048sha512_256() throws Exception {
testVerification("rsa_signature_2048_sha512_256_test.json", "RSA", Format.RAW, true);
}
@Test
public void testRsaSignatures3072sha512_256() throws Exception {
testVerification("rsa_signature_3072_sha512_256_test.json", "RSA", Format.RAW, true);
}
@Test
public void testRsaSignatures4096sha512_256() throws Exception {
testVerification("rsa_signature_4096_sha512_256_test.json", "RSA", Format.RAW, true);
}
// RSA signatures with SHA-3. Not every provider supports SHA-3. Hence the tests
// may be skipped.
@Test
@@ -20,15 +20,21 @@ import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.security.wycheproof.WycheproofRunner.NoPresubmitTest;
import com.google.security.wycheproof.WycheproofRunner.ProviderType;
import java.lang.reflect.Constructor;
import java.math.BigInteger;
import java.security.AlgorithmParameters;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.MGF1ParameterSpec;
import java.security.spec.PSSParameterSpec;
import java.security.spec.RSAKeyGenParameterSpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashSet;
import java.util.Set;
@@ -42,6 +48,96 @@ import org.junit.runners.JUnit4;
@RunWith(JUnit4.class)
public class RsaPssTest {
/**
* Returns an AlgorithmParameterSpec for generating a RSASSA-PSS key,
* which include the PSSParameters.
* Requires jdk11.
*
* @param keySizeInBits the size of the modulus in bits.
* @param sha the name of the hash function for hashing the input (e.g. "SHA-256")
* @param mgf the name of the mask generating function (typically "MGF1")
* @param mgfSha the name of the hash function for the mask generating function
* (typically the same as sha).
* @param saltLength the length of the salt in bytes (typically the digest size of sha,
* i.e. 32 for "SHA-256")
* @throws NoSuchMethodException if the AlgorithmParameterSpec is not
* supported (i.e. this happens before jdk11).
*/
public RSAKeyGenParameterSpec getPssAlgorithmParameters(
int keySizeInBits,
String sha,
String mgf,
String mgfSha,
int saltLength) throws Exception {
BigInteger publicExponent = new BigInteger("65537");
PSSParameterSpec params =
new PSSParameterSpec(sha, mgf, new MGF1ParameterSpec(mgfSha), saltLength, 1);
// Uses reflection to call
// public RSAKeyGenParameterSpec(int keysize, BigInteger publicExponent,
// AlgorithmParameterSpec keyParams)
// because this method is only supported in jdk11. This throws a NoSuchMethodException
// for older jdks.
Constructor<RSAKeyGenParameterSpec> c =
RSAKeyGenParameterSpec.class.getConstructor(
int.class, BigInteger.class, AlgorithmParameterSpec.class);
return c.newInstance(keySizeInBits, publicExponent, params);
}
/**
* Tries encoding and decoding of RSASSA-PSS keys generated with RSASSA-PSS.
*
* RSASSA-PSS keys contain the PSSParameters, hence their encodings are
* somewhat different than plain RSA keys.
*/
@NoPresubmitTest(
providers = {ProviderType.OPENJDK},
bugs = {"b/120406853"}
)
@Test
public void testEncodeDecodePublic() throws Exception {
int keySizeInBits = 2048;
PublicKey pub;
try {
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSASSA-PSS");
keyGen.initialize(keySizeInBits);
KeyPair keypair = keyGen.genKeyPair();
pub = keypair.getPublic();
} catch (NoSuchAlgorithmException ex) {
System.out.println("Key generation for RSASSA-PSS is not supported.");
return;
}
byte[] encoded = pub.getEncoded();
assertEquals(
"The test assumes that the public key is in X.509 format", "X.509", pub.getFormat());
System.out.println("Generated RSA-PSS key");
System.out.println(TestUtil.bytesToHex(encoded));
KeyFactory kf = KeyFactory.getInstance("RSASSA-PSS");
X509EncodedKeySpec spec = new X509EncodedKeySpec(encoded);
kf.generatePublic(spec);
// Tries to generate another pair or keys. This time the generator is given an
// RSAKeyGenParameterSpec containing the key size an the PSS parameters.
String sha = "SHA-256";
String mgf = "MGF1";
int saltLength = 20;
try {
RSAKeyGenParameterSpec params =
getPssAlgorithmParameters(keySizeInBits, sha, mgf, sha, saltLength);
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSASSA-PSS");
keyGen.initialize(params);
KeyPair keypair = keyGen.genKeyPair();
pub = keypair.getPublic();
} catch (NoSuchAlgorithmException | NoSuchMethodException ex) {
System.out.println("Key generation for RSASSA-PSS is not supported.");
return;
}
byte[] encoded2 = pub.getEncoded();
System.out.println("Generated RSA-PSS key with PSS parameters");
System.out.println(TestUtil.bytesToHex(encoded2));
X509EncodedKeySpec spec2 = new X509EncodedKeySpec(encoded2);
kf.generatePublic(spec2);
}
/**
* Tests the default parameters used for a given algorithm name.
*
@@ -128,6 +224,14 @@ public class RsaPssTest {
* are the same. It expects that the saltLength is the same as the size of the message digest.
* It expects that the default for the trailerField is 1. These expectations are based on
* existing implementations. They differ from the ASN defaults in RFC 8017.
*
* <p>There is no test for defaults for the algorithm name "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 and hence is of course a bad
* choice. Other defaults lead to incompatibilities and hence isn't a reasonable choice either.
* jdk11 requires that the parameters are always specified. BouncyCastle however uses the SHA-1
* default. The behaviour in jdk11 is preferable, since it requires that an implementor chooses
* PSSParameters explicitly, and does not default to weak behaviour.
*/
@Test
public void testDefaults() throws Exception {
@@ -136,18 +240,14 @@ public class RsaPssTest {
testDefaultForAlgorithm("SHA256withRSAandMGF1", "SHA-256", "MGF1", "SHA-256", 32, 1);
testDefaultForAlgorithm("SHA384withRSAandMGF1", "SHA-384", "MGF1", "SHA-384", 48, 1);
testDefaultForAlgorithm("SHA512withRSAandMGF1", "SHA-512", "MGF1", "SHA-512", 64, 1);
testDefaultForAlgorithm("SHA512/224withRSAandMGF1", "SHA-512", "MGF1", "SHA-512", 28, 1);
testDefaultForAlgorithm("SHA512/256withRSAandMGF1", "SHA-512", "MGF1", "SHA-512", 32, 1);
testDefaultForAlgorithm(
"SHA512/224withRSAandMGF1", "SHA-512/224", "MGF1", "SHA-512/224", 28, 1);
testDefaultForAlgorithm(
"SHA512/256withRSAandMGF1", "SHA-512/256", "MGF1", "SHA-512/256", 32, 1);
testDefaultForAlgorithm("SHA3-224withRSAandMGF1", "SHA3-224", "MGF1", "SHA3-224", 28, 1);
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 */
@@ -165,9 +265,9 @@ public class RsaPssTest {
* 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.
* These algorithm names fail to specify the hash function for the MGF. A cleaner solution
* in jdk11 is to use the algorithm name "RSASSA-PSS" and specify the parameters separately.
* 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").
@@ -192,6 +292,10 @@ public class RsaPssTest {
md = "SHA384";
} else if (md.equals("SHA-512")) {
md = "SHA512";
} else if (md.equals("SHA-512/224")) {
md = "SHA512/224";
} else if (md.equals("SHA-512/256")) {
md = "SHA512/256";
}
return md + "WITHRSAand" + mgf;
}
@@ -203,9 +307,14 @@ public class RsaPssTest {
* 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.
*/
protected static PublicKey getPublicKey(JsonObject object) throws Exception {
protected static PublicKey getPublicKey(JsonObject object, boolean pssParamsIncluded)
throws Exception {
KeyFactory kf;
kf = KeyFactory.getInstance("RSA");
if (pssParamsIncluded) {
kf = KeyFactory.getInstance("RSASSA-PSS");
} else {
kf = KeyFactory.getInstance("RSA");
}
byte[] encoded = TestUtil.hexToBytes(getString(object, "keyDer"));
X509EncodedKeySpec x509keySpec = new X509EncodedKeySpec(encoded);
return kf.generatePublic(x509keySpec);
@@ -255,8 +364,16 @@ public class RsaPssTest {
* @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.
* @param paramsIncluded if true then the enoding of the public key contains the PSS parameters.
* The algorithm parameters of PSS are defined in appendix A.2 of RFC 8017. One option is not
* to include the parameters and use { OID rsaEncryption PARAMETERS NULL } for the algorithm
* identifier. Another option is to include the parameters by using
* { OID id-RSASSA-PSS PARAMETERS RSASSA-PSS-params } as algorithm identifier.
* The second option requires that an RSAKey contains an AlgorithmParameterSpec. The
* AlgorithmParameterSpec is a recent addition made in jdk11. Hence many providers are
* currently not supporting this.
**/
public void testRsaPss(String filename, boolean allowSkippingKeys)
public void testRsaPss(String filename, boolean allowSkippingKeys, boolean paramsIncluded)
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
@@ -281,6 +398,7 @@ public class RsaPssTest {
int cntTests = 0;
int errors = 0;
int skippedKeys = 0;
int verifiedTests = 0;
Set<String> skippedAlgorithms = new HashSet<String>();
for (JsonElement g : test.getAsJsonArray("testGroups")) {
JsonObject group = g.getAsJsonObject();
@@ -288,10 +406,12 @@ public class RsaPssTest {
PublicKey key = null;
Signature verifier = null;
try {
key = getPublicKey(group);
PSSParameterSpec pssParams = getPSSParams(group);
key = getPublicKey(group, paramsIncluded);
verifier = Signature.getInstance(algorithm);
verifier.setParameter(pssParams);
if (!paramsIncluded) {
PSSParameterSpec pssParams = getPSSParams(group);
verifier.setParameter(pssParams);
}
} catch (GeneralSecurityException ex) {
if (allowSkippingKeys) {
skippedKeys++;
@@ -359,13 +479,27 @@ public class RsaPssTest {
+ " sig:"
+ sig);
errors++;
} else if (verified) {
verifiedTests++;
}
}
}
System.out.println("Number of skipped keys:" + skippedKeys);
for (String s : skippedAlgorithms) {
System.out.println("Skipped algorithms " + s);
// Prints some information if tests were skipped. This avoids giving
// the impression that algorithms are supported.
if (skippedKeys > 0 || verifiedTests == 0) {
System.out.println(
"File:"
+ filename
+ " number of skipped keys:"
+ skippedKeys
+ " verified signatures:"
+ verifiedTests);
for (String s : skippedAlgorithms) {
System.out.println("Skipped algorithms " + s);
}
}
assertEquals(0, errors);
if (skippedKeys == 0) {
assertEquals(numTests, cntTests);
@@ -376,7 +510,7 @@ public class RsaPssTest {
@Test
public void testRsaPss2048Sha256() throws Exception {
testRsaPss("rsa_pss_2048_sha256_mgf1_32_test.json", true);
testRsaPss("rsa_pss_2048_sha256_mgf1_32_test.json", true, false);
}
@NoPresubmitTest(
@@ -385,22 +519,50 @@ public class RsaPssTest {
)
@Test
public void testRsaPss3072Sha256() throws Exception {
testRsaPss("rsa_pss_3072_sha256_mgf1_32_test.json", true);
testRsaPss("rsa_pss_3072_sha256_mgf1_32_test.json", true, false);
}
@Test
public void testRsaPss4096Sha256() throws Exception {
testRsaPss("rsa_pss_4096_sha256_mgf1_32_test.json", true);
testRsaPss("rsa_pss_4096_sha256_mgf1_32_test.json", true, false);
}
@Test
public void testRsaPss4096Sha512() throws Exception {
testRsaPss("rsa_pss_4096_sha512_mgf1_32_test.json", true);
testRsaPss("rsa_pss_4096_sha512_mgf1_32_test.json", true, false);
}
@Test
public void testRsaPss2048Sha256NoSalt() throws Exception {
testRsaPss("rsa_pss_2048_sha256_mgf1_0_test.json", true);
testRsaPss("rsa_pss_2048_sha256_mgf1_0_test.json", true, false);
}
@Test
public void testRsaPss2048Sha512_224() throws Exception {
testRsaPss("rsa_pss_2048_sha512_256_mgf1_28_test.json", true, false);
}
@Test
public void testRsaPss2048Sha512_256() throws Exception {
testRsaPss("rsa_pss_2048_sha512_256_mgf1_32_test.json", true, false);
}
// BouncyCastle and Conscrypt do not support RSA-PSS Parameters in the
// encoding of the key. jdk11 should support this, but as long as
// testEncodeDecodePublic fails it makes no sense to try this test.
/*
@ExcludedTest(
providers = {ProviderType.BOUNCY_CASTLE, ProviderType.CONSCRYPT},
comment = "RSA-PSS parameters in RSAKeys is added in jdk11"
)
@NoPresubmitTest(
providers = {ProviderType.OPENJDK},
bugs={"jdk can't read the keys"}
)
@Test
public void testRsaPss2048Sha256WithParams() throws Exception {
testRsaPss("rsa_pss_2048_sha256_mgf1_32_params_test.json", false, true);
}
*/
}