Adding test vectors and tests for the bug fix in CL 264115794.
BouncyCastle had an implementation for X25519 and X448 for some time. But the JCA interface has been added only recently in version 1.61. There is an overflow bug in this version that will be fixed upstream in verion 1.63. The tests ignore some minor bugs (Runtime exceptions instead of checked exceptions) so that the test against BouncyCastle can be added as a presubmit test. Changing the ASN encoding of XDH keys. Previously they used the jdk encoding. This encoding was wrong. NOKEYCHECK=True PiperOrigin-RevId: 264348566 GitOrigin-RevId: 6564f188a3ed50f73c48cb8c41833d8c3798ab78
This commit is contained in:
@@ -41,6 +41,7 @@ import org.junit.runners.Suite.SuiteClasses;
|
||||
JsonKeyWrapTest.class,
|
||||
JsonMacTest.class,
|
||||
JsonSignatureTest.class,
|
||||
JsonXdhTest.class,
|
||||
MessageDigestTest.class,
|
||||
RsaEncryptionTest.class,
|
||||
RsaKeyTest.class,
|
||||
|
||||
@@ -42,6 +42,7 @@ import org.junit.runners.Suite.SuiteClasses;
|
||||
JsonKeyWrapTest.class,
|
||||
JsonMacTest.class,
|
||||
JsonSignatureTest.class,
|
||||
JsonXdhTest.class,
|
||||
MessageDigestTest.class,
|
||||
RsaEncryptionTest.class,
|
||||
RsaKeyTest.class,
|
||||
|
||||
@@ -17,8 +17,12 @@ import static org.junit.Assert.assertEquals;
|
||||
|
||||
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.security.InvalidKeyException;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
@@ -30,15 +34,108 @@ 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.
|
||||
/**
|
||||
* Tests for XDH.
|
||||
*
|
||||
* XDH is a Diffie-Hellman key agreement scheme.
|
||||
* It has been added to jdk11 (http://openjdk.java.net/jeps/324)
|
||||
* <p>XDH is a Diffie-Hellman key agreement scheme over curve25519 or curve448. It has been added to
|
||||
* jdk11 (http://openjdk.java.net/jeps/324) jdk11 also adds new interfaces for XDH. The tests in
|
||||
* this class avoid the new interfaces, so that compiling with older versions is still possible.
|
||||
*/
|
||||
@RunWith(JUnit4.class)
|
||||
public class JsonXdhTest {
|
||||
|
||||
@NoPresubmitTest(
|
||||
providers = {ProviderType.BOUNCY_CASTLE},
|
||||
bugs = {"b/138722408"})
|
||||
@Test
|
||||
public void testKeyGeneration() throws Exception {
|
||||
KeyPairGenerator kpg;
|
||||
try {
|
||||
kpg = KeyPairGenerator.getInstance("XDH");
|
||||
} catch (NoSuchAlgorithmException ex) {
|
||||
System.out.println("XDH not supported");
|
||||
return;
|
||||
}
|
||||
// An alternative is
|
||||
// NamedParameterSpec paramSpec = new NamedParameterSpec("X25519");
|
||||
// kpg.initialize(paramSpec);
|
||||
// But this only compiles with jdk11.
|
||||
kpg.initialize(255);
|
||||
KeyPair kp = kpg.generateKeyPair();
|
||||
PrivateKey priv = kp.getPrivate();
|
||||
PublicKey pub = kp.getPublic();
|
||||
|
||||
// Encodings are a bit of a problem.
|
||||
byte[] privEncoded = priv.getEncoded();
|
||||
System.out.println(
|
||||
"X25519 privat key format:"
|
||||
+ priv.getFormat()
|
||||
+ " encoded:"
|
||||
+ TestUtil.bytesToHex(privEncoded));
|
||||
|
||||
byte[] pubEncoded = pub.getEncoded();
|
||||
System.out.println(
|
||||
"X25519 public key format:"
|
||||
+ pub.getFormat()
|
||||
+ " encoded:"
|
||||
+ TestUtil.bytesToHex(pubEncoded));
|
||||
}
|
||||
|
||||
/**
|
||||
* An alternative way to generate an XDH key is to use specific names for the algorithm (i.e.
|
||||
* "X25519" or "X448"). These names fully specify key size and algorithm.
|
||||
*
|
||||
* <p>This test generates a key pair with such an algorithm name, serializes the keys, prints them
|
||||
* and the imports the keys back again. This allows to debug issues such as
|
||||
* https://bugs.openjdk.java.net/browse/JDK-8213493
|
||||
*/
|
||||
public void testKeyGenerationWithName(String algorithmName) throws Exception {
|
||||
KeyPairGenerator kpg;
|
||||
try {
|
||||
kpg = KeyPairGenerator.getInstance(algorithmName);
|
||||
} catch (NoSuchAlgorithmException ex) {
|
||||
System.out.println(algorithmName + " is not supported");
|
||||
return;
|
||||
}
|
||||
KeyPair kp = kpg.generateKeyPair();
|
||||
|
||||
PrivateKey priv = kp.getPrivate();
|
||||
PublicKey pub = kp.getPublic();
|
||||
|
||||
// Encodings are a bit of a problem.
|
||||
byte[] privEncoded = priv.getEncoded();
|
||||
System.out.println(
|
||||
algorithmName
|
||||
+ " privat key format:"
|
||||
+ priv.getFormat()
|
||||
+ " encoded:"
|
||||
+ TestUtil.bytesToHex(privEncoded));
|
||||
|
||||
byte[] pubEncoded = pub.getEncoded();
|
||||
System.out.println(
|
||||
algorithmName
|
||||
+ " public key format:"
|
||||
+ pub.getFormat()
|
||||
+ " encoded:"
|
||||
+ TestUtil.bytesToHex(pubEncoded));
|
||||
|
||||
KeyFactory kf = KeyFactory.getInstance("XDH");
|
||||
PKCS8EncodedKeySpec privKeySpec = new PKCS8EncodedKeySpec(privEncoded);
|
||||
PrivateKey unusedPrivKey2 = kf.generatePrivate(privKeySpec);
|
||||
X509EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(pubEncoded);
|
||||
PublicKey unusedPubKey2 = kf.generatePublic(pubKeySpec);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testKeyGenerationX25519() throws Exception {
|
||||
testKeyGenerationWithName("X25519");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testKeyGenerationX448() throws Exception {
|
||||
testKeyGenerationWithName("X448");
|
||||
}
|
||||
|
||||
/** Convenience mehtod to get a String from a JsonObject */
|
||||
protected static String getString(JsonObject object, String name) throws Exception {
|
||||
return object.get(name).getAsString();
|
||||
@@ -50,36 +147,39 @@ public class JsonXdhTest {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* This test requires test vectors where the public key is X.509 encoded and the private key is
|
||||
* PKCS #8 encoded. I.e. test vectors that use the JSON schema "xdh_asn_comp_schema.json".
|
||||
*
|
||||
* 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.
|
||||
* <p>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
|
||||
* <p>Example for test vector
|
||||
*
|
||||
* <pre>
|
||||
* {
|
||||
* "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" : []
|
||||
* },
|
||||
* ...
|
||||
**/
|
||||
* "algorithm" : "XDH",
|
||||
* "generatorVersion" : "0.8",
|
||||
* "numberOfTests" : 32,
|
||||
* "header" : [],
|
||||
* "schema" : "xdh_asn_comp_schema.json",
|
||||
* "testGroups" : [
|
||||
* {
|
||||
* "curve" : "curve25519",
|
||||
* "type" : "XDHComp",
|
||||
* "tests" : [
|
||||
* {
|
||||
* "tcId" : 1,
|
||||
* "comment" : "normal case",
|
||||
* "public" : "302c300706032b...",
|
||||
* "private" : "302e0201003007...",
|
||||
* "shared" : "87b7f212b627f7...",
|
||||
* "result" : "valid",
|
||||
* "flags" : []
|
||||
* },
|
||||
* ...
|
||||
* </pre>
|
||||
*/
|
||||
public void testXdhComp(String filename) throws Exception {
|
||||
// Checks the precondition for this test.
|
||||
// XDH has been added in jdk11.
|
||||
@@ -90,14 +190,17 @@ public class JsonXdhTest {
|
||||
return;
|
||||
}
|
||||
|
||||
final String expectedVersion = "0.6";
|
||||
final String expectedSchema = "xdh_asn_comp_schema.json";
|
||||
JsonObject test = JsonUtil.getTestVectors(filename);
|
||||
String generatorVersion = test.get("generatorVersion").getAsString();
|
||||
if (!generatorVersion.equals(expectedVersion)) {
|
||||
String schema = test.get("schema").getAsString();
|
||||
if (!schema.equals(expectedSchema)) {
|
||||
System.out.println(
|
||||
"XDH: expecting test vectors with version "
|
||||
+ expectedVersion
|
||||
+ " found vectors with version "
|
||||
"XDH: expecting JSON schema "
|
||||
+ expectedSchema
|
||||
+ " found "
|
||||
+ schema
|
||||
+ " generatorVersion:"
|
||||
+ generatorVersion);
|
||||
}
|
||||
int numTests = test.get("numberOfTests").getAsInt();
|
||||
@@ -145,7 +248,11 @@ public class JsonXdhTest {
|
||||
+ "\nshared:"
|
||||
+ sharedHex
|
||||
+ "\nexpected:"
|
||||
+ expectedHex);
|
||||
+ expectedHex
|
||||
+ "\npublic:"
|
||||
+ TestUtil.bytesToHex(publicEncoded)
|
||||
+ "\nprivate:"
|
||||
+ TestUtil.bytesToHex(priv));
|
||||
errors++;
|
||||
} else {
|
||||
passedTests++;
|
||||
@@ -158,6 +265,19 @@ public class JsonXdhTest {
|
||||
} else {
|
||||
rejectedTests++;
|
||||
}
|
||||
} catch (IllegalStateException | ClassCastException ex) {
|
||||
// TODO(bleichen): Eventually the cases here should be counted as errors.
|
||||
// BouncyCastle throws IllegalStateException when the shared secret is all 0.
|
||||
// The library throws a ClassCastException in some case where the public key uses
|
||||
// a different curve. Instead of these exception I'd rather expect checked exception.
|
||||
// However, testing for incorrect results is more important at this point.
|
||||
System.out.println(
|
||||
"Test vector with tcId:" + tcid + " comment:" + comment + " throws:" + ex);
|
||||
if (result.equals("valid")) {
|
||||
skippedTests++;
|
||||
} else {
|
||||
rejectedTests++;
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
// Other exceptions typically indicate that something is wrong with the implementation.
|
||||
System.out.println(
|
||||
@@ -166,15 +286,31 @@ public class JsonXdhTest {
|
||||
}
|
||||
}
|
||||
}
|
||||
System.out.println(
|
||||
filename
|
||||
+ " passed:"
|
||||
+ passedTests
|
||||
+ " skipped:"
|
||||
+ skippedTests
|
||||
+ " errors:"
|
||||
+ errors
|
||||
+ " rejected:"
|
||||
+ rejectedTests);
|
||||
assertEquals(0, errors);
|
||||
assertEquals(numTests, passedTests + rejectedTests + skippedTests);
|
||||
}
|
||||
|
||||
@NoPresubmitTest(
|
||||
providers = {ProviderType.BOUNCY_CASTLE, ProviderType.OPENJDK},
|
||||
bugs = {"b/138722408"})
|
||||
@Test
|
||||
public void testX25519() throws Exception {
|
||||
testXdhComp("x25519_asn_test.json");
|
||||
}
|
||||
|
||||
@NoPresubmitTest(
|
||||
providers = {ProviderType.BOUNCY_CASTLE, ProviderType.OPENJDK},
|
||||
bugs = {"b/138722408"})
|
||||
@Test
|
||||
public void testX448() throws Exception {
|
||||
testXdhComp("x448_asn_test.json");
|
||||
|
||||
+3862
-931
File diff suppressed because it is too large
Load Diff
+6678
-546
File diff suppressed because it is too large
Load Diff
+3312
-392
File diff suppressed because it is too large
Load Diff
+3795
-845
File diff suppressed because it is too large
Load Diff
+6817
-644
File diff suppressed because it is too large
Load Diff
+3381
-442
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user