Skip to content

Commit b64c278

Browse files
committed
XDHKeyAgreementMRTest now expects the curve binding kept under org.bouncycastle.emulate.oracle, and checks the RFC 8418 XDH HKDF agreements on both curves with and without a UKM against an independently computed HKDF.
1 parent 9492bab commit b64c278

1 file changed

Lines changed: 88 additions & 14 deletions

File tree

‎prov/src/test/jdk1.11/org/bouncycastle/jcajce/provider/test/XDHKeyAgreementMRTest.java‎

Lines changed: 88 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
import java.security.spec.NamedParameterSpec;
1111

1212
import javax.crypto.KeyAgreement;
13+
import javax.crypto.Mac;
14+
import javax.crypto.spec.SecretKeySpec;
1315

1416
import junit.framework.TestCase;
1517
import org.bouncycastle.jcajce.spec.UserKeyingMaterialSpec;
@@ -56,26 +58,87 @@ public void testUkmSaltIsApplied()
5658
assertFalse("salt ignored in HKDF agreement", Arrays.areEqual(noSalt, salted1));
5759
}
5860

61+
/**
62+
* The RFC 8418 XDHwith*HKDF agreements on both curves, without a UKM, with one, and with one
63+
* plus a salt. Each result is checked against HKDF (RFC 5869) computed independently from the
64+
* raw agreement with the SunJCE HMAC, and the UKM is checked to be the HKDF info, not the salt.
65+
*/
5966
public void testRFC8418HKDFAgreements()
6067
throws Exception
6168
{
62-
KeyPairGenerator kpGen = KeyPairGenerator.getInstance("X448", BC);
63-
64-
KeyPair kp1 = kpGen.generateKeyPair();
65-
KeyPair kp2 = kpGen.generateKeyPair();
66-
6769
String[] algorithms = new String[]{ "XDHwithSHA256HKDF", "XDHwithSHA384HKDF", "XDHwithSHA512HKDF" };
68-
UserKeyingMaterialSpec ukmSpec = new UserKeyingMaterialSpec(Hex.decode("beeffeed"));
70+
String[] macs = new String[]{ "HmacSHA256", "HmacSHA384", "HmacSHA512" };
71+
String[] curves = new String[]{ "X25519", "X448" };
72+
73+
byte[] ukm = Hex.decode("beeffeed");
74+
byte[] salt = Hex.decode("000102030405060708090a0b0c0d0e0f");
6975

70-
for (int i = 0; i != algorithms.length; i++)
76+
for (int c = 0; c != curves.length; c++)
7177
{
72-
byte[] sec1 = agree(algorithms[i], kp1.getPrivate(), kp2.getPublic(), ukmSpec);
73-
byte[] sec2 = agree(algorithms[i], kp2.getPrivate(), kp1.getPublic(), ukmSpec);
78+
KeyPairGenerator kpGen = KeyPairGenerator.getInstance(curves[c], BC);
7479

75-
assertTrue(algorithms[i] + " mismatch", Arrays.areEqual(sec1, sec2));
80+
KeyPair kp1 = kpGen.generateKeyPair();
81+
KeyPair kp2 = kpGen.generateKeyPair();
82+
83+
byte[] z = agree(curves[c], kp1.getPrivate(), kp2.getPublic(), null);
84+
85+
for (int i = 0; i != algorithms.length; i++)
86+
{
87+
String label = algorithms[i] + "/" + curves[c];
88+
89+
byte[] noUkm = checkHKDFAgreement(label + " no ukm", algorithms[i], kp1, kp2, null,
90+
hkdf(macs[i], null, z, new byte[0], z.length));
91+
byte[] withUkm = checkHKDFAgreement(label + " ukm", algorithms[i], kp1, kp2, new UserKeyingMaterialSpec(ukm),
92+
hkdf(macs[i], null, z, ukm, z.length));
93+
checkHKDFAgreement(label + " ukm+salt", algorithms[i], kp1, kp2, new UserKeyingMaterialSpec(ukm, salt),
94+
hkdf(macs[i], salt, z, ukm, z.length));
95+
96+
assertFalse(label + " ukm ignored", Arrays.areEqual(noUkm, withUkm));
97+
assertFalse(label + " ukm used as salt", Arrays.areEqual(withUkm, hkdf(macs[i], ukm, z, new byte[0], z.length)));
98+
}
7699
}
77100
}
78101

102+
private byte[] checkHKDFAgreement(String label, String algorithm, KeyPair kp1, KeyPair kp2, AlgorithmParameterSpec spec, byte[] expected)
103+
throws Exception
104+
{
105+
byte[] sec1 = agree(algorithm, kp1.getPrivate(), kp2.getPublic(), spec);
106+
byte[] sec2 = agree(algorithm, kp2.getPrivate(), kp1.getPublic(), spec);
107+
108+
assertTrue(label + " mismatch", Arrays.areEqual(sec1, sec2));
109+
assertTrue(label + " known answer", Arrays.areEqual(expected, sec1));
110+
111+
return sec1;
112+
}
113+
114+
// RFC 5869 using the SunJCE HMAC, so the expected value does not come from BC's HKDF.
115+
private static byte[] hkdf(String macAlg, byte[] salt, byte[] ikm, byte[] info, int length)
116+
throws Exception
117+
{
118+
Mac mac = Mac.getInstance(macAlg, "SunJCE");
119+
120+
mac.init(new SecretKeySpec(salt != null ? salt : new byte[mac.getMacLength()], macAlg));
121+
byte[] prk = mac.doFinal(ikm);
122+
123+
mac.init(new SecretKeySpec(prk, macAlg));
124+
125+
byte[] okm = new byte[length];
126+
byte[] t = new byte[0];
127+
for (int off = 0, counter = 1; off < length; counter++)
128+
{
129+
mac.update(t);
130+
mac.update(info);
131+
mac.update((byte)counter);
132+
t = mac.doFinal();
133+
134+
int len = Math.min(t.length, length - off);
135+
System.arraycopy(t, 0, okm, off, len);
136+
off += len;
137+
}
138+
139+
return okm;
140+
}
141+
79142
public void testEmulateOracleProperty()
80143
throws Exception
81144
{
@@ -94,15 +157,26 @@ public void testEmulateOracleProperty()
94157
assertEquals("inappropriate key for X25519", e.getMessage());
95158
}
96159

97-
// ...with it the agreement behaves as Oracle's XDH and accepts either.
98-
System.setProperty(Properties.EMULATE_ORACLE, "true");
160+
// ...with it the agreement reports itself as Oracle's XDH, but stays bound to its curve.
161+
Properties.setThreadOverride(Properties.EMULATE_ORACLE, true);
99162
try
100163
{
101-
KeyAgreement.getInstance("X25519", BC).init(x448Kp.getPrivate());
164+
try
165+
{
166+
KeyAgreement.getInstance("X25519", BC).init(x448Kp.getPrivate());
167+
fail("X448 key accepted by X25519 agreement under emulate oracle");
168+
}
169+
catch (InvalidKeyException e)
170+
{
171+
assertEquals("inappropriate key for XDH", e.getMessage());
172+
}
173+
174+
// the XDH name itself is not tied to a curve
175+
KeyAgreement.getInstance("XDH", BC).init(x448Kp.getPrivate());
102176
}
103177
finally
104178
{
105-
System.clearProperty(Properties.EMULATE_ORACLE);
179+
Properties.removeThreadOverride(Properties.EMULATE_ORACLE);
106180
}
107181
}
108182

0 commit comments

Comments
 (0)