1010import java .security .spec .NamedParameterSpec ;
1111
1212import javax .crypto .KeyAgreement ;
13+ import javax .crypto .Mac ;
14+ import javax .crypto .spec .SecretKeySpec ;
1315
1416import junit .framework .TestCase ;
1517import 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